From 8fe6a9f5374a0ed56ba4e1e9b2287d581009719e Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 17 Aug 2022 11:25:48 +0200 Subject: [PATCH] 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 --- .../AbstractTestContextBootstrapper.java | 82 ++++++++++--------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java index 52a11a3ae600..ec7c183ad56a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java @@ -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; @@ -201,36 +200,13 @@ public final List getTestExecutionListeners() { * @see SpringFactoriesLoader#load(Class, FailureHandler) */ protected List 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 listeners = SpringFactoriesLoader.forDefaultResourceLocation() - .load(TestExecutionListener.class, failureHandler); + .load(TestExecutionListener.class, this::handleListenerInstantiationFailure); if (logger.isInfoEnabled()) { - List 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 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 @@ private List instantiateListeners(Class instantiateListeners(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} */