From 137f831bba9d0f45085d64fad47947cbc91a30e9 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 4 Jul 2018 21:53:06 +0200 Subject: [PATCH] Make ThrowableCollector configurable 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 --- .../descriptor/ClassTestDescriptor.java | 10 ++- .../descriptor/TestMethodTestDescriptor.java | 3 +- .../execution/ExtensionValuesStore.java | 3 +- .../TestFactoryTestDescriptorTests.java | 4 +- .../execution/ExtensionContextTests.java | 8 +- .../hierarchical/HierarchicalTestEngine.java | 28 +++++- .../HierarchicalTestExecutor.java | 8 +- .../support/hierarchical/NodeTestTask.java | 13 ++- .../OpenTest4JAwareThrowableCollector.java | 32 +++++++ .../hierarchical/ThrowableCollector.java | 87 +++++++++++++------ .../HierarchicalTestExecutorTests.java | 3 +- .../NodeTestTaskWalkerIntegrationTests.java | 3 +- 12 files changed, 155 insertions(+), 47 deletions(-) create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java index 204930538d2..46e835b29c8 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java @@ -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; /** @@ -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); @@ -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); @@ -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) { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java index 0448d98969a..0ee14ece676 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java @@ -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; @@ -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); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionValuesStore.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionValuesStore.java index fc31c44dc7d..a5f662264de 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionValuesStore.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionValuesStore.java @@ -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; /** @@ -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 supplier : storedValues.values()) { Object value = supplier.get(); if (value instanceof ExtensionContext.Store.CloseableResource) { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java index 229b9d56391..b247bab832a 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java @@ -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}. @@ -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, diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextTests.java index 652ab550d7a..44f67ecad4d 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextTests.java @@ -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; @@ -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); } @@ -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", @@ -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); diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java index 3d89c9058ee..f3df4b29d09 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java @@ -45,7 +45,10 @@ public abstract class HierarchicalTestEngine 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); @@ -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. + * + *

An engine may use the information in the supplied request + * such as the contained + * {@linkplain ExecutionRequest#getConfigurationParameters() configuration parameters} + * to decide what kind of factory to return or how to configure it. + * + *

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}. diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java index 6069b9ff3a7..2b8f8bdbe86 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java @@ -36,11 +36,14 @@ class HierarchicalTestExecutor { 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 execute() { @@ -52,7 +55,8 @@ Future execute() { NodeTestTask prepareNodeTestTaskTree() { TestDescriptor rootTestDescriptor = this.request.getRootTestDescriptor(); EngineExecutionListener executionListener = this.request.getEngineExecutionListener(); - NodeTestTask rootTestTask = new NodeTestTask<>(rootTestDescriptor, executionListener, this.executorService); + NodeTestTask rootTestTask = new NodeTestTask<>(rootTestDescriptor, executionListener, this.executorService, + this.throwableCollectorFactory); new NodeTestTaskWalker().walk(rootTestTask); return rootTestTask; } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java index 3ee21321864..cac3ad57fba 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java @@ -31,10 +31,10 @@ */ class NodeTestTask 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 node; private final ExecutionMode executionMode; private final Set exclusiveResources; @@ -48,18 +48,20 @@ class NodeTestTask 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(descriptor, listener, executorService)) + .map(descriptor -> new NodeTestTask(descriptor, listener, executorService, throwableCollectorFactory)) .collect(toCollection(ArrayList::new)); // @formatter:on } @@ -96,6 +98,7 @@ public void setParentContext(C parentContext) { @Override public void execute() { + throwableCollector = throwableCollectorFactory.create(); prepare(); if (throwableCollector.isEmpty()) { checkWhetherSkipped(); @@ -142,7 +145,8 @@ private void executeRecursively() { private void executeDynamicTest(TestDescriptor dynamicTestDescriptor, List> futures) { listener.dynamicTestRegistered(dynamicTestDescriptor); - NodeTestTask nodeTestTask = new NodeTestTask<>(dynamicTestDescriptor, listener, executorService); + NodeTestTask nodeTestTask = new NodeTestTask<>(dynamicTestDescriptor, listener, executorService, + throwableCollectorFactory); Set exclusiveResources = nodeTestTask.getExclusiveResources(); if (!exclusiveResources.isEmpty()) { listener.executionStarted(dynamicTestDescriptor); @@ -169,6 +173,7 @@ private void reportCompletion() { listener.executionStarted(testDescriptor); } listener.executionFinished(testDescriptor, throwableCollector.toTestExecutionResult()); + throwableCollector = null; } @SuppressWarnings("unchecked") diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java new file mode 100644 index 00000000000..3506462176c --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java @@ -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 aborting. + * + * @see ThrowableCollector + * @since 5.3 + */ +@API(status = MAINTAINED, since = "5.3") +public class OpenTest4JAwareThrowableCollector extends ThrowableCollector { + + public OpenTest4JAwareThrowableCollector() { + super(TestAbortedException.class::isInstance); + } + +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java index a02c6a7a7c3..ea12fe61379 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java @@ -16,23 +16,46 @@ import static org.junit.platform.engine.TestExecutionResult.failed; import static org.junit.platform.engine.TestExecutionResult.successful; +import java.util.function.Predicate; + import org.apiguardian.api.API; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestExecutionResult; -import org.opentest4j.TestAbortedException; /** * Simple component that can be used to collect one or more instances of * {@link Throwable}. * - * @since 5.2 + *

