Skip to content

Commit

Permalink
Log cause for failure to load AssumptionViolatedException
Browse files Browse the repository at this point in the history
This commit improves diagnostics by logging the cause of a failure to
load the AssumptionViolatedException class in
OpenTest4JAndJUnit4AwareThrowableCollector.

Issue: #2004
  • Loading branch information
sbrannen committed Sep 8, 2019
1 parent cc13950 commit edd36d7
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<? super Throwable> abortedExecutionPredicate = createAbortedExecutionPredicate();

OpenTest4JAndJUnit4AwareThrowableCollector() {
Expand All @@ -39,15 +48,20 @@ private static Predicate<? super Throwable> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,52 +10,101 @@

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;

/**
* Unit tests for {@link OpenTest4JAndJUnit4AwareThrowableCollector}.
*
* @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("<not found>"))
.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("<not found>"))
.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
Expand All @@ -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");
}

Expand Down

0 comments on commit edd36d7

Please sign in to comment.