2

Context: I'm writing unit test for a gRPC service. I want to verify that the method of the mock on the server side is called. I'm using easy mock. To be sure we get the response of gRPC (whatever it is) I need to suspend the thread before easy mock verify the calls.

So I tried something like this using LockSupport:

  @Test
  public void alphaMethodTest() throws Exception
  {
     Dummy dummy = createNiceMock(Dummy.class);

     dummy.alphaMethod(anyBoolean());
     expectLastCall().once();

     EasyMock.replay(dummy);

     DummyServiceGrpcImpl dummyServiceGrpc = new DummyServiceGrpcImpl();
     bcreuServiceGrpc.setDummy(dummy);


     DummyServiceGrpc.DummyServiceStub stub = setupDummyServiceStub();

     Thread thread = Thread.currentThread();

     stub.alphaMethod(emptyRequest, new StreamObserver<X>(){
         @Override
         public void onNext(X value) {
            LockSupport.unpark(thread);
         }
     }

     Instant expirationTime = Instant.now().plus(pDuration);
     LockSupport.parkUntil(expirationTime.toEpochMilli());

     verify(dummy);
}

But I have many tests like this one (around 40) and I suspect threading issue. I usually get one or two failing the verify step, sometime all of them pass. I try to use a ReentrantLock with Condition instead. But again some are failing (IllegalMonitorStateException on the signalAll):

  @Test
  public void alphaMethodTest() throws Exception
  {
     Dummy dummy = createNiceMock(Dummy.class);

     dummy.alphaMethod(anyBoolean());
     expectLastCall().once();

     EasyMock.replay(dummy);

     DummyServiceGrpcImpl dummyServiceGrpc = new DummyServiceGrpcImpl();
     bcreuServiceGrpc.setDummy(dummy);


     DummyServiceGrpc.DummyServiceStub stub = setupDummyServiceStub();


     ReentrantLock lock = new ReentrantLock();

     Condition conditionPromiseTerminated = lock.newCondition();

     stub.alphaMethod(emptyRequest, new StreamObserver<X>(){
         @Override
         public void onNext(X value) {
            conditionPromiseTerminated.signalAll();
         }
     }

     Instant expirationTime = Instant.now().plus(pDuration);
     conditionPromiseTerminated.awaitUntil(new Date(expirationTime.toEpochMilli()));

     verify(dummy);
}

I'm sorry not providing runnable example for you, my current code is using a private API :/.

Do you think LockSupport may cause trouble because of the multiple tests running? Am I missing something using lock support or reentrant lock. Do you think of any other class of the concurrent API that would suit better my needs?

B. Bri
  • 546
  • 2
  • 7
  • 23

1 Answers1

1

LockSupport is a bit dangerous, you will need to read the documentation closely and find out that:

The call spuriously (that is, for no reason) returns.

So when you think your code will do some "waiting", it might simply return immediately. The simplest reason for that would be this for example, but there could be other reasons too.

When using ReentrantLock, all of them should fail with IllegalMonitorStateException, because you never acquire the lock via ReentrantLock::lock. And stop using new Date(...), it is deprecated for a reason.


I think you are over-complicating things, you could do the same signaling with a plain lock, a simplified example:

public static void main(String[] args) {

    Object lock = new Object();

    Thread first = new Thread(() -> {
        synchronized (lock) {
            System.out.println("Locked");
            try {
                System.out.println("Sleeping");
                lock.wait();
                System.out.println("Waked up");
            } catch (InterruptedException e) {
                // these are your tests, no one should interrupt
                // unless it's yourself
                throw new RuntimeException(e);
            }
        }
    });

    first.start();
    sleepOneSecond();

    Thread second = new Thread(() -> {
        synchronized (lock) {
            System.out.println("notifying waiting threads");
            lock.notify();
        }
    });

    second.start();

}

private static void sleepOneSecond() {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

Notice the output:

Locked
Sleeping
notifying waiting threads
Waked up

It should be obvious how the "communication" (signaling) between threads happens.

Eugene
  • 117,005
  • 15
  • 201
  • 306