Handle NoClassDefFoundError consistently for skipped TestExecutionListener

Commit d1b65f6d3e introduced a regression for the handling of
NoClassDefFoundError when skipping a TestExecutionListener that cannot
be loaded. Specifically, the DEBUG log message changed and included the
stack trace; whereas, it previously did not include the stack trace.

This commit consistently handles NoClassDefFoundError for a skipped
TestExecutionListener, whether a default listener or a listener
registered via @TestExecutionListeners and aligns with the previous
behavior by omitting the stack trace for a NoClassDefFoundError.

Closes gh-28962
This commit is contained in:
Sam Brannen 2022-08-17 11:25:48 +02:00
parent 7e8d6dbd45
commit 8fe6a9f537
1 changed files with 45 additions and 37 deletions

View File

@ -25,7 +25,6 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -201,36 +200,13 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
* @see SpringFactoriesLoader#load(Class, FailureHandler)
*/
protected List<TestExecutionListener> getDefaultTestExecutionListeners() {
FailureHandler failureHandler = (factoryType, factoryImplementationName, failure) -> {
Throwable ex = (failure instanceof InvocationTargetException ite ?
ite.getTargetException() : failure);
if (ex instanceof LinkageError || ex instanceof ClassNotFoundException) {
if (logger.isDebugEnabled()) {
logger.debug("Could not load default TestExecutionListener [" + factoryImplementationName +
"]. Specify custom listener classes or make the default listener classes available.", ex);
}
}
else {
if (ex instanceof RuntimeException runtimeException) {
throw runtimeException;
}
if (ex instanceof Error error) {
throw error;
}
throw new IllegalStateException("Failed to load default TestExecutionListener [" +
factoryImplementationName + "].", ex);
}
};
List<TestExecutionListener> listeners = SpringFactoriesLoader.forDefaultResourceLocation()
.load(TestExecutionListener.class, failureHandler);
.load(TestExecutionListener.class, this::handleListenerInstantiationFailure);
if (logger.isInfoEnabled()) {
List<String> classNames = listeners.stream()
.map(listener -> listener.getClass().getName())
.collect(Collectors.toList());
logger.info(String.format("Loaded default TestExecutionListener implementations from location [%s]: %s",
SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames));
List<String> classNames = listeners.stream().map(Object::getClass).map(Class::getName).toList();
logger.info("Loaded default TestExecutionListener implementations from location [%s]: %s"
.formatted(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames));
}
return Collections.unmodifiableList(listeners);
@ -244,15 +220,8 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
listeners.add(BeanUtils.instantiateClass(listenerClass));
}
catch (BeanInstantiationException ex) {
if (ex.getCause() instanceof NoClassDefFoundError) {
// TestExecutionListener not applicable due to a missing dependency
if (logger.isDebugEnabled()) {
logger.debug(String.format(
"Skipping candidate TestExecutionListener [%s] due to a missing dependency. " +
"Specify custom listener classes or make the default listener classes " +
"and their required dependencies available. Offending class: [%s]",
listenerClass.getName(), ex.getCause().getMessage()));
}
if (ex.getCause() instanceof NoClassDefFoundError noClassDefFoundError) {
handleNoClassDefFoundError(listenerClass.getName(), noClassDefFoundError);
}
else {
throw ex;
@ -262,6 +231,45 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
return listeners;
}
private void handleListenerInstantiationFailure(
Class<?> factoryType, String listenerClassName, Throwable failure) {
Throwable ex = (failure instanceof InvocationTargetException ite ?
ite.getTargetException() : failure);
if (ex instanceof LinkageError || ex instanceof ClassNotFoundException) {
if (ex instanceof NoClassDefFoundError noClassDefFoundError) {
handleNoClassDefFoundError(listenerClassName, noClassDefFoundError);
}
else if (logger.isDebugEnabled()) {
logger.debug("""
Could not load default TestExecutionListener [%s]. Specify custom \
listener classes or make the default listener classes available."""
.formatted(listenerClassName), ex);
}
}
else {
if (ex instanceof RuntimeException runtimeException) {
throw runtimeException;
}
if (ex instanceof Error error) {
throw error;
}
throw new IllegalStateException(
"Failed to load default TestExecutionListener [%s].".formatted(listenerClassName), ex);
}
}
private void handleNoClassDefFoundError(String listenerClassName, NoClassDefFoundError error) {
// TestExecutionListener not applicable due to a missing dependency
if (logger.isDebugEnabled()) {
logger.debug("""
Skipping candidate TestExecutionListener [%s] due to a missing dependency. \
Specify custom listener classes or make the default listener classes \
and their required dependencies available. Offending class: [%s]"""
.formatted(listenerClassName, error.getMessage()));
}
}
/**
* {@inheritDoc}
*/