Skip to content

Commit 835d800

Browse files
committed
Avoid JupiterTestEngine crash if Hamcrest is not on the classpath
Prior to this commit, the JupiterTestEngine would crash without executing any tests if JUnit 4 was on the classpath but Hamcrest was not. This commit fixes this bug by ensuring that initialization of the OpenTest4JAndJUnit4AwareThrowableCollector class no longer fails if the org.junit.internal.AssumptionViolatedException class cannot be loaded from the classpath -- for example, due to a missing Hamcrest dependency. Fixes #2004
1 parent e9baa52 commit 835d800

File tree

3 files changed

+96
-5
lines changed

3 files changed

+96
-5
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.5.2.adoc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ No changes.
2121

2222
==== Bug Fixes
2323

24-
* ❓
24+
* The `JupiterTestEngine` no longer crashes without executing any tests if JUnit 4 is on
25+
the classpath but Hamcrest is not. Specifically, initialization of the
26+
`OpenTest4JAndJUnit4AwareThrowableCollector` class no longer fails if the
27+
`org.junit.internal.AssumptionViolatedException` class cannot be loaded from the
28+
classpath due to a missing Hamcrest dependency.
2529

2630

2731
[[release-notes-5.5.2-junit-vintage]]

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import java.util.function.Predicate;
1414

15+
import org.junit.platform.commons.util.BlacklistedExceptions;
1516
import org.junit.platform.commons.util.ReflectionUtils;
1617
import org.junit.platform.engine.support.hierarchical.ThrowableCollector;
1718
import org.opentest4j.TestAbortedException;
@@ -37,10 +38,16 @@ private static Predicate<? super Throwable> createAbortedExecutionPredicate() {
3738
Predicate<Throwable> otaPredicate = TestAbortedException.class::isInstance;
3839

3940
// Additionally support JUnit 4's AssumptionViolatedException?
40-
Class<?> clazz = ReflectionUtils.tryToLoadClass(
41-
"org.junit.internal.AssumptionViolatedException").toOptional().orElse(null);
42-
if (clazz != null) {
43-
return otaPredicate.or(clazz::isInstance);
41+
try {
42+
Class<?> clazz = ReflectionUtils.tryToLoadClass("org.junit.internal.AssumptionViolatedException").get();
43+
if (clazz != null) {
44+
return otaPredicate.or(clazz::isInstance);
45+
}
46+
}
47+
catch (Throwable throwable) {
48+
BlacklistedExceptions.rethrowIfBlacklisted(throwable);
49+
// Otherwise ignore it since it's likely a ClassNotFoundException,
50+
// NoClassDefFoundError, or similar.
4451
}
4552

4653
// Else just OTA's TestAbortedException
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2015-2019 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.jupiter.engine.support;
12+
13+
import static org.junit.jupiter.api.Assertions.assertNotNull;
14+
import static org.junit.jupiter.api.Assertions.assertThrows;
15+
16+
import java.net.URL;
17+
import java.net.URLClassLoader;
18+
19+
import org.junit.internal.AssumptionViolatedException;
20+
import org.junit.jupiter.api.Test;
21+
import org.junit.platform.commons.util.ReflectionUtils;
22+
23+
/**
24+
* Unit tests for {@link OpenTest4JAndJUnit4AwareThrowableCollector}.
25+
*
26+
* @since 5.6
27+
*/
28+
class OpenTest4JAndJUnit4AwareThrowableCollectorTests {
29+
30+
@Test
31+
void simulateHamcrestNotInTheClasspath() throws Exception {
32+
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
33+
try {
34+
HamcrestHidingClassLoader classLoader = new HamcrestHidingClassLoader();
35+
36+
// We have to set our custom ClassLoader as the TCCL so that
37+
// ReflectionUtils uses it (indirectly via ClassLoaderUtils).
38+
Thread.currentThread().setContextClassLoader(classLoader);
39+
40+
// Ensure that our custom ClassLoader actually throws a NoClassDefFoundError
41+
// when attempting to load the AssumptionViolatedException class.
42+
assertThrows(NoClassDefFoundError.class,
43+
() -> ReflectionUtils.tryToLoadClass(AssumptionViolatedException.class.getName()));
44+
45+
Class<?> clazz = classLoader.loadClass(OpenTest4JAndJUnit4AwareThrowableCollector.class.getName());
46+
assertNotNull(ReflectionUtils.newInstance(clazz));
47+
}
48+
finally {
49+
Thread.currentThread().setContextClassLoader(originalClassLoader);
50+
}
51+
}
52+
53+
private static class HamcrestHidingClassLoader extends URLClassLoader {
54+
55+
HamcrestHidingClassLoader() {
56+
super(new URL[] {
57+
OpenTest4JAndJUnit4AwareThrowableCollector.class.getProtectionDomain().getCodeSource().getLocation() },
58+
getSystemClassLoader());
59+
}
60+
61+
@Override
62+
public Class<?> loadClass(String name) throws ClassNotFoundException {
63+
64+
// Load a new instance of the OpenTest4JAndJUnit4AwareThrowableCollector class
65+
if (name.equals(OpenTest4JAndJUnit4AwareThrowableCollector.class.getName())) {
66+
return findClass(name);
67+
}
68+
69+
// Simulate that Hamcrest is not in the classpath when loading AssumptionViolatedException
70+
if (name.equals(AssumptionViolatedException.class.getName())) {
71+
throw new NoClassDefFoundError("org/hamcrest/SelfDescribing");
72+
}
73+
74+
// Else
75+
return super.loadClass(name);
76+
}
77+
78+
}
79+
80+
}

0 commit comments

Comments
 (0)