Let's say you have a piece of code like this (what it actually does is inconsequential):
...
new_root = fs.revision_root(self.fs_layer, rev_num, \
self._apr_pool)
for path in self.files:
history = fs.node_history(new_root, path, pool)
history = fs.history_prev(history, 0, pool)
_, rev = fs.history_location(history, pool)
if rev != self._head_num:
...
Question: how do you test that?
"Simple", you say. "Just allow a sequence of node_history/history_prev/history_location commands for every entry in self.files".
Well, yes, if self.files is a list; what do you do if it's a dict or a set or some other datatype where the order of iteration isn't defined?
*cricket* *cricket*
That's what I thought; not so easy, is it?
To address this exact usage scenario, MockSession objects come with an add_any_order method.
add_any_order is used to specify that you want a given pool of commands run, but you don't care what order they're executed in.
Before we address the example given above, let's target a simpler problem:
...
for path in self.files:
file_stream = fs.file_contents(rev_root, path, \
self._apr_pool)
self.file_streams.append(file_stream)
...
Remember, we're assuming self.files is a dict or a set.
The code to test this looks something like this:
any_order = mock.add_any_order()
for path in self_files:
any_order.add_command(fs.file_contents, \
(rev_root, path, apr_pool))
or, spelled a little differently:
any_order = mock.AnyOrder()
for path in self_files:
any_order.add_command(fs.file_contents, \
(rev_root, path, apr_pool))
mock.add_any_order(any_order)
In both snippets, the same essential steps take place:
-
Create an instance of
svnmock.mock.AnyOrder.
-
Add commands to it.
-
Inform the
MockSession object about the AnyOrder instance.
Note that in the first snippet, even though step 3 is done at the same time as step 1, we don't have to re-notify session that we've added some commands; that's taken care of automatically.
Letting Go, Part 2
Now that we've seen the basics, let's take on the original example.
So you don't have to keep scrolling back up, here it is again:
...
new_root = fs.revision_root(self.fs_layer, rev_num, \
self._apr_pool)
for path in self.files:
history = fs.node_history(new_root, path, apr_pool)
history = fs.history_prev(history, 0, apr_pool)
_, rev = fs.history_location(history, apr_pool)
if rev != self._head_num:
...
What follows is the solution.
We'll talk about it on the other side.
new_root = mock.add_command(fs.revision_root, \
(self_fs_layer, rev_num, apr_pool))
ao_files = mock.add_any_order()
for path in self_files:
path_seq = ao_files.add_sequence()
history = path_seq.add_command(fs.node_history, \
(new_root, path, apr_pool))
history = path_seq.add_command(fs.history_prev, \
(history, 0, apr_pool))
_, rev = path_seq.add_command(fs.history_location, \
(history, apr_pool))
Let's break that down.
First, don't get scared by the appearance of AnyOrder.add_sequence; it works just like MockSession.add_any_order, which you've already seen.
The difference is that instead of inserting an AnyOrder instance, it inserts a Sequence.
Sequence, much like MockSession, is used to say that you want a particular series of function calls run in a defined order.
This is just we want here: we may not care what order we get the files from self.files, but we most certainly care that fs.node_history is run before fs.history_prev.
To that end, we group the three fs.* function calls into a Sequence, then add that to our AnyOrder instance.
This gets us the behaviour we want: the files may be iterated over in any order, but the commands needed to process them will be checked strictly.