This class distinguishes between {@code Throwables} that abort + * and those that fail test execution. The latter take precedence over + * the former, i.e. if both types of {@code Throwables} were collected, the ones + * that abort execution are reported as + * {@linkplain Throwable#addSuppressed(Throwable) suppressed} {@code Throwables} + * of the first {@code Throwable} that failed execution. + * + * @since 5.3 */ -@API(status = MAINTAINED, since = "5.2") +@API(status = MAINTAINED, since = "5.3") public class ThrowableCollector { + private final Predicate abortedExecutionPredicate; + private Throwable throwable; + /** + * Create a new {@code ThrowableCollector} that uses the supplied + * {@link Predicate} to determine whether a {@link Throwable} + * aborted or failed execution. + * + * @param abortedExecutionPredicate the predicate used to decide whether a + * {@code Throwable} aborted execution; never {@code null}. + */ + public ThrowableCollector(Predicate abortedExecutionPredicate) { + this.abortedExecutionPredicate = Preconditions.notNull(abortedExecutionPredicate, + "abortedExecutionPredicate must not be null"); + } + /** * Execute the supplied {@link Executable} and collect any {@link Throwable} * thrown during the execution. @@ -67,11 +90,12 @@ private void add(Throwable t) { if (this.throwable == null) { this.throwable = t; } - else if (this.throwable instanceof TestAbortedException && !(t instanceof TestAbortedException)) { + else if (hasAbortedExecution(this.throwable) && !hasAbortedExecution(t)) { t.addSuppressed(this.throwable); this.throwable = t; } else if (throwable != t) { + // Jupiter does not throw the same Throwable from Node.after() anymore but other engines might this.throwable.addSuppressed(t); } } @@ -81,14 +105,14 @@ else if (throwable != t) { * {@code ThrowableCollector}. * *

If this collector is not empty, the first collected {@code Throwable} - * will be returned with any additional throwables + * will be returned with any additional {@code Throwables} * {@linkplain Throwable#addSuppressed(Throwable) suppressed} in the * first {@code Throwable}. * - *

If the first collected {@code Throwable} was a - * {@link TestAbortedException} and at least one later collected throwable - * wasn't, the first non-{@code TestAbortedException} will be returned with - * the {@code TestAbortedException} and any additional throwables + *

If the first collected {@code Throwable} aborted execution + * and at least one later collected {@code Throwable} failed + * execution, the first failing {@code Throwable} will be returned + * with the previous aborting and any additional {@code Throwables} * {@linkplain Throwable#addSuppressed(Throwable) suppressed} inside. * * @return the first collected {@code Throwable} or {@code null} if this @@ -116,24 +140,19 @@ public boolean isNotEmpty() { return (this.throwable != null); } - public void assertNotSame(Throwable otherThrowable) { - if (this.throwable != otherThrowable) { - assertEmpty(); - } - } - /** * Assert that this {@code ThrowableCollector} is empty (i.e., * has not collected any {@code Throwables}). * *

If this collector is not empty, the first collected {@code Throwable} - * will be thrown with any additional throwables + * will be thrown with any additional {@code Throwables} * {@linkplain Throwable#addSuppressed(Throwable) suppressed} in the * first {@code Throwable}. Note, however, that the {@code Throwable} * will not be wrapped. Rather, it will be * {@linkplain ExceptionUtils#throwAsUncheckedException masked} * as an unchecked exception. * + * @see #getThrowable() * @see ExceptionUtils#throwAsUncheckedException(Throwable) */ public void assertEmpty() { @@ -143,24 +162,28 @@ public void assertEmpty() { } /** - * Convert the collected throwables into a {@link TestExecutionResult}. + * Convert the collected {@link Throwable Throwables} into a {@link TestExecutionResult}. * * @return {@linkplain TestExecutionResult#aborted aborted} if the collected - * {@code throwable} is a {@link TestAbortedException}; - * {@linkplain TestExecutionResult#failed failed} if any other - * {@link Throwable} was collected; and - * {@linkplain TestExecutionResult#successful successful} otherwise + * {@code Throwable} aborted execution; + * {@linkplain TestExecutionResult#failed failed} if it failed + * execution; and {@linkplain TestExecutionResult#successful successful} + * otherwise */ - public TestExecutionResult toTestExecutionResult() { + TestExecutionResult toTestExecutionResult() { if (isEmpty()) { return successful(); } - if (throwable instanceof TestAbortedException) { + if (hasAbortedExecution(throwable)) { return aborted(throwable); } return failed(throwable); } + private boolean hasAbortedExecution(Throwable t) { + return this.abortedExecutionPredicate.test(t); + } + /** * Functional interface for an executable block of code that may throw a * {@link Throwable}. @@ -169,13 +192,23 @@ public TestExecutionResult toTestExecutionResult() { public interface Executable { /** - * Execute this executable. - * - * @throws TestAbortedException to signal abortion - * @throws Throwable to signal failure + * Execute this executable, potentially throwing a {@link Throwable} + * that signals abortion or failure. */ void execute() throws Throwable; } + /** + * Factory for {@code ThrowableCollector} instances. + */ + public interface Factory { + + /** + * Create a new instance of a {@code ThrowableCollector}. + */ + ThrowableCollector create(); + + } + } diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java index 091a5db7f17..ad882050b84 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java @@ -650,7 +650,8 @@ public Type getType() { private static class MyExecutor extends HierarchicalTestExecutor { MyExecutor(ExecutionRequest request, MyEngineExecutionContext rootContext) { - super(request, rootContext, new SameThreadHierarchicalTestExecutorService()); + super(request, rootContext, new SameThreadHierarchicalTestExecutorService(), + OpenTest4JAwareThrowableCollector::new); } } diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskWalkerIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskWalkerIntegrationTests.java index d189928f4e0..96e975e7c1b 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskWalkerIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskWalkerIntegrationTests.java @@ -73,7 +73,8 @@ private NodeTestTask prepareNodeTestTaskTree(Class testClass) { TestDescriptor testDescriptor = new JupiterTestEngine().discover(discoveryRequest, UniqueId.forEngine("junit-jupiter")); ExecutionRequest executionRequest = new ExecutionRequest(testDescriptor, null, null); - HierarchicalTestExecutor executor = new HierarchicalTestExecutor<>(executionRequest, null, null); + HierarchicalTestExecutor executor = new HierarchicalTestExecutor<>(executionRequest, null, null, + () -> new ThrowableCollector(t -> false)); return executor.prepareNodeTestTaskTree(); }