Mocking Challange: Mocking, TDD and Multi Threading
A few days ago, Ron Jeffries offered a challange, find a way to use mocks to battle dead locks using mutexes. This is following a huge debate on the Test Driven Deveopment list about mocks, pros & cons. I think you can guess where I am on this debate. Anyway, this is exactly the kind of a challange that I like, so I gave it a shot.
Now, it's not a 100% solution, I'll be the first to admit, but it's good enough as a proof of concept as well as answering the challange. Now, for the purpose of discussion, I limited myself to the following properties of mutexes:
- You can only acquire & release a mutex, you can't pulse it, try acquiring, etc.
- An actual mutex is not involved, as a matter of fact (I could add it, but then what would happen with a real dead lock?), beside, it would complicate the implementation, and this is a PoC only.
I track the following issues within a thread:
- Acquiring but not releasing.
- Releasing an unaquired mutex.
- Releasing out of order.
Between mutliply threads:
- Acquiring & releasing not on the same order on different threads.
All in all, it took me about an hour to write this, so take that into account when you read it. Here is a sample code that explains how it works, first, here are our threads:
private static void OneTwoThreeAquireRelease()
{
IMutex one = MutexRepository.Get("One");
IMutex two = MutexRepository.Get("Two");
IMutex three = MutexRepository.Get("Three");
one.Aquire();
two.Aquire();
three.Aquire();
three.Release();
two.Release();
one.Release();
Assert.IsTrue(VerifyingMockMutexRepository.NoAcquiredMutexesOnThread);
}
private static void TwoOneThreeAquireRelease()
{
IMutex one = MutexRepository.Get("One");
IMutex two = MutexRepository.Get("Two");
IMutex three = MutexRepository.Get("Three");
two.Aquire();
one.Aquire();
three.Aquire();
three.Release();
one.Release();
two.Release();
Assert.IsTrue(VerifyingMockMutexRepository.NoAcquiredMutexesOnThread);
}
Notice that within each thread, they are perfectly okay, but when you mix them... you get a potential dead lock. And here is the test:
[Test]
public void AcuireAndReleaseInWrongOrderOnDifferentThreads()
{
Thread[] threads = new Thread[2];
threads[0] = new Thread(OneTwoThreeAquireRelease);
threads[1] = new Thread(TwoOneThreeAquireRelease);
RunAllThreadsAndWaitForCompletion(threads);
IList<string> problems = VerifyingMockMutexRepository.GetAllProblems();
Assert.AreEqual(2, problems.Count);
Assert.AreEqual("Mutex One is in circular relationship with Two", problems[0]);
Assert.AreEqual("Mutex Two is in circular relationship with One", problems[1]);
}
private static void RunAllThreadsAndWaitForCompletion(ICollection<Thread> threads)
{
foreach (Thread thread in threads)
{
thread.Start();
}
foreach (Thread thread in threads)
{
thread.Join();
}
}
Viola! Success, we have liftoff :-D We have successfully detected a potential deadlock. The code is structure to a IMutex and IMutexRepository, where the MutexRepository is merely the access point for that, so it's very easy to switch that for tests (or you can use DI or Castle to do it for you.)
You can get the code here. Again, this is a Proof Of Concept only, so have pity on me when you point out that when this or that happens, everything blows apart. I tried to make it work correctly in all cases, but I probably missed something.
Why I didn't use Rhino Mocks? Well, I could, but it just wasn't worth the time & trouble when I started from scratch and I had to execute behavior on the tests. If I had an existing interface, I would've probably used it, though.
Comments
Comment preview