« Return to Thread: JMock not thread safe - how does one do multi-threaded testing?

Re: JMock not thread safe - how does one do multi-threaded testing?

by Nat Pryce :: Rate this Message:

Reply to Author | View in Thread

A quick heads-up. The API has changed. It's now more convenient if you  
use multiple threads and mock concrete classes. However, you'll need  
to change your code if you're using a build from Svn.

--Nat

www.natpryce.com

On 1 Apr 2009, at 23:14, James Abley <james.abley@...> wrote:

> 2009/4/1 James Abley <james.abley@...>:
>> 2009/4/1 Nat Pryce <nat.pryce@...>:
>>> JMock is thread-safe if only one thread enters the Mockery at a  
>>> time.
>>
>> That's an interesting definition of thread-safe - only safe when one
>> thread uses it. :-D
>>
>>> (E.g. it doesn't use thread-local variables or any magic like that).
>>> So, if your code is meant to guarantee that only one thread calls a
>>> mock object at a time, but you're seeing the Mockery fail because of
>>> race-conditions, then your code is not thread safe.
>>>
>>> If you want multiple threads to enter the Mockery at once, you  
>>> should
>>> use the SynchronisingImposteriser that is currently in Subversion  
>>> head
>>> but will be in the upcoming 2.6.0 release.
>>>
>>> --Nat
>>
>> I guess that's what I'm seeing. I'll write some tests against
>> subversion trunk HEAD and see if I can get it working with the  
>> current
>> code, or maybe come back to you with a possible new feature request.
>>
>> Thanks for the fast responses; this isn't blocking me at all; it's a
>> minor annoyance that I'd like to scratch.
>>
>> Cheers,
>>
>> James
>
> Works perfectly, plus gives me nice failures if I try to use the
> non-thread-safe version. Lovely job.
>
> package org.jmock.test.unit.lib.concurrent;
>
> import static org.junit.Assert.*;
>
> import java.util.ArrayList;
> import java.util.List;
> import java.util.concurrent.Callable;
> import java.util.concurrent.ConcurrentHashMap;
> import java.util.concurrent.ConcurrentMap;
> import java.util.concurrent.CountDownLatch;
> import java.util.concurrent.ExecutionException;
> import java.util.concurrent.ExecutorService;
> import java.util.concurrent.Executors;
> import java.util.concurrent.Future;
> import java.util.concurrent.FutureTask;
> import java.util.concurrent.atomic.AtomicInteger;
>
> import org.jmock.Expectations;
> import org.jmock.Mockery;
> import org.jmock.integration.junit4.JMock;
> import org.jmock.integration.junit4.JUnit4Mockery;
> import org.jmock.lib.concurrent.SynchronisingImposteriser;
> import org.junit.Test;
> import org.junit.runner.RunWith;
>
> @RunWith(JMock.class)
> public class ConcurrentRequestsTest {
>
>    /**
>     * JMock context object.
>     */
>    private final Mockery context = new JUnit4Mockery() {
>        {
>            /* Need to use the SynchronisingImposteriser otherwise it
> will fail when use in a multi-threaded situation. */
>            setImposteriser(new SynchronisingImposteriser());
>        }
>    };
>
>    @Test
>    public void onlyASingleClientPopulatesTheCache() throws Exception {
>        final Cache cache = new Cache();
>
>        int clientRequests = 100;
>
>        final Integer key = Integer.valueOf(43);
>
>        ExecutorService exec =  
> Executors.newFixedThreadPool(clientRequests);
>
>        final CountDownLatch startGate = new CountDownLatch(1);
>        final CountDownLatch endGate = new  
> CountDownLatch(clientRequests);
>
>        List<Future<Integer>> results = new  
> ArrayList<Future<Integer>>();
>
>        for (int i = 0; i < clientRequests; ++i) {
>            final Request request = context.mock(Request.class,  
> "request-" + i);
>
>            context.checking(new Expectations() {
>                {
>                    allowing(request).getKey();
>                    will(returnValue(key));
>
>                    allowing(request).getCallable();
>                    will(returnValue(new Callable<Integer>() {
>
>                        public Integer call() throws Exception {
>
>                            /*
>                             * Simulate an operation taking a little
> while to complete.
>                             */
>                            Thread.sleep(10);
>                            return request.getKey();
>                        }
>                    }));
>                }
>            });
>
>            FutureTask<Integer> requestThreadCall = new
> FutureTask<Integer>(new Callable<Integer>() {
>
>                public Integer call() throws Exception {
>                    startGate.await();
>                    try {
>                        return cache.retrieveFromCache(request);
>                    } finally {
>                        endGate.countDown();
>                    }
>                }
>            });
>
>            results.add(requestThreadCall);
>            exec.execute(requestThreadCall);
>        }
>
>        startGate.countDown();
>        endGate.await();
>
>        for (Future<Integer> result : results) {
>            assertEquals(key, result.get());
>        }
>
>        assertEquals("Only a single cache miss", 1,  
> cache.missCount.intValue());
>        assertEquals("everything else was a cache hit or waited for
> task completion", clientRequests - 1,
>                cache.hitCount.intValue() +  
> cache.waitingCount.intValue());
>    }
>
>    static interface Request {
>
>        Integer getKey();
>
>        Callable<Integer> getCallable();
>    }
>
>    static class Cache {
>
>        private final ConcurrentMap<Integer, Future<Integer>> cache;
>        private final AtomicInteger hitCount;
>        private final AtomicInteger missCount;
>        private final AtomicInteger waitingCount;
>
>        Cache() {
>            this.cache = new ConcurrentHashMap<Integer,  
> Future<Integer>>();
>            this.hitCount = new AtomicInteger();
>            this.missCount = new AtomicInteger();
>            this.waitingCount = new AtomicInteger();
>        }
>
>        Integer retrieveFromCache(final Request request) throws
> InterruptedException, ExecutionException {
>            Future<Integer> value = cache.get(request.getKey());
>
>            if (value == null) {
>                FutureTask<Integer> thunk = new
> FutureTask<Integer>(request.getCallable());
>
>                value = cache.putIfAbsent(request.getKey(), thunk);
>
>                if (value == null) {
>                    cacheMiss();
>                    thunk.run();
>                    value = thunk;
>                } else {
>                    cacheHitOrWaitingForCompletion();
>                }
>            } else {
>                cacheHit();
>            }
>
>            return value.get();
>        }
>
>        private void cacheHit() {
>            this.hitCount.incrementAndGet();
>        }
>
>        private void cacheHitOrWaitingForCompletion() {
>            this.waitingCount.incrementAndGet();
>        }
>
>        private void cacheMiss() {
>            this.missCount.incrementAndGet();
>        }
>    }
> }
>
> Every time I think I've found a missing feature, I should just check
> what's in Subversion since you've always already implemented it!
>
> Thanks,
>
> James
>
> ---------------------------------------------------------------------
> To unsubscribe from this list, please visit:
>
>    http://xircles.codehaus.org/manage_email
>
>

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


 « Return to Thread: JMock not thread safe - how does one do multi-threaded testing?