JMock and Threads: Deterministic Execution

JMock includes a number of classes in the org.jmock.lib.concurrent package to help test concurrent code either with or without mock objects. Some of these classes emulate multiple threads within the single test thread while others make testing with multiple threads easier. Most of them are independent of the rest of jMock: you can use them even if you prefer a different mock object library or don't want to use mock objects at all.

The simplest of the emulation classes is the DetermisticExecutor. This implements the Executor interface defined by Java's standard library but instead of executing tasks on background threads, it queues the tasks for later execution. The test can then tell it to run the tasks and they will run on the test thread. If the tasks fail, exceptions are thrown on the test thread and caught and reported by the test framework. (When thrown on background threads exceptions can be swallowed or only reported on the console and the test may consider the test to have passed.)

Here's an example of the deterministic executor being used to test the behaviour of a multithreaded search that farms a search to different search engines concurrently and announces results to a consumer they arrive. When all results have been received it announces that the search has finished.

SearchEngine searchA = context.mock(SearchEngine.class, "searchA");
SearchEngine searchB = context.mock(SearchEngine.class, "searchB");

DeterministicExecutor executor = new DeterministicExecutor();

MultiSearch search = new MultiSearch(executor, asList(searchA, searchB));

SearchConsumer searchConsumer = context.mock(SearchConsumer.class);

@Test
public void runsSearchConcurrentlyOnAllSearchEngines() {
    final Set<String> keywords = setOf("sheep", "cheese");
    
    final Set<SearchResult> resultsFromA = setOf(result("A1"), result("A2")));
    final Set<SearchResult> resultsFromA = setOf(result("B1"), result("B2"), result("B3"));
    
    context.checking(new Expectations() {{
        allowing(searchA).search(keywords); will(returnValue(resultsFromA));
        allowing(searchB).search(keywords); will(returnValue(resultsFromB));

        oneOf(consumer).searchResults(resultsFromA);
            when(searching.isNot("finished"));
        oneOf(consumer).searchResults(resultsFromB);
            when(searching.isNot("finished"));
        oneOf(consumer).searchFinished();
            then(searching.is("finished"));
    }});
    
    search.search(keywords, consumer);

    executor.runUntilIdle();
}

The test tells the executor to run until idle, which runs pending tasks until there are no more to execute. This supports tasks that launch new tasks. If that happened forever, the test would, of course, enter an infinite loop. The DeterministicExecutor also let's a test run only the pending tasks to support applications that spawn an infinite sequence of tasks.

The deterministic executor captures tasks before running them instead of running them immediately on the calling thread because those two execution strategies have different behaviour in terms of reentrancy. Immediate execution of a task allows the task to call back into the object before the method that launched the task has returned. The task could therefore execute while the object is in an invalid state. With a multithreaded executor, the object's lock would stop a concurrent task entering the object until the method that launched it had released the lock; that behaviour is what we must emulate in single-threaded tests. Most of the time this doesn't make a difference but when it does the failures can very confusing.

Copyright © 2009 Nat Pryce. Posted 2009-03-16. Share it.

Comments powered by Disqus