diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.5.2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.5.2.adoc index 75a0768deed..d903fca3f35 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.5.2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.5.2.adoc @@ -21,7 +21,11 @@ No changes. ==== Bug Fixes -* ❓ +* The `JupiterTestEngine` no longer crashes without executing any tests if JUnit 4 is on + the classpath but Hamcrest is not. Specifically, initialization of the + `OpenTest4JAndJUnit4AwareThrowableCollector` class no longer fails if the + `org.junit.internal.AssumptionViolatedException` class cannot be loaded from the + classpath due to a missing Hamcrest dependency. [[release-notes-5.5.2-junit-vintage]] 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 8a33d86a630..419e5e2134a 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,7 @@ import java.util.function.Predicate; +import org.junit.platform.commons.util.BlacklistedExceptions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; import org.opentest4j.TestAbortedException; @@ -37,10 +38,16 @@ private static Predicate createAbortedExecutionPredicate() { Predicate otaPredicate = TestAbortedException.class::isInstance; // Additionally support JUnit 4's AssumptionViolatedException? - Class clazz = ReflectionUtils.tryToLoadClass( - "org.junit.internal.AssumptionViolatedException").toOptional().orElse(null); - if (clazz != null) { - return otaPredicate.or(clazz::isInstance); + try { + Class clazz = ReflectionUtils.tryToLoadClass("org.junit.internal.AssumptionViolatedException").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. } // 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 new file mode 100644 index 00000000000..04b991f3325 --- /dev/null +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015-2019 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 + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.support; + +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 org.junit.internal.AssumptionViolatedException; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * Unit tests for {@link OpenTest4JAndJUnit4AwareThrowableCollector}. + * + * @since 5.6 + */ +class OpenTest4JAndJUnit4AwareThrowableCollectorTests { + + @Test + void simulateHamcrestNotInTheClasspath() throws Exception { + ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); + try { + HamcrestHidingClassLoader classLoader = new HamcrestHidingClassLoader(); + + // We have to set our custom ClassLoader as the TCCL so that + // ReflectionUtils uses it (indirectly via ClassLoaderUtils). + Thread.currentThread().setContextClassLoader(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())); + + Class clazz = classLoader.loadClass(OpenTest4JAndJUnit4AwareThrowableCollector.class.getName()); + assertNotNull(ReflectionUtils.newInstance(clazz)); + } + finally { + Thread.currentThread().setContextClassLoader(originalClassLoader); + } + } + + private static class HamcrestHidingClassLoader extends URLClassLoader { + + HamcrestHidingClassLoader() { + super(new URL[] { + OpenTest4JAndJUnit4AwareThrowableCollector.class.getProtectionDomain().getCodeSource().getLocation() }, + getSystemClassLoader()); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + + // Load a new instance of the OpenTest4JAndJUnit4AwareThrowableCollector class + if (name.equals(OpenTest4JAndJUnit4AwareThrowableCollector.class.getName())) { + return findClass(name); + } + + // Simulate that Hamcrest is not in the classpath when loading AssumptionViolatedException + if (name.equals(AssumptionViolatedException.class.getName())) { + throw new NoClassDefFoundError("org/hamcrest/SelfDescribing"); + } + + // Else + return super.loadClass(name); + } + + } + +}