Skip to content

Commit

Permalink
Make ThrowableCollector configurable
Browse files Browse the repository at this point in the history
This commit generalizes `ThrowableCollector` to take a predicate that
is used to decide whether a `Throwable` is aborted or failed execution.
The Jupiter engines uses a specialized implementation that treats OTA's
`TestAbortedExceptions` as aborting and everything else as failing:
`OpenTest4JAwareThrowableCollector`.

In addition, this commit introduces `ThrowableCollector.Factory` and
lets `HierarchicalTestEngines` create them in order to allow the engine
to decide how to configure its `ThrowableCollectors`. For backwards
compatibility, the default implementation returns a factory that
always creates instances of `OpenTest4JAwareThrowableCollector`.

Issue: #1313
  • Loading branch information
marcphilipp committed Jul 7, 2018
1 parent c1d682a commit 137f831
Show file tree
Hide file tree
Showing 12 changed files with 155 additions and 47 deletions.
Expand Up @@ -56,6 +56,7 @@
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.descriptor.ClassSource;
import org.junit.platform.engine.support.hierarchical.ExclusiveResource;
import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector;
import org.junit.platform.engine.support.hierarchical.ThrowableCollector;

/**
Expand Down Expand Up @@ -153,7 +154,7 @@ public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext conte
registerAfterEachMethodAdapters(registry);

Lifecycle lifecycle = getTestInstanceLifecycle(this.testClass, context.getConfigurationParameters());
ThrowableCollector throwableCollector = new ThrowableCollector();
ThrowableCollector throwableCollector = new OpenTest4JAwareThrowableCollector();
ClassExtensionContext extensionContext = new ClassExtensionContext(context.getExtensionContext(),
context.getExecutionListener(), this, lifecycle, context.getConfigurationParameters(), throwableCollector);

Expand Down Expand Up @@ -201,7 +202,8 @@ public JupiterEngineExecutionContext before(JupiterEngineExecutionContext contex
@Override
public void after(JupiterEngineExecutionContext context) {

Throwable previousThrowable = context.getThrowableCollector().getThrowable();
ThrowableCollector throwableCollector = context.getThrowableCollector();
Throwable previousThrowable = throwableCollector.getThrowable();

if (context.beforeAllMethodsExecuted()) {
invokeAfterAllMethods(context);
Expand All @@ -216,7 +218,9 @@ public void after(JupiterEngineExecutionContext context) {
// the execution of this Node. If an exception was already thrown, any
// later exceptions were added as suppressed exceptions to that original
// exception unless a more severe exception occurred in the meantime.
context.getThrowableCollector().assertNotSame(previousThrowable);
if (previousThrowable != throwableCollector.getThrowable()) {
throwableCollector.assertEmpty();
}
}

private TestInstanceFactory resolveTestInstanceFactory(ExtensionRegistry registry) {
Expand Down
Expand Up @@ -34,6 +34,7 @@
import org.junit.platform.commons.util.ExceptionUtils;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector;
import org.junit.platform.engine.support.hierarchical.ThrowableCollector;
import org.junit.platform.engine.support.hierarchical.ThrowableCollector.Executable;

Expand Down Expand Up @@ -80,7 +81,7 @@ public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext conte
ExtensionRegistry registry = populateNewExtensionRegistry(context);
Object testInstance = context.getTestInstanceProvider().getTestInstance(Optional.of(registry));

ThrowableCollector throwableCollector = new ThrowableCollector();
ThrowableCollector throwableCollector = new OpenTest4JAwareThrowableCollector();
ExtensionContext extensionContext = new MethodExtensionContext(context.getExtensionContext(),
context.getExecutionListener(), this, context.getConfigurationParameters(), testInstance,
throwableCollector);
Expand Down
Expand Up @@ -26,6 +26,7 @@
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContextException;
import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector;
import org.junit.platform.engine.support.hierarchical.ThrowableCollector;

/**
Expand All @@ -51,7 +52,7 @@ public ExtensionValuesStore(ExtensionValuesStore parentStore) {
* does not close values in parent stores.
*/
public void closeAllStoredCloseableValues() {
ThrowableCollector throwableCollector = new ThrowableCollector();
ThrowableCollector throwableCollector = new OpenTest4JAwareThrowableCollector();
for (Supplier<Object> supplier : storedValues.values()) {
Object value = supplier.get();
if (value instanceof ExtensionContext.Store.CloseableResource) {
Expand Down
Expand Up @@ -36,7 +36,7 @@
import org.junit.platform.engine.support.descriptor.FileSource;
import org.junit.platform.engine.support.descriptor.UriSource;
import org.junit.platform.engine.support.hierarchical.Node;
import org.junit.platform.engine.support.hierarchical.ThrowableCollector;
import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector;

/**
* Unit tests for {@link TestFactoryTestDescriptor}.
Expand Down Expand Up @@ -121,7 +121,7 @@ void before() throws Exception {
isClosed = false;

context = new JupiterEngineExecutionContext(null, null).extend().withThrowableCollector(
new ThrowableCollector()).withExtensionContext(extensionContext).build();
new OpenTest4JAwareThrowableCollector()).withExtensionContext(extensionContext).build();

Method testMethod = CustomStreamTestCase.class.getDeclaredMethod("customStream");
descriptor = new TestFactoryTestDescriptor(UniqueId.forEngine("engine"), CustomStreamTestCase.class,
Expand Down
Expand Up @@ -43,7 +43,7 @@
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.reporting.ReportEntry;
import org.junit.platform.engine.support.hierarchical.ThrowableCollector;
import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;

Expand Down Expand Up @@ -131,7 +131,7 @@ void tagsCanBeRetrievedInExtensionContext() {
assertThat(nestedExtensionContext.getRoot()).isSameAs(outerExtensionContext);

MethodExtensionContext methodExtensionContext = new MethodExtensionContext(outerExtensionContext, null,
methodTestDescriptor, configParams, new OuterClass(), new ThrowableCollector());
methodTestDescriptor, configParams, new OuterClass(), new OpenTest4JAwareThrowableCollector());
assertThat(methodExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "method-tag");
assertThat(methodExtensionContext.getRoot()).isSameAs(outerExtensionContext);
}
Expand All @@ -152,7 +152,7 @@ void fromMethodTestDescriptor() {
ClassExtensionContext classExtensionContext = new ClassExtensionContext(engineExtensionContext, null,
classTestDescriptor, configParams, null);
MethodExtensionContext methodExtensionContext = new MethodExtensionContext(classExtensionContext, null,
methodTestDescriptor, configParams, testInstance, new ThrowableCollector());
methodTestDescriptor, configParams, testInstance, new OpenTest4JAwareThrowableCollector());

// @formatter:off
assertAll("methodContext",
Expand Down Expand Up @@ -205,7 +205,7 @@ void usingStore() {
ClassTestDescriptor classTestDescriptor = outerClassDescriptor(methodTestDescriptor);
ExtensionContext parentContext = new ClassExtensionContext(null, null, classTestDescriptor, configParams, null);
MethodExtensionContext childContext = new MethodExtensionContext(parentContext, null, methodTestDescriptor,
configParams, new OuterClass(), new ThrowableCollector());
configParams, new OuterClass(), new OpenTest4JAwareThrowableCollector());

ExtensionContext.Store childStore = childContext.getStore(Namespace.GLOBAL);
ExtensionContext.Store parentStore = parentContext.getStore(Namespace.GLOBAL);
Expand Down
Expand Up @@ -45,7 +45,10 @@ public abstract class HierarchicalTestEngine<C extends EngineExecutionContext> i
@Override
public final void execute(ExecutionRequest request) {
try (HierarchicalTestExecutorService executorService = createExecutorService(request)) {
new HierarchicalTestExecutor<>(request, createExecutionContext(request), executorService).execute().get();
C executionContext = createExecutionContext(request);
ThrowableCollector.Factory throwableCollectorFactory = createThrowableCollectorFactory(request);
new HierarchicalTestExecutor<>(request, executionContext, executorService,
throwableCollectorFactory).execute().get();
}
catch (Exception exception) {
throw new JUnitException("Error executing tests for engine " + getId(), exception);
Expand Down Expand Up @@ -74,6 +77,29 @@ protected HierarchicalTestExecutorService createExecutorService(ExecutionRequest
return new SameThreadHierarchicalTestExecutorService();
}

/**
* Create the {@linkplain ThrowableCollector.Factory factory} for creating
* {@link ThrowableCollector} instances used to handle exceptions that occur
* during execution of this engine's tests.
*
* <p>An engine may use the information in the supplied <em>request</em>
* such as the contained
* {@linkplain ExecutionRequest#getConfigurationParameters() configuration parameters}
* to decide what kind of factory to return or how to configure it.
*
* <p>By default, this method returns a factory that always creates instances of
* {@link OpenTest4JAwareThrowableCollector}.
*
* @param request the request about to be executed
* @see OpenTest4JAwareThrowableCollector
* @see ThrowableCollector
* @since 1.3
*/
@API(status = EXPERIMENTAL, since = "1.3")
protected ThrowableCollector.Factory createThrowableCollectorFactory(ExecutionRequest request) {
return OpenTest4JAwareThrowableCollector::new;
}

/**
* Create the initial execution context for executing the supplied
* {@linkplain ExecutionRequest request}.
Expand Down
Expand Up @@ -36,11 +36,14 @@ class HierarchicalTestExecutor<C extends EngineExecutionContext> {
private final ExecutionRequest request;
private final C rootContext;
private final HierarchicalTestExecutorService executorService;
private final ThrowableCollector.Factory throwableCollectorFactory;

HierarchicalTestExecutor(ExecutionRequest request, C rootContext, HierarchicalTestExecutorService executorService) {
HierarchicalTestExecutor(ExecutionRequest request, C rootContext, HierarchicalTestExecutorService executorService,
ThrowableCollector.Factory throwableCollectorFactory) {
this.request = request;
this.rootContext = rootContext;
this.executorService = executorService;
this.throwableCollectorFactory = throwableCollectorFactory;
}

Future<Void> execute() {
Expand All @@ -52,7 +55,8 @@ Future<Void> execute() {
NodeTestTask<C> prepareNodeTestTaskTree() {
TestDescriptor rootTestDescriptor = this.request.getRootTestDescriptor();
EngineExecutionListener executionListener = this.request.getEngineExecutionListener();
NodeTestTask<C> rootTestTask = new NodeTestTask<>(rootTestDescriptor, executionListener, this.executorService);
NodeTestTask<C> rootTestTask = new NodeTestTask<>(rootTestDescriptor, executionListener, this.executorService,
this.throwableCollectorFactory);
new NodeTestTaskWalker().walk(rootTestTask);
return rootTestTask;
}
Expand Down
Expand Up @@ -31,10 +31,10 @@
*/
class NodeTestTask<C extends EngineExecutionContext> implements TestTask {

private final ThrowableCollector throwableCollector = new ThrowableCollector();
private final TestDescriptor testDescriptor;
private final EngineExecutionListener listener;
private final HierarchicalTestExecutorService executorService;
private final ThrowableCollector.Factory throwableCollectorFactory;
private final Node<C> node;
private final ExecutionMode executionMode;
private final Set<ExclusiveResource> exclusiveResources;
Expand All @@ -48,18 +48,20 @@ class NodeTestTask<C extends EngineExecutionContext> implements TestTask {

private SkipResult skipResult;
private boolean started;
private ThrowableCollector throwableCollector;

NodeTestTask(TestDescriptor testDescriptor, EngineExecutionListener listener,
HierarchicalTestExecutorService executorService) {
HierarchicalTestExecutorService executorService, ThrowableCollector.Factory throwableCollectorFactory) {
this.testDescriptor = testDescriptor;
this.listener = listener;
this.executorService = executorService;
this.throwableCollectorFactory = throwableCollectorFactory;
node = asNode(testDescriptor);
executionMode = node.getExecutionMode();
exclusiveResources = node.getExclusiveResources();
// @formatter:off
children = testDescriptor.getChildren().stream()
.map(descriptor -> new NodeTestTask<C>(descriptor, listener, executorService))
.map(descriptor -> new NodeTestTask<C>(descriptor, listener, executorService, throwableCollectorFactory))
.collect(toCollection(ArrayList::new));
// @formatter:on
}
Expand Down Expand Up @@ -96,6 +98,7 @@ public void setParentContext(C parentContext) {

@Override
public void execute() {
throwableCollector = throwableCollectorFactory.create();
prepare();
if (throwableCollector.isEmpty()) {
checkWhetherSkipped();
Expand Down Expand Up @@ -142,7 +145,8 @@ private void executeRecursively() {

private void executeDynamicTest(TestDescriptor dynamicTestDescriptor, List<Future<?>> futures) {
listener.dynamicTestRegistered(dynamicTestDescriptor);
NodeTestTask<C> nodeTestTask = new NodeTestTask<>(dynamicTestDescriptor, listener, executorService);
NodeTestTask<C> nodeTestTask = new NodeTestTask<>(dynamicTestDescriptor, listener, executorService,
throwableCollectorFactory);
Set<ExclusiveResource> exclusiveResources = nodeTestTask.getExclusiveResources();
if (!exclusiveResources.isEmpty()) {
listener.executionStarted(dynamicTestDescriptor);
Expand All @@ -169,6 +173,7 @@ private void reportCompletion() {
listener.executionStarted(testDescriptor);
}
listener.executionFinished(testDescriptor, throwableCollector.toTestExecutionResult());
throwableCollector = null;
}

@SuppressWarnings("unchecked")
Expand Down
@@ -0,0 +1,32 @@
/*
* Copyright 2015-2018 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 v2.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.platform.engine.support.hierarchical;

import static org.apiguardian.api.API.Status.MAINTAINED;

import org.apiguardian.api.API;
import org.opentest4j.TestAbortedException;

/**
* Specialization of {@link ThrowableCollector} that treats instances of
* {@link TestAbortedException} as <em>aborting</em>.
*
* @see ThrowableCollector
* @since 5.3
*/
@API(status = MAINTAINED, since = "5.3")
public class OpenTest4JAwareThrowableCollector extends ThrowableCollector {

public OpenTest4JAwareThrowableCollector() {
super(TestAbortedException.class::isInstance);
}

}

0 comments on commit 137f831

Please sign in to comment.