Skip to content

Commit

Permalink
Handle NoClassDefFoundError consistently for skipped TestExecutionLis…
Browse files Browse the repository at this point in the history
…tener

Commit d1b65f6 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
  • Loading branch information
sbrannen committed Aug 17, 2022
1 parent 7e8d6db commit 8fe6a9f
Showing 1 changed file with 45 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
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;
Expand Down Expand Up @@ -201,36 +200,13 @@ public final List<TestExecutionListener> getTestExecutionListeners() {
* @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);
Expand All @@ -244,15 +220,8 @@ private List<TestExecutionListener> instantiateListeners(Class<? extends TestExe
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;
Expand All @@ -262,6 +231,45 @@ private List<TestExecutionListener> instantiateListeners(Class<? extends TestExe
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}
*/
Expand Down

0 comments on commit 8fe6a9f

Please sign in to comment.