From edd36d7cac9a6cf76c46a6470ad3e955dcc02d48 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sun, 8 Sep 2019 15:06:36 +0200 Subject: [PATCH] Log cause for failure to load AssumptionViolatedException This commit improves diagnostics by logging the cause of a failure to load the AssumptionViolatedException class in OpenTest4JAndJUnit4AwareThrowableCollector. Issue: #2004 --- ...est4JAndJUnit4AwareThrowableCollector.java | 20 ++++- ...AndJUnit4AwareThrowableCollectorTests.java | 82 +++++++++++++++---- 2 files changed, 85 insertions(+), 17 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java index 419e5e2134ac..bc5499528b03 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java @@ -12,6 +12,8 @@ import java.util.function.Predicate; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.BlacklistedExceptions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; @@ -28,6 +30,13 @@ */ class OpenTest4JAndJUnit4AwareThrowableCollector extends ThrowableCollector { + private static final Logger logger = LoggerFactory.getLogger(OpenTest4JAndJUnit4AwareThrowableCollector.class); + + private static final String ASSUMPTION_VIOLATED_EXCEPTION = "org.junit.internal.AssumptionViolatedException"; + + private static final String COMMON_FAILURE_MESSAGE = "Failed to load class " + ASSUMPTION_VIOLATED_EXCEPTION + + ": only supporting " + TestAbortedException.class.getName() + " for aborted execution."; + private static final Predicate abortedExecutionPredicate = createAbortedExecutionPredicate(); OpenTest4JAndJUnit4AwareThrowableCollector() { @@ -39,15 +48,20 @@ private static Predicate createAbortedExecutionPredicate() { // Additionally support JUnit 4's AssumptionViolatedException? try { - Class clazz = ReflectionUtils.tryToLoadClass("org.junit.internal.AssumptionViolatedException").get(); + Class clazz = ReflectionUtils.tryToLoadClass(ASSUMPTION_VIOLATED_EXCEPTION).get(); if (clazz != null) { return otaPredicate.or(clazz::isInstance); } } catch (Throwable throwable) { BlacklistedExceptions.rethrowIfBlacklisted(throwable); - // Otherwise ignore it since it's likely a ClassNotFoundException, - // NoClassDefFoundError, or similar. + if (throwable instanceof NoClassDefFoundError) { + logger.info(throwable, () -> COMMON_FAILURE_MESSAGE + " Note that " + ASSUMPTION_VIOLATED_EXCEPTION + + " requires that Hamcrest is on the classpath."); + } + else { + logger.debug(throwable, () -> COMMON_FAILURE_MESSAGE); + } } // Else just OTA's TestAbortedException diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java index 17edd49f3b37..8423fce9920b 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java @@ -10,14 +10,20 @@ package org.junit.jupiter.engine.support; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import java.net.URL; import java.net.URLClassLoader; +import java.util.logging.Level; +import java.util.logging.LogRecord; import org.junit.internal.AssumptionViolatedException; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.engine.TrackLogRecords; +import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.commons.util.ReflectionUtils; /** @@ -25,37 +31,80 @@ * * @since 5.5.2 */ +@TrackLogRecords class OpenTest4JAndJUnit4AwareThrowableCollectorTests { @Test - void simulateHamcrestNotInTheClasspath() throws Exception { - ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); - try { - HamcrestHidingClassLoader classLoader = new HamcrestHidingClassLoader(); + void simulateJUnit4NotInTheClasspath(LogRecordListener listener) throws Throwable { + TestClassLoader classLoader = new TestClassLoader(true, false); - // We have to set our custom ClassLoader as the TCCL so that - // ReflectionUtils uses it (indirectly via ClassLoaderUtils). - Thread.currentThread().setContextClassLoader(classLoader); + doWithCustomClassLoader(classLoader, () -> { + // Ensure that our custom ClassLoader actually throws a ClassNotFoundException + // when attempting to load the AssumptionViolatedException class. + assertThrows(ClassNotFoundException.class, + () -> ReflectionUtils.tryToLoadClass(AssumptionViolatedException.class.getName()).get()); + + Class clazz = classLoader.loadClass(OpenTest4JAndJUnit4AwareThrowableCollector.class.getName()); + assertNotNull(ReflectionUtils.newInstance(clazz)); + + // @formatter:off + assertThat(listener.stream(Level.FINE).map(LogRecord::getMessage).findFirst().orElse("")) + .isEqualTo( + "Failed to load class org.junit.internal.AssumptionViolatedException: " + + "only supporting org.opentest4j.TestAbortedException for aborted execution."); + // @formatter:on + }); + } + + @Test + void simulateHamcrestNotInTheClasspath(LogRecordListener listener) throws Throwable { + TestClassLoader classLoader = new TestClassLoader(false, true); + doWithCustomClassLoader(classLoader, () -> { // Ensure that our custom ClassLoader actually throws a NoClassDefFoundError // when attempting to load the AssumptionViolatedException class. assertThrows(NoClassDefFoundError.class, - () -> ReflectionUtils.tryToLoadClass(AssumptionViolatedException.class.getName())); + () -> ReflectionUtils.tryToLoadClass(AssumptionViolatedException.class.getName()).get()); Class clazz = classLoader.loadClass(OpenTest4JAndJUnit4AwareThrowableCollector.class.getName()); assertNotNull(ReflectionUtils.newInstance(clazz)); + + // @formatter:off + assertThat(listener.stream(Level.INFO).map(LogRecord::getMessage).findFirst().orElse("")) + .isEqualTo( + "Failed to load class org.junit.internal.AssumptionViolatedException: " + + "only supporting org.opentest4j.TestAbortedException for aborted execution. " + + "Note that org.junit.internal.AssumptionViolatedException requires that Hamcrest is on the classpath."); + // @formatter:on + }); + } + + private void doWithCustomClassLoader(ClassLoader classLoader, Executable executable) throws Throwable { + ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); + try { + // We have to set our custom ClassLoader as the TCCL so that + // ReflectionUtils uses it (indirectly via ClassLoaderUtils). + Thread.currentThread().setContextClassLoader(classLoader); + + executable.execute(); } finally { Thread.currentThread().setContextClassLoader(originalClassLoader); } } - private static class HamcrestHidingClassLoader extends URLClassLoader { + private static class TestClassLoader extends URLClassLoader { + + private static URL[] CLASSPATH_URLS = new URL[] { + OpenTest4JAndJUnit4AwareThrowableCollector.class.getProtectionDomain().getCodeSource().getLocation() }; - HamcrestHidingClassLoader() { - super(new URL[] { - OpenTest4JAndJUnit4AwareThrowableCollector.class.getProtectionDomain().getCodeSource().getLocation() }, - getSystemClassLoader()); + private final boolean simulateJUnit4Missing; + private final boolean simulateHamcrestMissing; + + public TestClassLoader(boolean simulateJUnit4Missing, boolean simulateHamcrestMissing) { + super(CLASSPATH_URLS, getSystemClassLoader()); + this.simulateJUnit4Missing = simulateJUnit4Missing; + this.simulateHamcrestMissing = simulateHamcrestMissing; } @Override @@ -66,8 +115,13 @@ public Class loadClass(String name) throws ClassNotFoundException { return findClass(name); } + // Simulate that JUnit 4 is not in the classpath when loading AssumptionViolatedException + if (this.simulateJUnit4Missing && name.equals(AssumptionViolatedException.class.getName())) { + throw new ClassNotFoundException(AssumptionViolatedException.class.getName()); + } + // Simulate that Hamcrest is not in the classpath when loading AssumptionViolatedException - if (name.equals(AssumptionViolatedException.class.getName())) { + if (this.simulateHamcrestMissing && name.equals(AssumptionViolatedException.class.getName())) { throw new NoClassDefFoundError("org/hamcrest/SelfDescribing"); }