diff --git a/junit-commons/src/main/java/org/junit/gen5/commons/util/FunctionUtils.java b/junit-commons/src/main/java/org/junit/gen5/commons/util/FunctionUtils.java new file mode 100644 index 00000000000..0274aae0b79 --- /dev/null +++ b/junit-commons/src/main/java/org/junit/gen5/commons/util/FunctionUtils.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2016 the original author or authors. + * + * 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 + */ + +package org.junit.gen5.commons.util; + +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Collection of utilities for working with {@link Function Functions}, + * {@link Predicate Predicates}, etc. + * + * @since 5.0 + */ +public class FunctionUtils { + + private FunctionUtils() { + /* no-op */ + } + + /** + * Returns a predicate that first applies the specified function and then + * tests the specified predicate against the result of the function. + * + * @param function the function to apply + * @param predicate the predicate to test against the result of the function + */ + public static Predicate where(Function function, Predicate predicate) { + Preconditions.notNull(function, "function must not be null"); + Preconditions.notNull(predicate, "predicate must not be null"); + return input -> predicate.test(function.apply(input)); + } + +} diff --git a/junit-tests/src/test/java/org/junit/gen5/commons/util/FunctionUtilsTests.java b/junit-tests/src/test/java/org/junit/gen5/commons/util/FunctionUtilsTests.java new file mode 100644 index 00000000000..5d976514583 --- /dev/null +++ b/junit-tests/src/test/java/org/junit/gen5/commons/util/FunctionUtilsTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2016 the original author or authors. + * + * 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 + */ + +package org.junit.gen5.commons.util; + +import static java.util.function.Predicate.isEqual; +import static org.junit.gen5.api.Assertions.*; + +import java.util.function.Predicate; + +import org.junit.gen5.api.Test; + +class FunctionUtilsTests { + + @Test + void whereWithNullFunction() { + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> { + FunctionUtils.where(null, o -> true); + }); + assertEquals("function must not be null", exception.getMessage()); + } + + @Test + void whereWithNullPredicate() { + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> { + FunctionUtils.where(o -> o, null); + }); + assertEquals("predicate must not be null", exception.getMessage()); + } + + @Test + void whereWithChecksPredicateAgainstResultOfFunction() { + Predicate combinedPredicate = FunctionUtils.where(String::length, isEqual(3)); + assertFalse(combinedPredicate.test("fo")); + assertTrue(combinedPredicate.test("foo")); + assertFalse(combinedPredicate.test("fooo")); + } +} diff --git a/junit-tests/src/test/java/org/junit/gen5/engine/ExecutionEvent.java b/junit-tests/src/test/java/org/junit/gen5/engine/ExecutionEvent.java new file mode 100644 index 00000000000..a56c6e95970 --- /dev/null +++ b/junit-tests/src/test/java/org/junit/gen5/engine/ExecutionEvent.java @@ -0,0 +1,95 @@ +/* + * Copyright 2015-2016 the original author or authors. + * + * 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 + */ + +package org.junit.gen5.engine; + +import static java.util.function.Predicate.isEqual; +import static org.junit.gen5.commons.util.FunctionUtils.where; +import static org.junit.gen5.engine.ExecutionEvent.Type.*; + +import java.util.Optional; +import java.util.function.Predicate; + +import org.junit.gen5.commons.util.ToStringBuilder; + +/** + * Represents an event collected by + * {@link ExecutionEventRecordingEngineExecutionListener}. + * + * @see ExecutionEventConditions + */ +public class ExecutionEvent { + + public enum Type { + DYNAMIC_TEST_REGISTERED, SKIPPED, STARTED, FINISHED + } + + public static ExecutionEvent dynamicTestRegistered(TestDescriptor testDescriptor) { + return new ExecutionEvent(DYNAMIC_TEST_REGISTERED, testDescriptor, null); + } + + public static ExecutionEvent executionSkipped(TestDescriptor testDescriptor, String reason) { + return new ExecutionEvent(SKIPPED, testDescriptor, reason); + } + + public static ExecutionEvent executionStarted(TestDescriptor testDescriptor) { + return new ExecutionEvent(STARTED, testDescriptor, null); + } + + public static ExecutionEvent executionFinished(TestDescriptor testDescriptor, TestExecutionResult result) { + return new ExecutionEvent(FINISHED, testDescriptor, result); + } + + public static Predicate byType(ExecutionEvent.Type type) { + return where(ExecutionEvent::getType, isEqual(type)); + } + + public static Predicate byTestDescriptor(Predicate predicate) { + return where(ExecutionEvent::getTestDescriptor, predicate); + } + + public static Predicate byPayload(Class payloadClass, Predicate predicate) { + return event -> event.getPayload(payloadClass).filter(predicate).isPresent(); + } + + private final ExecutionEvent.Type type; + private final TestDescriptor testDescriptor; + private final Object payload; + + private ExecutionEvent(ExecutionEvent.Type type, TestDescriptor testDescriptor, Object payload) { + this.type = type; + this.testDescriptor = testDescriptor; + this.payload = payload; + } + + public ExecutionEvent.Type getType() { + return type; + } + + public TestDescriptor getTestDescriptor() { + return testDescriptor; + } + + public Optional getPayload(Class payloadClass) { + return Optional.ofNullable(payload).filter(payloadClass::isInstance).map(payloadClass::cast); + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("type", type) + .append("testDescriptor", testDescriptor) + .append("payload", payload) + .toString(); + // @formatter:on + } + +} diff --git a/junit-tests/src/test/java/org/junit/gen5/engine/ExecutionEventConditions.java b/junit-tests/src/test/java/org/junit/gen5/engine/ExecutionEventConditions.java new file mode 100644 index 00000000000..99df14b3a8e --- /dev/null +++ b/junit-tests/src/test/java/org/junit/gen5/engine/ExecutionEventConditions.java @@ -0,0 +1,60 @@ +/* + * Copyright 2015-2016 the original author or authors. + * + * 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 + */ + +package org.junit.gen5.engine; + +import static org.assertj.core.api.Assertions.allOf; +import static org.junit.gen5.engine.ExecutionEvent.*; +import static org.junit.gen5.engine.ExecutionEvent.Type.*; +import static org.junit.gen5.engine.TestExecutionResult.Status.*; +import static org.junit.gen5.engine.TestExecutionResultConditions.status; + +import org.assertj.core.api.Condition; +import org.junit.gen5.engine.ExecutionEvent.Type; + +/** + * Collection of AssertJ conditions for {@link ExecutionEvent}. + */ +public class ExecutionEventConditions { + + public static Condition test() { + return new Condition<>(byTestDescriptor(TestDescriptor::isTest), "is a test"); + } + + public static Condition container() { + return new Condition<>(byTestDescriptor(TestDescriptor::isContainer), "is a container"); + } + + public static Condition started() { + return type(STARTED); + } + + public static Condition finishedWithFailure(Condition causeMessage) { + return finished(allOf(status(FAILED), causeMessage)); + } + + public static Condition finishedSuccessfully() { + return finished(status(SUCCESSFUL)); + } + + public static Condition finished(Condition resultCondition) { + return allOf(type(FINISHED), result(resultCondition)); + } + + public static Condition type(Type expectedType) { + return new Condition<>(byType(expectedType), "type is %s", expectedType); + } + + public static Condition result(Condition condition) { + return new Condition<>(byPayload(TestExecutionResult.class, condition::matches), "event with result where %s", + condition); + } + +} diff --git a/junit-tests/src/test/java/org/junit/gen5/engine/ExecutionEventRecordingEngineExecutionListener.java b/junit-tests/src/test/java/org/junit/gen5/engine/ExecutionEventRecordingEngineExecutionListener.java new file mode 100644 index 00000000000..106e87251e3 --- /dev/null +++ b/junit-tests/src/test/java/org/junit/gen5/engine/ExecutionEventRecordingEngineExecutionListener.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2016 the original author or authors. + * + * 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 + */ + +package org.junit.gen5.engine; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * {@link EngineExecutionListener} that records all events and makes them + * available to tests. + * + * @see ExecutionEvent + */ +public class ExecutionEventRecordingEngineExecutionListener implements EngineExecutionListener { + + public final List executionEvents = new CopyOnWriteArrayList<>(); + + @Override + public void dynamicTestRegistered(TestDescriptor testDescriptor) { + addEvent(ExecutionEvent.dynamicTestRegistered(testDescriptor)); + } + + @Override + public void executionSkipped(TestDescriptor testDescriptor, String reason) { + addEvent(ExecutionEvent.executionSkipped(testDescriptor, reason)); + } + + @Override + public void executionStarted(TestDescriptor testDescriptor) { + addEvent(ExecutionEvent.executionStarted(testDescriptor)); + } + + @Override + public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult result) { + addEvent(ExecutionEvent.executionFinished(testDescriptor, result)); + } + + public List getExecutionEvents() { + return executionEvents; + } + + private void addEvent(ExecutionEvent dynamicTestRegistered) { + executionEvents.add(dynamicTestRegistered); + } + +} diff --git a/junit-tests/src/test/java/org/junit/gen5/engine/TestExecutionResultConditions.java b/junit-tests/src/test/java/org/junit/gen5/engine/TestExecutionResultConditions.java new file mode 100644 index 00000000000..9c6f320ec49 --- /dev/null +++ b/junit-tests/src/test/java/org/junit/gen5/engine/TestExecutionResultConditions.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2016 the original author or authors. + * + * 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 + */ + +package org.junit.gen5.engine; + +import static java.util.function.Predicate.isEqual; +import static org.junit.gen5.commons.util.FunctionUtils.where; + +import org.assertj.core.api.Condition; +import org.junit.gen5.engine.TestExecutionResult.Status; + +/** + * Collection of AssertJ conditions for {@link TestExecutionResult}. + */ +public class TestExecutionResultConditions { + + public static Condition status(Status expectedStatus) { + return new Condition<>(where(TestExecutionResult::getStatus, isEqual(expectedStatus)), "status is %s", + expectedStatus); + } + + public static Condition causeMessage(String expectedMessage) { + return new Condition(where(TestExecutionResult::getThrowable, throwable -> { + return throwable.isPresent() && expectedMessage.equals(throwable.get().getMessage()); + }), "message of cause is %s", expectedMessage); + } + +} diff --git a/junit-tests/src/test/java/org/junit/gen5/engine/junit4/JUnit4TestEngineClassExecutionTests.java b/junit-tests/src/test/java/org/junit/gen5/engine/junit4/JUnit4TestEngineClassExecutionTests.java index 07e0aeedcf3..02587081e49 100644 --- a/junit-tests/src/test/java/org/junit/gen5/engine/junit4/JUnit4TestEngineClassExecutionTests.java +++ b/junit-tests/src/test/java/org/junit/gen5/engine/junit4/JUnit4TestEngineClassExecutionTests.java @@ -10,25 +10,34 @@ package org.junit.gen5.engine.junit4; -import static org.junit.gen5.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.data.Index.atIndex; +import static org.junit.gen5.engine.ExecutionEventConditions.*; +import static org.junit.gen5.engine.TestExecutionResultConditions.causeMessage; import static org.junit.gen5.engine.TestPlanSpecification.*; import org.junit.gen5.api.Test; import org.junit.gen5.engine.EngineAwareTestDescriptor; +import org.junit.gen5.engine.ExecutionEventRecordingEngineExecutionListener; import org.junit.gen5.engine.ExecutionRequest; -import org.junit.gen5.engine.TrackingEngineExecutionListener; import org.junit.gen5.engine.junit4.samples.PlainJUnit4TestCaseWithSingleTestWhichFails; class JUnit4TestEngineClassExecutionTests { - TrackingEngineExecutionListener listener = new TrackingEngineExecutionListener(); + ExecutionEventRecordingEngineExecutionListener listener = new ExecutionEventRecordingEngineExecutionListener(); @Test void executesPlainJUnit4TestCaseWithSingleTestWhichFails() { execute(PlainJUnit4TestCaseWithSingleTestWhichFails.class); - assertEquals(1, listener.testStartedCount.get()); - assertEquals(1, listener.testFailedCount.get()); + // @formatter:off + assertThat(listener.getExecutionEvents()) + .hasSize(4) + .has(allOf(container(), started()), atIndex(0)) + .has(allOf(test(), started()), atIndex(1)) + .has(allOf(test(), finishedWithFailure(causeMessage("this test should fail"))), atIndex(2)) + .has(allOf(container(), finishedSuccessfully()), atIndex(3)); + // @formatter:on } private void execute(Class testClass) { diff --git a/junit4-engine/src/main/java/org/junit/gen5/engine/junit4/execution/RunListenerAdapter.java b/junit4-engine/src/main/java/org/junit/gen5/engine/junit4/execution/RunListenerAdapter.java index 3e7b7f9b3bd..9e09ab41e29 100644 --- a/junit4-engine/src/main/java/org/junit/gen5/engine/junit4/execution/RunListenerAdapter.java +++ b/junit4-engine/src/main/java/org/junit/gen5/engine/junit4/execution/RunListenerAdapter.java @@ -12,7 +12,7 @@ import static java.util.function.Function.identity; import static java.util.stream.Collectors.toMap; -import static org.junit.gen5.engine.TestExecutionResult.failed; +import static org.junit.gen5.engine.TestExecutionResult.*; import java.util.Map; @@ -42,7 +42,9 @@ public RunListenerAdapter(RunnerTestDescriptor runnerTestDescriptor, EngineExecu @Override public void testStarted(Description description) throws Exception { - listener.executionStarted(lookupDescriptor(description)); + TestDescriptor testDescriptor = lookupDescriptor(description); + listener.executionStarted(testDescriptor.getParent().get()); + listener.executionStarted(testDescriptor); } @Override @@ -50,6 +52,7 @@ public void testFailure(Failure failure) throws Exception { TestDescriptor testDescriptor = lookupDescriptor(failure.getDescription()); TestExecutionResult result = failed(failure.getException()); listener.executionFinished(testDescriptor, result); + listener.executionFinished(testDescriptor.getParent().get(), successful()); } private TestDescriptor lookupDescriptor(Description description) {