From 7ec85a3c3bf4aa96e8320147dae179ca181f1b33 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 13 Jul 2016 20:29:01 +0200 Subject: [PATCH] Support suppressed exceptions in the TestContext framework Prior to this commit, if multiple TestExecutionListener 'after' methods threw an exception, only the first such exception was rethrown. Subsequent exceptions were logged, but there was no way to access or process them other than via the log file. This commit addresses this shortcoming by making use of the support for suppressed exceptions introduced in Java 7. Specifically, if multiple TestExecutionListener 'after' methods throw an exception, the first exception will be rethrown with subsequent exceptions suppressed in the first one. Issue: SPR-14459 --- .../test/context/TestContextManager.java | 9 ++ ...ntextManagerSuppressedExceptionsTests.java | 140 ++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 spring-test/src/test/java/org/springframework/test/context/TestContextManagerSuppressedExceptionsTests.java diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java index a2413b1b39f..4aa1c4ec858 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java @@ -368,6 +368,9 @@ public class TestContextManager { if (afterTestExecutionException == null) { afterTestExecutionException = ex; } + else { + afterTestExecutionException.addSuppressed(ex); + } } } if (afterTestExecutionException != null) { @@ -423,6 +426,9 @@ public class TestContextManager { if (afterTestMethodException == null) { afterTestMethodException = ex; } + else { + afterTestMethodException.addSuppressed(ex); + } } } if (afterTestMethodException != null) { @@ -464,6 +470,9 @@ public class TestContextManager { if (afterTestClassException == null) { afterTestClassException = ex; } + else { + afterTestClassException.addSuppressed(ex); + } } } if (afterTestClassException != null) { diff --git a/spring-test/src/test/java/org/springframework/test/context/TestContextManagerSuppressedExceptionsTests.java b/spring-test/src/test/java/org/springframework/test/context/TestContextManagerSuppressedExceptionsTests.java new file mode 100644 index 00000000000..f3a779e0bf0 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/TestContextManagerSuppressedExceptionsTests.java @@ -0,0 +1,140 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context; + +import java.lang.reflect.Method; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * JUnit 4 based unit tests for {@link TestContextManager}, which verify proper + * support for suppressed exceptions thrown by {@link TestExecutionListener + * TestExecutionListeners}. + * + * @author Sam Brannen + * @since 5.0 + * @see Throwable#getSuppressed() + */ +public class TestContextManagerSuppressedExceptionsTests { + + @Test + public void afterTestExecution() throws Exception { + test("afterTestExecution", FailingAfterTestExecutionTestCase.class, + (tcm, c, m) -> tcm.afterTestExecution(this, m, null)); + } + + @Test + public void afterTestMethod() throws Exception { + test("afterTestMethod", FailingAfterTestMethodTestCase.class, + (tcm, c, m) -> tcm.afterTestMethod(this, m, null)); + } + + @Test + public void afterTestClass() throws Exception { + test("afterTestClass", FailingAfterTestClassTestCase.class, (tcm, c, m) -> tcm.afterTestClass()); + } + + private void test(String useCase, Class testClass, Callback callback) throws Exception { + TestContextManager testContextManager = new TestContextManager(testClass); + assertEquals("Registered TestExecutionListeners", 2, testContextManager.getTestExecutionListeners().size()); + + try { + Method testMethod = getClass().getMethod("toString"); + callback.invoke(testContextManager, testClass, testMethod); + fail("should have thrown an AssertionError"); + } + catch (AssertionError err) { + // 'after' callbacks are reversed, so 2 comes before 1. + assertEquals(useCase + "-2", err.getMessage()); + Throwable[] suppressed = err.getSuppressed(); + assertEquals(1, suppressed.length); + assertEquals(useCase + "-1", suppressed[0].getMessage()); + } + } + + + // ------------------------------------------------------------------- + + @FunctionalInterface + private interface Callback { + + void invoke(TestContextManager tcm, Class clazz, Method method) throws Exception; + } + + private static class FailingAfterTestClassListener1 implements TestExecutionListener { + + @Override + public void afterTestClass(TestContext testContext) { + fail("afterTestClass-1"); + } + } + + private static class FailingAfterTestClassListener2 implements TestExecutionListener { + + @Override + public void afterTestClass(TestContext testContext) { + fail("afterTestClass-2"); + } + } + + private static class FailingAfterTestMethodListener1 implements TestExecutionListener { + + @Override + public void afterTestMethod(TestContext testContext) { + fail("afterTestMethod-1"); + } + } + + private static class FailingAfterTestMethodListener2 implements TestExecutionListener { + + @Override + public void afterTestMethod(TestContext testContext) { + fail("afterTestMethod-2"); + } + } + + private static class FailingAfterTestExecutionListener1 implements TestExecutionListener { + + @Override + public void afterTestExecution(TestContext testContext) { + fail("afterTestExecution-1"); + } + } + + private static class FailingAfterTestExecutionListener2 implements TestExecutionListener { + + @Override + public void afterTestExecution(TestContext testContext) { + fail("afterTestExecution-2"); + } + } + + @TestExecutionListeners({ FailingAfterTestExecutionListener1.class, FailingAfterTestExecutionListener2.class }) + private static class FailingAfterTestExecutionTestCase { + } + + @TestExecutionListeners({ FailingAfterTestMethodListener1.class, FailingAfterTestMethodListener2.class }) + private static class FailingAfterTestMethodTestCase { + } + + @TestExecutionListeners({ FailingAfterTestClassListener1.class, FailingAfterTestClassListener2.class }) + private static class FailingAfterTestClassTestCase { + } + +}