// Copyright © 2011-2014, Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0
package fi.jumi.core.runs;
import fi.jumi.actors.ActorRef;
import fi.jumi.api.drivers.*;
import fi.jumi.core.api.RunId;
import fi.jumi.core.stdout.OutputCapturer;
import org.junit.*;
import org.junit.rules.ExpectedException;
import org.mockito.*;
import java.io.PrintStream;
import java.util.concurrent.*;
import static fi.jumi.core.util.Asserts.catchException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.internal.matchers.ThrowableCauseMatcher.hasCause;
import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage;
import static org.mockito.Mockito.*;
@SuppressWarnings("Convert2MethodRef")
public class ThreadBoundSuiteNotifierTest {
private static final RunId FIRST_RUN_ID = new RunId(RunId.FIRST_ID);
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final RunListener listener = mock(RunListener.class);
private final OutputCapturer outputCapturer = new OutputCapturer();
private final PrintStream stdout = outputCapturer.out();
private Throwable lastError;
private final SuiteNotifier notifier = new ThreadBoundSuiteNotifier(ActorRef.wrap(listener), new RunIdSequence(), outputCapturer);
@Test
public void notifies_about_the_beginning_and_end_of_a_run() {
TestNotifier tn1 = notifier.fireTestStarted(TestId.ROOT);
TestNotifier tn2 = notifier.fireTestStarted(TestId.of(0));
tn2.fireTestFinished();
tn1.fireTestFinished();
InOrder inOrder = inOrder(listener);
inOrder.verify(listener).onRunStarted(FIRST_RUN_ID);
inOrder.verify(listener).onTestStarted(FIRST_RUN_ID, TestId.ROOT);
inOrder.verify(listener).onTestStarted(FIRST_RUN_ID, TestId.of(0));
inOrder.verify(listener).onTestFinished(FIRST_RUN_ID, TestId.of(0));
inOrder.verify(listener).onTestFinished(FIRST_RUN_ID, TestId.ROOT);
inOrder.verify(listener).onRunFinished(FIRST_RUN_ID);
verifyNoMoreInteractions(listener);
}
@Test
public void captures_what_is_printed_during_a_run() {
TestNotifier tn1 = notifier.fireTestStarted(TestId.ROOT);
stdout.print("1");
TestNotifier tn2 = notifier.fireTestStarted(TestId.of(0));
stdout.print("2");
tn2.fireTestFinished();
stdout.print("3");
tn1.fireTestFinished();
verify(listener).onPrintedOut(FIRST_RUN_ID, "1");
verify(listener).onPrintedOut(FIRST_RUN_ID, "2");
verify(listener).onPrintedOut(FIRST_RUN_ID, "3");
}
@Test
public void does_not_capture_what_is_printed_outside_a_run() {
stdout.print("before");
TestNotifier tn1 = notifier.fireTestStarted(TestId.ROOT);
tn1.fireTestFinished();
stdout.print("after");
verify(listener, never()).onPrintedOut(Matchers.any(RunId.class), anyString());
}
@Test
public void forwards_internal_errors_to_the_listener() {
Throwable cause = new Throwable("dummy exception");
notifier.fireInternalError("the message", cause);
verify(listener).onInternalError("the message", cause);
}
// bulletproofing the public API
@Test
public void fireFailure_must_be_called_on_the_current_test() {
TestNotifier tn1 = notifier.fireTestStarted(TestId.ROOT);
TestNotifier tn2 = notifier.fireTestStarted(TestId.of(0));
expectIllegalStateException("must be called on the innermost non-finished TestNotifier; " +
"expected TestNotifier(RunId(1), [TestId(), TestId(0)]) " +
"but was TestNotifier(RunId(1), [TestId()]) which is not innermost", () -> {
tn1.fireFailure(new Exception("dummy"));
});
InOrder inOrder = inOrder(listener);
inOrder.verify(listener).onRunStarted(FIRST_RUN_ID);
inOrder.verify(listener).onTestStarted(FIRST_RUN_ID, TestId.ROOT);
inOrder.verify(listener).onTestStarted(FIRST_RUN_ID, TestId.of(0));
inOrder.verify(listener).onInternalError("Incorrect notifier API usage", lastError);
verifyNoMoreInteractions(listener);
}
@Test
public void fireFailure_cannot_be_called_after_the_test_is_finished() {
TestNotifier tn1 = notifier.fireTestStarted(TestId.ROOT);
TestNotifier tn2 = notifier.fireTestStarted(TestId.of(0));
tn2.fireTestFinished();
expectIllegalStateException("must be called on the innermost non-finished TestNotifier; " +
"expected TestNotifier(RunId(1), [TestId()]) " +
"but was TestNotifier(RunId(1), [TestId(), TestId(0)]) which is finished", () -> {
tn2.fireFailure(new Exception("dummy"));
});
InOrder inOrder = inOrder(listener);
inOrder.verify(listener).onRunStarted(FIRST_RUN_ID);
inOrder.verify(listener).onTestStarted(FIRST_RUN_ID, TestId.ROOT);
inOrder.verify(listener).onTestStarted(FIRST_RUN_ID, TestId.of(0));
inOrder.verify(listener).onTestFinished(FIRST_RUN_ID, TestId.of(0));
inOrder.verify(listener).onInternalError("Incorrect notifier API usage", lastError);
verifyNoMoreInteractions(listener);
}
@Test
public void error_in_fireFailure_will_not_lose_the_original_test_failure_being_reported() {
TestNotifier tn1 = notifier.fireTestStarted(TestId.ROOT);
TestNotifier tn2 = notifier.fireTestStarted(TestId.of(0));
Exception originalTestFailure = new Exception("original test failure");
Exception thrownException = catchException(() -> tn1.fireFailure(originalTestFailure));
assertThat("chained exception", thrownException,
allOf(instanceOf(IllegalStateException.class), // because not called on the innermost TestNotifier
hasCause(equalTo(originalTestFailure))));
}
@Test
public void fireTestFinished_must_be_called_on_the_current_test() {
TestNotifier tn1 = notifier.fireTestStarted(TestId.ROOT);
TestNotifier tn2 = notifier.fireTestStarted(TestId.of(0));
expectIllegalStateException("must be called on the innermost non-finished TestNotifier; " +
"expected TestNotifier(RunId(1), [TestId(), TestId(0)]) " +
"but was TestNotifier(RunId(1), [TestId()]) which is not innermost", () -> {
tn1.fireTestFinished();
});
InOrder inOrder = inOrder(listener);
inOrder.verify(listener).onRunStarted(FIRST_RUN_ID);
inOrder.verify(listener).onTestStarted(FIRST_RUN_ID, TestId.ROOT);
inOrder.verify(listener).onTestStarted(FIRST_RUN_ID, TestId.of(0));
inOrder.verify(listener).onInternalError("Incorrect notifier API usage", lastError);
verifyNoMoreInteractions(listener);
}
@Test
public void fireTestFinished_cannot_be_called_after_the_test_is_finished() {
TestNotifier tn1 = notifier.fireTestStarted(TestId.ROOT);
TestNotifier tn2 = notifier.fireTestStarted(TestId.of(0));
tn2.fireTestFinished();
expectIllegalStateException("must be called on the innermost non-finished TestNotifier; " +
"expected TestNotifier(RunId(1), [TestId()]) " +
"but was TestNotifier(RunId(1), [TestId(), TestId(0)]) which is finished", () -> {
tn2.fireTestFinished();
});
InOrder inOrder = inOrder(listener);
inOrder.verify(listener).onRunStarted(FIRST_RUN_ID);
inOrder.verify(listener).onTestStarted(FIRST_RUN_ID, TestId.ROOT);
inOrder.verify(listener).onTestStarted(FIRST_RUN_ID, TestId.of(0));
inOrder.verify(listener).onTestFinished(FIRST_RUN_ID, TestId.of(0));
inOrder.verify(listener).onInternalError("Incorrect notifier API usage", lastError);
verifyNoMoreInteractions(listener);
}
/**
* Reproduces an issue with the specs2 testing framework's JUnit integration, which starts each test in their own
* threads but sends all test finished events from a common thread.
*/
@Test
public void fireTestFinished_may_be_called_from_a_different_thread_than_in_which_the_test_run_was_started() throws Exception {
TestNotifier tn = inNewThread(() -> notifier.fireTestStarted(TestId.ROOT));
tn.fireTestFinished();
InOrder inOrder = inOrder(listener);
inOrder.verify(listener).onRunStarted(FIRST_RUN_ID);
inOrder.verify(listener).onTestStarted(FIRST_RUN_ID, TestId.ROOT);
inOrder.verify(listener).onTestFinished(FIRST_RUN_ID, TestId.ROOT);
inOrder.verify(listener).onRunFinished(FIRST_RUN_ID);
verifyNoMoreInteractions(listener);
}
/**
* Although unlikely, it's allowed for a TestId invocation to nest invocations of the same TestId. Thus any error
* checking must use the TestNotifier instance and not just check the TestId.
*/
@Test
public void error_checking_is_based_on_TestNotifier_instances_instead_of_TestId_values() {
TestNotifier tn1 = notifier.fireTestStarted(TestId.ROOT);
TestNotifier tn2 = notifier.fireTestStarted(TestId.ROOT);
expectIllegalStateException("must be called on the innermost non-finished TestNotifier; " +
"expected TestNotifier(RunId(1), [TestId(), TestId()]) " +
"but was TestNotifier(RunId(1), [TestId()]) which is not innermost", () -> {
tn1.fireTestFinished();
});
InOrder inOrder = inOrder(listener);
inOrder.verify(listener).onRunStarted(FIRST_RUN_ID);
inOrder.verify(listener, times(2)).onTestStarted(FIRST_RUN_ID, TestId.ROOT);
inOrder.verify(listener).onInternalError("Incorrect notifier API usage", lastError);
verifyNoMoreInteractions(listener);
}
// helpers
private void expectIllegalStateException(String expectedMessage, Runnable command) {
Exception e = catchException(command);
lastError = e;
assertThat(e, allOf(instanceOf(IllegalStateException.class), hasMessage(equalTo(expectedMessage))));
}
private static <T> T inNewThread(Callable<T> callable) throws Exception {
ExecutorService executor = Executors.newCachedThreadPool();
try {
return executor.submit(callable).get();
} finally {
executor.shutdownNow();
}
}
}