Wed 4 Jan 2006
So, the other day, I was minding my own business, just writing some test cases for Cypress’s new storage.svn.local module, as I do. I kept getting strange errors from svnmock that a certain API function isn’t being called with the right arguments. After a while, I eventually track down the problem to a chunk of code iterating over a dictionary, meaning that the iteration isn’t guaranteed to be stable.
This was something that I hadn’t anticipated when designing the API, the need to say “the following commands need to be executed, but I don’t care in what order”. The next few days are a blur, resulting in a near-total reworking of svnmock’s internals. The end result looks very much like typecheck’s internals: a set of plug-in style classes that do all the work, controlled by a single dispatcher.
One of these classes is AnyOrder, which is designed to solve the “run the following commands in whatever order you can” problem. This class proved tricky to implement, primarily because I was underestimating what kind of mechanism it would require. My initial test cases were too easy; the machinery needed to solve:
sequence_1 = Seq([command_1, command_2]) sequence_2 = Seq([command_3, command_4]) AnyOrder([sequence_1, sequence_2])
is simple, because the first commands in each sequence are different. On the other hand,
sequence_1 = Seq([command_1, command_2]) sequence_2 = Seq([command_1, command_3]) AnyOrder([sequence_1, sequence_2])
is significantly harder, since the first commands are the same. In the end, I solved this with a backtracking-capable state machine. Actually coding it wasn’t the hard part, though: the hard part was realising I needed a backtracking state machine in the first place.
The most recent problem has been how to reconcile svnmock’s return values with the AnyOrder mechanism. Recall that you can do something like
pool = session.add(core.svn_pool_create, [None]) scratch_pool = session.add(core.svn_pool_create, [pool])
and svnmock will assert that the scratch pool is indeed created out of the first pool (by tracking the return value from the first core.apr_pool_create() call). It turns out that this becomes tricky when you introduce backtracking in to the mix (answering “why” requires a whiteboard to illustrate).
The current solution to this involves replacing the old return value scheme with a dedicated Return class. Instances of the class store information about the function that returned them, such as the exact API function and its arguments. This works in most cases, but falls apart in situations like this:
pool_1 = core.svn_pool_create(None) pool_2 = core.svn_pool_create(None) scratch = core.svn_pool_create(pool_1)
Since both pool_1 and pool_2 are created with the same API function and arguments, there’s no way to assert that scratch is generated from pool_1 as opposed to pool_2. I’m not sure if this is common enough to worry about, though; I suppose if this little module catches on, and this particular bug starts causing people headaches, I’ll be more motivated to fix it.