tempus-fugit

Java micro-library for writing & testing concurrent code

Timeout in JMock Synchroniser

JMock’s Synchroniser serialises access to the mock object’s “context”, it means all invocations of mocked methods call will be synchronized on the same monitor, effectively forcing them to run in sequence without thread safety concerns. As it uses synchronized though, you can (with some effort) get into trouble with tests that never finish.

If you’re seeing this kind of thing, apart from using the @Test(timeout=1000) annotation, you might consider an alternative ThreadingPolicy implementation using Locks that can timeout and maintain liveliness.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class TimingOutSynchroniser implements ThreadingPolicy {

    private final Lock lock = new ReentrantLock();
    private final Condition awaitingStatePredicate = lock.newCondition();
    private final Duration lockTimeout;

    private Error firstError = null;

    public TimingOutSynchroniser() {
        this(millis(250));
    }

    public TimingOutSynchroniser(Duration timeout) {
        this.lockTimeout = timeout;
    }

    public void waitUntil(StatePredicate predicate) throws InterruptedException {
        waitUntil(predicate, new InfiniteTimeout());
    }

    /**
     * Waits up to a timeout for a StatePredicate to become active.  Fails the
     * test if the timeout expires.
     */
    public void waitUntil(StatePredicate predicate, long timeoutMs) throws InterruptedException {
        waitUntil(predicate, new FixedTimeout(timeoutMs));
    }

    private void waitUntil(StatePredicate predicate, Timeout testTimeout) throws InterruptedException {
        try {
            lock.tryLock(lockTimeout.inMillis(), MILLISECONDS);
            while (!predicate.isActive()) {
                try {
                    awaitingStatePredicate.await(testTimeout.timeRemaining(), MILLISECONDS);
                } catch (TimeoutException e) {
                    if (firstError != null)
                        throw firstError;
                    Assert.fail("timed out waiting for " + asString(predicate));
                }
            }
        } finally {
            if (lock.tryLock())
                lock.unlock();
        }

    }

    public Invokable synchroniseAccessTo(final Invokable mockObject) {
        return new Invokable() {
            public Object invoke(Invocation invocation) throws Throwable {
                return synchroniseInvocation(mockObject, invocation);
            }
        };
    }

    private Object synchroniseInvocation(Invokable mockObject, Invocation invocation) throws Throwable {
        try {
            lock.tryLock(lockTimeout.inMillis(), MILLISECONDS);
            try {
                return mockObject.invoke(invocation);
            } catch (Error e) {
                if (firstError == null)
                    firstError = e;
                throw e;
            } finally {
                awaitingStatePredicate.signalAll();
            }
        } finally {
            if (lock.tryLock())
                lock.unlock();
        }
    }
}

Over to you...