diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java index 318356df327c..54640f43feaa 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java @@ -61,4 +61,19 @@ public interface TestExecutionExceptionHandler extends Extension { */ void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable; + default void handleExceptionInBeforeAllMethod(ExtensionContext context, Throwable throwable) throws Throwable { + throw throwable; + } + + default void handleExceptionInBeforeEachMethod(ExtensionContext context, Throwable throwable) throws Throwable { + throw throwable; + } + + default void handleExceptionInAfterEachMethod(ExtensionContext context, Throwable throwable) throws Throwable { + throw throwable; + } + + default void handleExceptionInAfterAllMethod(ExtensionContext context, Throwable throwable) throws Throwable { + throw throwable; + } } 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 e0aead7dfe37..b3111593cb5b 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 @@ -28,6 +28,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Function; import org.apiguardian.api.API; @@ -37,9 +38,11 @@ import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; import org.junit.jupiter.api.extension.TestInstanceFactory; import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.jupiter.api.extension.TestInstantiationException; +import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.engine.execution.AfterEachMethodAdapter; import org.junit.jupiter.engine.execution.BeforeEachMethodAdapter; import org.junit.jupiter.engine.execution.ExecutableInvoker; @@ -48,6 +51,7 @@ import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.BlacklistedExceptions; +import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.StringUtils; @@ -359,8 +363,15 @@ private void invokeBeforeAllMethods(JupiterEngineExecutionContext context) { Object testInstance = extensionContext.getTestInstance().orElse(null); for (Method method : this.beforeAllMethods) { - throwableCollector.execute( - () -> executableInvoker.invoke(method, testInstance, extensionContext, registry)); + throwableCollector.execute(() -> { + try { + executableInvoker.invoke(method, testInstance, extensionContext, registry); + } + catch (Throwable throwable) { + invokeTestExecutionExceptionHandlers(throwable, registry, + ((ex, handler) -> () -> handler.handleExceptionInBeforeAllMethod(extensionContext, ex))); + } + }); if (throwableCollector.isNotEmpty()) { break; } @@ -373,8 +384,15 @@ private void invokeAfterAllMethods(JupiterEngineExecutionContext context) { ThrowableCollector throwableCollector = context.getThrowableCollector(); Object testInstance = extensionContext.getTestInstance().orElse(null); - this.afterAllMethods.forEach(method -> throwableCollector.execute( - () -> executableInvoker.invoke(method, testInstance, extensionContext, registry))); + this.afterAllMethods.forEach(method -> throwableCollector.execute(() -> { + try { + executableInvoker.invoke(method, testInstance, extensionContext, registry); + } + catch (Throwable throwable) { + invokeTestExecutionExceptionHandlers(throwable, registry, + ((ex, handler) -> () -> handler.handleExceptionInAfterAllMethod(extensionContext, ex))); + } + })); } private void invokeAfterAllCallbacks(JupiterEngineExecutionContext context) { @@ -386,6 +404,31 @@ private void invokeAfterAllCallbacks(JupiterEngineExecutionContext context) { .forEach(extension -> throwableCollector.execute(() -> extension.afterAll(extensionContext))); } + private void invokeTestExecutionExceptionHandlers(Throwable ex, ExtensionRegistry registry, + BiFunction generator) { + + invokeTestExecutionExceptionHandlers(ex, registry.getReversedExtensions(TestExecutionExceptionHandler.class), + generator); + } + + private void invokeTestExecutionExceptionHandlers(Throwable ex, List handlers, + BiFunction generator) { + + // No handlers left? + if (handlers.isEmpty()) { + ExceptionUtils.throwAsUncheckedException(ex); + } + + try { + // Invoke next available handler + Executable executable = generator.apply(ex, handlers.remove(0)); + executable.execute(); + } + catch (Throwable t) { + invokeTestExecutionExceptionHandlers(t, handlers, generator); + } + } + private void registerBeforeEachMethodAdapters(ExtensionRegistry registry) { List beforeEachMethods = findBeforeEachMethods(this.testClass); registerMethodsAsExtensions(beforeEachMethods, registry, this::synthesizeBeforeEachMethodAdapter); 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 0ee14ece676b..5403750676e7 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 @@ -132,9 +132,15 @@ private void invokeBeforeEachCallbacks(JupiterEngineExecutionContext context) { private void invokeBeforeEachMethods(JupiterEngineExecutionContext context) { ExtensionRegistry registry = context.getExtensionRegistry(); - invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(context, - ((extensionContext, adapter) -> () -> adapter.invokeBeforeEachMethod(extensionContext, registry)), - BeforeEachMethodAdapter.class); + invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(context, ((extensionContext, adapter) -> () -> { + try { + adapter.invokeBeforeEachMethod(extensionContext, registry); + } + catch (Throwable throwable) { + invokeTestExecutionExceptionHandlers(throwable, registry, + ((ex, handler) -> () -> handler.handleExceptionInBeforeEachMethod(extensionContext, ex))); + } + }), BeforeEachMethodAdapter.class); } private void invokeBeforeTestExecutionCallbacks(JupiterEngineExecutionContext context) { @@ -171,20 +177,21 @@ protected void invokeTestMethod(JupiterEngineExecutionContext context, DynamicTe executableInvoker.invoke(testMethod, instance, extensionContext, context.getExtensionRegistry()); } catch (Throwable throwable) { - invokeTestExecutionExceptionHandlers(context.getExtensionRegistry(), extensionContext, throwable); + invokeTestExecutionExceptionHandlers(throwable, context.getExtensionRegistry(), + ((ex, handler) -> () -> handler.handleTestExecutionException(extensionContext, ex))); } }); } - private void invokeTestExecutionExceptionHandlers(ExtensionRegistry registry, ExtensionContext context, - Throwable ex) { + private void invokeTestExecutionExceptionHandlers(Throwable ex, ExtensionRegistry registry, + BiFunction generator) { invokeTestExecutionExceptionHandlers(ex, registry.getReversedExtensions(TestExecutionExceptionHandler.class), - context); + generator); } private void invokeTestExecutionExceptionHandlers(Throwable ex, List handlers, - ExtensionContext context) { + BiFunction generator) { // No handlers left? if (handlers.isEmpty()) { @@ -193,10 +200,11 @@ private void invokeTestExecutionExceptionHandlers(Throwable ex, List () -> adapter.invokeAfterEachMethod(extensionContext, registry)), - AfterEachMethodAdapter.class); + invokeAllAfterMethodsOrCallbacks(context, ((extensionContext, adapter) -> () -> { + try { + adapter.invokeAfterEachMethod(extensionContext, registry); + } + catch (Throwable throwable) { + invokeTestExecutionExceptionHandlers(throwable, registry, + ((ex, handler) -> () -> handler.handleExceptionInAfterEachMethod(extensionContext, ex))); + } + }), AfterEachMethodAdapter.class); } private void invokeAfterEachCallbacks(JupiterEngineExecutionContext context) { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllExceptionHandlerTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllExceptionHandlerTests.java new file mode 100644 index 000000000000..e44a90def80a --- /dev/null +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllExceptionHandlerTests.java @@ -0,0 +1,254 @@ +/* + * 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.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.allOf; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.test.event.ExecutionEventConditions.assertRecordedExecutionEventsContainsExactly; +import static org.junit.platform.engine.test.event.ExecutionEventConditions.container; +import static org.junit.platform.engine.test.event.ExecutionEventConditions.engine; +import static org.junit.platform.engine.test.event.ExecutionEventConditions.event; +import static org.junit.platform.engine.test.event.ExecutionEventConditions.finishedSuccessfully; +import static org.junit.platform.engine.test.event.ExecutionEventConditions.finishedWithFailure; +import static org.junit.platform.engine.test.event.ExecutionEventConditions.started; +import static org.junit.platform.engine.test.event.ExecutionEventConditions.test; +import static org.junit.platform.engine.test.event.TestExecutionResultConditions.isA; +import static org.junit.platform.engine.test.event.TestExecutionResultConditions.message; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.platform.engine.test.event.ExecutionEventRecorder; +import org.junit.platform.launcher.LauncherDiscoveryRequest; + +/** + * Integration tests that verify exception handling of {@code @BeforeAll} and {@code @AfterAll} methods in + * {@link TestExecutionExceptionHandler}. + */ +class BeforeAndAfterAllExceptionHandlerTests extends AbstractJupiterTestEngineTests { + + static List handlerCalls = new ArrayList<>(); + + @BeforeEach + void resetStatics() { + handlerCalls.clear(); + } + + @Test + void exceptionHandlerRethrowsException() { + LauncherDiscoveryRequest request = request().selectors(selectMethod(RethrowTestCase.class, "test")).build(); + ExecutionEventRecorder eventRecorder = executeTests(request); + + assertRecordedExecutionEventsContainsExactly(eventRecorder.getExecutionEvents(), // + event(engine(), started()), // + event(container(RethrowTestCase.class), started()), // + event(container(RethrowTestCase.class), + finishedWithFailure(allOf(isA(IOException.class), message("checked")))), // + event(engine(), finishedSuccessfully())); + + assertEquals(Arrays.asList("rethrowBeforeAll", "rethrowAfterAll", "rethrowAfterAll"), handlerCalls); + } + + @Test + void exceptionHandlerSwallowsException() { + LauncherDiscoveryRequest request = request().selectors(selectMethod(SwallowTestCase.class, "test")).build(); + ExecutionEventRecorder eventRecorder = executeTests(request); + + assertRecordedExecutionEventsContainsExactly(eventRecorder.getExecutionEvents(), // + event(engine(), started()), // + event(container(SwallowTestCase.class), started()), // + event(test("test"), started()), // + event(test("test"), finishedSuccessfully()), // + event(container(SwallowTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + + assertEquals( + Arrays.asList("swallowBeforeAll", "swallowBeforeAll", "swallowTest", "swallowAfterAll", "swallowAfterAll"), + handlerCalls); + } + + @Test + void exceptionHandlerConvertsException() { + LauncherDiscoveryRequest request = request().selectors(selectMethod(ConvertTestCase.class, "test")).build(); + ExecutionEventRecorder eventRecorder = executeTests(request); + + assertRecordedExecutionEventsContainsExactly(eventRecorder.getExecutionEvents(), // + event(engine(), started()), // + event(container(ConvertTestCase.class), started()), // + event(container(ConvertTestCase.class), + finishedWithFailure(allOf(isA(RuntimeException.class), message("unchecked")))), // + event(engine(), finishedSuccessfully())); + + assertEquals(Arrays.asList("convertBeforeAll", "convertAfterAll", "convertAfterAll"), handlerCalls); + } + + @Test + void severalHandlersAreCalledInOrder() { + LauncherDiscoveryRequest request = request().selectors(selectMethod(SeveralTestCase.class, "test")).build(); + ExecutionEventRecorder eventRecorder = executeTests(request); + + assertRecordedExecutionEventsContainsExactly(eventRecorder.getExecutionEvents(), // + event(engine(), started()), // + event(container(SeveralTestCase.class), started()), // + event(test("test"), started()), // + event(test("test"), finishedSuccessfully()), // + event(container(SeveralTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + + assertEquals(Arrays.asList( // + "convertBeforeAll", "rethrowBeforeAll", "swallowBeforeAll", // + "convertBeforeAll", "rethrowBeforeAll", "swallowBeforeAll", // + "convertTest", "rethrowTest", "swallowTest", // + "convertAfterAll", "rethrowAfterAll", "swallowAfterAll", // + "convertAfterAll", "rethrowAfterAll", "swallowAfterAll"), // + handlerCalls); + } + + // ------------------------------------------------------------------- + + static abstract class ATestCase { + + @BeforeAll + static void beforeAll() throws IOException { + throw new IOException("checked"); + } + + @BeforeAll + static void beforeAll2() throws IOException { + throw new IOException("checked"); + } + + @Test + void test() throws IOException { + throw new IOException("checked"); + } + + @AfterAll + static void afterAll() throws IOException { + throw new IOException("checked"); + } + + @AfterAll + static void afterAll2() throws IOException { + throw new IOException("checked"); + } + } + + @ExtendWith(RethrowException.class) + static class RethrowTestCase extends ATestCase { + } + + @ExtendWith(SwallowException.class) + static class SwallowTestCase extends ATestCase { + } + + @ExtendWith(ConvertException.class) + static class ConvertTestCase extends ATestCase { + } + + @ExtendWith(ShouldNotBeCalled.class) + @ExtendWith(SwallowException.class) + @ExtendWith(RethrowException.class) + @ExtendWith(ConvertException.class) + static class SeveralTestCase extends ATestCase { + } + + static class RethrowException implements TestExecutionExceptionHandler { + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("rethrowTest"); + throw throwable; + } + + @Override + public void handleExceptionInBeforeAllMethod(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("rethrowBeforeAll"); + throw throwable; + } + + @Override + public void handleExceptionInAfterAllMethod(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("rethrowAfterAll"); + throw throwable; + } + } + + static class SwallowException implements TestExecutionExceptionHandler { + // swallow exception by not rethrowing it + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("swallowTest"); + } + + @Override + public void handleExceptionInBeforeAllMethod(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("swallowBeforeAll"); + } + + @Override + public void handleExceptionInAfterAllMethod(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("swallowAfterAll"); + } + } + + static class ConvertException implements TestExecutionExceptionHandler { + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("convertTest"); + throw new RuntimeException("unchecked"); + } + + @Override + public void handleExceptionInBeforeAllMethod(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("convertBeforeAll"); + throw new RuntimeException("unchecked"); + } + + @Override + public void handleExceptionInAfterAllMethod(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("convertAfterAll"); + throw new RuntimeException("unchecked"); + } + } + + static class ShouldNotBeCalled implements TestExecutionExceptionHandler { + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("shouldNotBeCalledTest"); + } + + @Override + public void handleExceptionInBeforeAllMethod(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("shouldNotBeCalledBeforeAll"); + } + + @Override + public void handleExceptionInAfterAllMethod(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("shouldNotBeCalledAfterAll"); + } + } +} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachExceptionHandlerTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachExceptionHandlerTests.java new file mode 100644 index 000000000000..4355805a0b57 --- /dev/null +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachExceptionHandlerTests.java @@ -0,0 +1,258 @@ +/* + * 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.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.allOf; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.test.event.ExecutionEventConditions.assertRecordedExecutionEventsContainsExactly; +import static org.junit.platform.engine.test.event.ExecutionEventConditions.container; +import static org.junit.platform.engine.test.event.ExecutionEventConditions.engine; +import static org.junit.platform.engine.test.event.ExecutionEventConditions.event; +import static org.junit.platform.engine.test.event.ExecutionEventConditions.finishedSuccessfully; +import static org.junit.platform.engine.test.event.ExecutionEventConditions.finishedWithFailure; +import static org.junit.platform.engine.test.event.ExecutionEventConditions.started; +import static org.junit.platform.engine.test.event.ExecutionEventConditions.test; +import static org.junit.platform.engine.test.event.TestExecutionResultConditions.isA; +import static org.junit.platform.engine.test.event.TestExecutionResultConditions.message; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.platform.engine.test.event.ExecutionEventRecorder; +import org.junit.platform.launcher.LauncherDiscoveryRequest; + +/** + * Integration tests that verify exception handling of {@code @BeforeEach} and {@code @AfterEach} methods in + * {@link TestExecutionExceptionHandler}. + */ +class BeforeAndAfterEachExceptionHandlerTests extends AbstractJupiterTestEngineTests { + + static List handlerCalls = new ArrayList<>(); + + @BeforeEach + void resetStatics() { + handlerCalls.clear(); + } + + @Test + void exceptionHandlerRethrowsException() { + LauncherDiscoveryRequest request = request().selectors(selectMethod(ATestCase.class, "testRethrow")).build(); + ExecutionEventRecorder eventRecorder = executeTests(request); + + assertRecordedExecutionEventsContainsExactly(eventRecorder.getExecutionEvents(), // + event(engine(), started()), // + event(container(ATestCase.class), started()), // + event(test("testRethrow"), started()), // + event(test("testRethrow"), finishedWithFailure(allOf(isA(IOException.class), message("checked")))), // + event(container(ATestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + + assertEquals(Arrays.asList("rethrowBeforeEach", "rethrowAfterEach", "rethrowAfterEach"), handlerCalls); + } + + @Test + void exceptionHandlerSwallowsException() { + LauncherDiscoveryRequest request = request().selectors(selectMethod(ATestCase.class, "testSwallow")).build(); + ExecutionEventRecorder eventRecorder = executeTests(request); + + assertRecordedExecutionEventsContainsExactly(eventRecorder.getExecutionEvents(), // + event(engine(), started()), // + event(container(ATestCase.class), started()), // + event(test("testSwallow"), started()), // + event(test("testSwallow"), finishedSuccessfully()), // + event(container(ATestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + + assertEquals(Arrays.asList( // + "swallowBeforeEach", "swallowBeforeEach", "swallowTest", "swallowAfterEach", "swallowAfterEach"), // + handlerCalls); + } + + @Test + void exceptionHandlerConvertsException() { + LauncherDiscoveryRequest request = request().selectors(selectMethod(ATestCase.class, "testConvert")).build(); + ExecutionEventRecorder eventRecorder = executeTests(request); + + assertRecordedExecutionEventsContainsExactly(eventRecorder.getExecutionEvents(), // + event(engine(), started()), // + event(container(ATestCase.class), started()), // + event(test("testConvert"), started()), // + event(test("testConvert"), finishedWithFailure(allOf(isA(RuntimeException.class), message("unchecked")))), // + event(container(ATestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + + assertEquals(Arrays.asList("convertBeforeEach", "convertAfterEach", "convertAfterEach"), handlerCalls); + } + + @Test + void severalHandlersAreCalledInOrder() { + LauncherDiscoveryRequest request = request().selectors(selectMethod(ATestCase.class, "testSeveral")).build(); + ExecutionEventRecorder eventRecorder = executeTests(request); + + assertRecordedExecutionEventsContainsExactly(eventRecorder.getExecutionEvents(), // + event(engine(), started()), // + event(container(ATestCase.class), started()), // + event(test("testSeveral"), started()), // + event(test("testSeveral"), finishedSuccessfully()), // + event(container(ATestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + + assertEquals(Arrays.asList( // + "convertBeforeEach", "rethrowBeforeEach", "swallowBeforeEach", // + "convertBeforeEach", "rethrowBeforeEach", "swallowBeforeEach", // + "convertTest", "rethrowTest", "swallowTest", // + "convertAfterEach", "rethrowAfterEach", "swallowAfterEach", // + "convertAfterEach", "rethrowAfterEach", "swallowAfterEach"), // + handlerCalls); + } + + // ------------------------------------------------------------------- + + static class ATestCase { + + @BeforeEach + void beforeEach() throws IOException { + throw new IOException("checked"); + } + + @BeforeEach + void beforeEach2() throws IOException { + throw new IOException("checked"); + } + + @Test + @ExtendWith(RethrowException.class) + void testRethrow() throws IOException { + throw new IOException("checked"); + } + + @Test + @ExtendWith(SwallowException.class) + void testSwallow() throws IOException { + throw new IOException("checked"); + } + + @Test + @ExtendWith(ConvertException.class) + void testConvert() throws IOException { + throw new IOException("checked"); + } + + @Test + @ExtendWith(ShouldNotBeCalled.class) + @ExtendWith(SwallowException.class) + @ExtendWith(RethrowException.class) + @ExtendWith(ConvertException.class) + void testSeveral() throws IOException { + throw new IOException("checked"); + } + + @AfterEach + void afterEach() throws IOException { + throw new IOException("checked"); + } + + @AfterEach + void afterEach2() throws IOException { + throw new IOException("checked"); + } + } + + static class RethrowException implements TestExecutionExceptionHandler { + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("rethrowTest"); + throw throwable; + } + + @Override + public void handleExceptionInBeforeEachMethod(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("rethrowBeforeEach"); + throw throwable; + } + + @Override + public void handleExceptionInAfterEachMethod(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("rethrowAfterEach"); + throw throwable; + } + } + + static class SwallowException implements TestExecutionExceptionHandler { + // swallow exception by not rethrowing it + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("swallowTest"); + } + + @Override + public void handleExceptionInBeforeEachMethod(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("swallowBeforeEach"); + } + + @Override + public void handleExceptionInAfterEachMethod(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("swallowAfterEach"); + } + } + + static class ConvertException implements TestExecutionExceptionHandler { + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("convertTest"); + throw new RuntimeException("unchecked"); + } + + @Override + public void handleExceptionInBeforeEachMethod(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("convertBeforeEach"); + throw new RuntimeException("unchecked"); + } + + @Override + public void handleExceptionInAfterEachMethod(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("convertAfterEach"); + throw new RuntimeException("unchecked"); + } + } + + static class ShouldNotBeCalled implements TestExecutionExceptionHandler { + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("shouldNotBeCalledTest"); + } + + @Override + public void handleExceptionInBeforeEachMethod(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("shouldNotBeCalledBeforeEach"); + } + + @Override + public void handleExceptionInAfterEachMethod(ExtensionContext context, Throwable throwable) throws Throwable { + handlerCalls.add("shouldNotBeCalledAfterEach"); + } + } +}