Brute force concurrency testing

time to read 2 min | 357 words

Testing concurrency is hard, just ask anyone who had tried. And tracking down concurrent bugs is even harder.

I just run into concurrency issue with SvnBridge, and I decided that I have better have a smoke test around to prove that there are no obvious concurrency issues with the code.

Since I already have over a hundred integration tests, I thought that this would make a great concurrency test. We know that all the tests are passing. Now let us see if they are all passing together. This is quick & dirty, but it expose at least two bugs so far, one of them the original one that we have seen:

public class ConcurrentActionsTest
{
	[Fact]
	public void RunAllTestsConcurrentyly()
	{
		List<TestData> tests = new List<TestData>();
		Type[] types = Assembly.GetExecutingAssembly().GetTypes();
		foreach (Type type in types)
		{
			if (type.IsAbstract)
				continue;
			if (type == typeof(ConcurrentActionsTest))
				continue;
			foreach (MethodInfo info in type.GetMethods())
			{
				object[] attributes = info.GetCustomAttributes(typeof(SvnBridgeFactAttribute), true);
				if (attributes.Length == 0)
					continue;
				tests.Add(new TestData((SvnBridgeFactAttribute)attributes[0], info));
			}
		}

		List<IAsyncResult> results = new List<IAsyncResult>();
		List<Exception> errors = new List<Exception>();
		ExecuteTestDelegate exec = ExecuteTest;
		foreach (TestData test in tests)
		{
			foreach (ITestCommand command in test.Fact.CreateTestCommands(test.Method))
			{
				IAsyncResult invoke = exec.BeginInvoke(test, command, errors, null, null);
				results.Add(invoke);
			}
		}
		foreach (IAsyncResult result in results)
		{
			result.AsyncWaitHandle.WaitOne();
			exec.EndInvoke(result);
		}
		if (errors.Count > 0)
		{
			StringBuilder sb = new StringBuilder();
			foreach (Exception error in errors)
			{
				sb.AppendLine(error.ToString());
			}
			throw new Exception(sb.ToString());
		}
	}

	private delegate void ExecuteTestDelegate(TestData test, ITestCommand command, List<Exception> errors);

	private void ExecuteTest(TestData test, ITestCommand command, List<Exception> errors)
	{
		try
		{
			object instance = Activator.CreateInstance(test.Method.DeclaringType);
			command.Execute(instance);
		}
		catch (TargetInvocationException e)
		{
			lock (errors)
				errors.Add(e.InnerException);
		}
		catch (Exception e)
		{
			lock (errors)
				errors.Add(e);
		}
	}

	private class TestData
	{
		public readonly MethodInfo Method;
		public readonly SvnBridgeFactAttribute Fact;

		public TestData(SvnBridgeFactAttribute fact, MethodInfo method)
		{
			Fact = fact;
			Method = method;
		}
	}
}

As I was writing this post, I figure out one issue, the other would require concurrent debugging...