Lesson 4

In our last lesson, talked about how to use svnmock to simulate every possible error condition under the sun. Thus far, we've focused on exerting the maximum amount of control over program behaviour possible; now, we'll talk about how to deal with situations where that much control is bad thing.

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?

Letting Go, Part 1

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:

  1. Create an instance of svnmock.mock.AnyOrder.
  2. Add commands to it.
  3. 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.

Next...

Next up, we'll discuss some tricks, tips and best practices for getting the most out of svnmock.

svnmock