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:
parent
7e8d6dbd45
commit
8fe6a9f537
|
|
@ -25,7 +25,6 @@ import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
@ -201,36 +200,13 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
|
||||||
* @see SpringFactoriesLoader#load(Class, FailureHandler)
|
* @see SpringFactoriesLoader#load(Class, FailureHandler)
|
||||||
*/
|
*/
|
||||||
protected List<TestExecutionListener> getDefaultTestExecutionListeners() {
|
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()
|
List<TestExecutionListener> listeners = SpringFactoriesLoader.forDefaultResourceLocation()
|
||||||
.load(TestExecutionListener.class, failureHandler);
|
.load(TestExecutionListener.class, this::handleListenerInstantiationFailure);
|
||||||
|
|
||||||
if (logger.isInfoEnabled()) {
|
if (logger.isInfoEnabled()) {
|
||||||
List<String> classNames = listeners.stream()
|
List<String> classNames = listeners.stream().map(Object::getClass).map(Class::getName).toList();
|
||||||
.map(listener -> listener.getClass().getName())
|
logger.info("Loaded default TestExecutionListener implementations from location [%s]: %s"
|
||||||
.collect(Collectors.toList());
|
.formatted(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames));
|
||||||
logger.info(String.format("Loaded default TestExecutionListener implementations from location [%s]: %s",
|
|
||||||
SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.unmodifiableList(listeners);
|
return Collections.unmodifiableList(listeners);
|
||||||
|
|
@ -244,15 +220,8 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
|
||||||
listeners.add(BeanUtils.instantiateClass(listenerClass));
|
listeners.add(BeanUtils.instantiateClass(listenerClass));
|
||||||
}
|
}
|
||||||
catch (BeanInstantiationException ex) {
|
catch (BeanInstantiationException ex) {
|
||||||
if (ex.getCause() instanceof NoClassDefFoundError) {
|
if (ex.getCause() instanceof NoClassDefFoundError noClassDefFoundError) {
|
||||||
// TestExecutionListener not applicable due to a missing dependency
|
handleNoClassDefFoundError(listenerClass.getName(), noClassDefFoundError);
|
||||||
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()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw ex;
|
throw ex;
|
||||||
|
|
@ -262,6 +231,45 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
|
||||||
return listeners;
|
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}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue