Release Story: Rhino Mocks 2.3.3
So I'm sitting by the computer reading something and suddenly the new email sound startles and the Bugs folder in Outlook light up. This usually don't happen unless I enter a bug into my bug tracking system, so naturally I was curious about the nature of the bug and the person who entered it. I still don't know who entered the bug (a very annoying problem in Flyspary, you seem to have to register to enter bug non anonymously) but the bug was a masterpiece.
It had a clear explanation of the problem as well as code the demonstrated it. This was invaluable in finding out the cause. If you'll check the time stamps, the bug was entered in 06:29Am, I managed to reproduce it four minutes later. And that includes the time to open VS.Net! Five minutes afterward I knew what the problem was and was able to think of a fix for this.
The first thing i thought when I looked at the problem was that the problem was that the class was [Serializable], but it took just one look at the failure to see that i was heading in the wrong direction and to find the cause of the problem. The problem was that the class was overriding GetHashCode() & Equals() and that was when all hell broke loose. In order to manage the mock objects, Rhino Mocks store them inside a hash table (custom collection, but that is minor detail). When you're storing an object in a hash table the hash table uses the GetHashCode() & Equals() quite extensively in order to put it in its place.
Unfortunately, this cause huge slight problem with Rhino Mocks, since any call on a mocked object goes to the MockRepository where the object's state is looked up in the hash table and then executed. Obviously, if the hash table itself is calling methods and each method cause a hashtable seek, which call methods, which... You get the idea.
Microsoft thought about it when they built the hash table and they provide an interface you can implement that would allow you to get the hash code of an object externally. Just pass an IHashCodeProvider to the Hashtable constructor and you're set. Unfortunately, I didn't see any way to implement this for Rhino Mocks without using the object's GetHashCode() since I wanted a (relatively) unique number that would be consistent across the call's invocations. Since I couldn't see a way to do so, I just work around it by special casing Object's method and passing them without the usual checks. This worked in my tests and in the code that I was using, so I didn't give it any more thought. It wasn't the best solution, but that was the best solution that I could think of.
Let a couple of months pass and enter this bug. Suddenly it became imperative to find a way around this limitation, after all, how lame is it to have a mocking framework that doesn't allow you to overriding Equals() or GetHashCode()? I spent around 15 minutes thinking up and then coding a hash code provider that wouldn't need the object's state. The secret is that I'm already tagging each mock object with a special interface (IProxy is previous versions, IMockedObject now) which is used to locate the mock object's repository.
I simply added a Guid to the this interface and built a HashCodeProvider and Comparer that accepted a mock object and acted upon the Guid. I used a Guid because that is guaranteed to be unique and allow me to easily distinguish between the mock objects. I could've used the interface itself for that, but that wouldn't have given me an easy way to tell apart mocked objects and integers, which were the other option, suffer from thread safety problems.
Anyway, I made the change and run the tests (only 92/272 broken tests :-) )and got the scary ExecutionEngineException. To cut a long story short, I made some assumptions about classes that didn't hold true for interfaces. I must say that I'm very impressed with DynamicProxy and the things that it allows me to do (re-route a call midway is quite impressive, for instance). I understand that this is a standard feature in Java and I think that it should've been in .Net as well. Remoting proxies are... not really proxies. (I just found out how to lie about the object's type, I can't think of any use case for that, but it's nice to know that I can :-) )
To be short (a hopeless cause now), it took less than two hours to change a fundamental part of the way Rhino Mocks is managing mock objects. I'm releasing the new version with confidence that it will work as expected. I just can't imagine how bad it would be if I had to do it testless.
There are two changes that you should be aware of:
- You cannot set expectation on System.Object's method without overriding them in your class.
- The default ToString() value now match the default behavior of the system and will no longer return null.
Just a sentence about the version number, version 2.3 is actually 2.3.2054.etc, which I only found out about after releasing 2.3.1.etc which means that the more recent version is considered to be older than the older one which necessitated jumping over 2.3.2.
Comments
Comment preview