/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.commons.test.mockito.answer;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Answer class that helps to lock execution of test in separate threads for testing purposes.
* <p/>
* It can hold waiting thread until this answer is called.<br/>
* It can hold waiting thread that uses mock until answering to mock call is allowed.
*
* Here is an example of complex test that ensures that (for example) locked area is not called
* when other thread try to access it.
* <pre class="code"><code class="java">
* // given
* WaitingAnswer<Void> waitingAnswer = new WaitingAnswer<>();
* doAnswer(waitingAnswer).when(someClassUsedInTestedClass).someMethod(eq(param1), eq(param2));
*
* // start doing something in a separate thread
* executor.execute(() -> testedClass.doSomething());
* // wait until separate thread call answer to find the moment when critical area is occupied
* // to make test fast wait not more that provided timeout
* waitingAnswer.waitAnswerCall(1, TimeUnit.SECONDS);
*
* // when
* try {
* // start doing something in current thread
* testedClass.doSomething()
* // this area should not be reachable until answer is completed!
* fail("Error message");
* } finally {
* // In this case exception can be suppressed
* // Test is simplified to provide clean example
*
* // then
* // complete waiting answer
* waitingAnswer.completeAnswer();
* // ensure that someMethod is called only once to confirm that testedClass.doSomething()
* // doesn't call it this particular test
* verify(someClassUsedInTestedClass, timeout(100).times(1)).someMethod(any(), any());
* }
* </code></pre>
*
* @author Alexander Garagatyi
*/
public class WaitingAnswer<T> implements Answer<T> {
private final CountDownLatch answerIsCalledLatch;
private final CountDownLatch answerResultIsUnlockedLatch;
private long maxWaitingTime;
private TimeUnit maxWaitingUnit;
private T result;
private volatile String error;
public WaitingAnswer() {
this.maxWaitingTime = 1;
this.maxWaitingUnit = TimeUnit.SECONDS;
this.result = null;
this.answerIsCalledLatch = new CountDownLatch(1);
this.answerResultIsUnlockedLatch = new CountDownLatch(1);
this.error = null;
}
public WaitingAnswer(long maxWaitingTime, TimeUnit maxWaitingUnit) {
this();
this.maxWaitingTime = maxWaitingTime;
this.maxWaitingUnit = maxWaitingUnit;
}
public WaitingAnswer(T result) {
this();
this.result = result;
}
public WaitingAnswer(T result,
long maxWaitingTime,
TimeUnit maxWaitingUnit) {
this();
this.result = result;
this.maxWaitingTime = maxWaitingTime;
this.maxWaitingUnit = maxWaitingUnit;
}
/**
* Waits until answer is called in method {@link #answer(InvocationOnMock)}.
*
* @param maxWaitingTime
* max time to wait
* @param maxWaitingUnit
* time unit of the max waiting time argument
* @throws Exception
* if the waiting time elapsed before this answer is called
* @see #answer(InvocationOnMock)
*/
public void waitAnswerCall(long maxWaitingTime, TimeUnit maxWaitingUnit) throws Exception {
if (!answerIsCalledLatch.await(maxWaitingTime, maxWaitingUnit)) {
error = "Waiting time elapsed but answer is not called";
throw new Exception(error);
}
}
/**
* Stops process of waiting returning result of answer in method {@link #answer(InvocationOnMock)}.
*
* @throws Exception
* if this answer waiting time elapsed before this method is called
* @see #answer(InvocationOnMock)
*/
public void completeAnswer() throws Exception {
answerResultIsUnlockedLatch.countDown();
if (error != null) {
throw new Exception(error);
}
}
/**
* Stops waiting until answer is called in method {@link #waitAnswerCall(long, TimeUnit)} and
* then waits until method {@link #completeAnswer()} is called.
*
* @param invocationOnMock
* see {@link Answer#answer(InvocationOnMock)}
* @return returns answer result if provided in constructor or null otherwise
* @throws Exception
* if answer call or answer result waiting time is elapsed
* @throws Throwable
* in the same cases as in {@link Answer#answer(InvocationOnMock)}
* @see #waitAnswerCall(long, TimeUnit)
* @see #completeAnswer()
* @see Answer#answer(InvocationOnMock)
*/
@Override
public T answer(InvocationOnMock invocationOnMock) throws Throwable {
// report start of answer call
answerIsCalledLatch.countDown();
if (error != null) {
throw new Exception(error);
}
// wait until another thread unlocks returning of answer
if (!answerResultIsUnlockedLatch.await(maxWaitingTime, maxWaitingUnit)) {
error = "Waiting time elapsed but completeAnswer is not called";
throw new Exception(error);
}
return result;
}
}