Skip to content

Commit 8fe6a9f

Browse files
committed
Handle NoClassDefFoundError consistently for skipped TestExecutionListener
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
1 parent 7e8d6db commit 8fe6a9f

File tree

1 file changed

+45
-37
lines changed

1 file changed

+45
-37
lines changed

spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import java.util.List;
2626
import java.util.Map;
2727
import java.util.Set;
28-
import java.util.stream.Collectors;
2928

3029
import org.apache.commons.logging.Log;
3130
import org.apache.commons.logging.LogFactory;
@@ -201,36 +200,13 @@ public final List<TestExecutionListener> getTestExecutionListeners() {
201200
* @see SpringFactoriesLoader#load(Class, FailureHandler)
202201
*/
203202
protected List<TestExecutionListener> getDefaultTestExecutionListeners() {
204-
FailureHandler failureHandler = (factoryType, factoryImplementationName, failure) -> {
205-
Throwable ex = (failure instanceof InvocationTargetException ite ?
206-
ite.getTargetException() : failure);
207-
if (ex instanceof LinkageError || ex instanceof ClassNotFoundException) {
208-
if (logger.isDebugEnabled()) {
209-
logger.debug("Could not load default TestExecutionListener [" + factoryImplementationName +
210-
"]. Specify custom listener classes or make the default listener classes available.", ex);
211-
}
212-
}
213-
else {
214-
if (ex instanceof RuntimeException runtimeException) {
215-
throw runtimeException;
216-
}
217-
if (ex instanceof Error error) {
218-
throw error;
219-
}
220-
throw new IllegalStateException("Failed to load default TestExecutionListener [" +
221-
factoryImplementationName + "].", ex);
222-
}
223-
};
224-
225203
List<TestExecutionListener> listeners = SpringFactoriesLoader.forDefaultResourceLocation()
226-
.load(TestExecutionListener.class, failureHandler);
204+
.load(TestExecutionListener.class, this::handleListenerInstantiationFailure);
227205

228206
if (logger.isInfoEnabled()) {
229-
List<String> classNames = listeners.stream()
230-
.map(listener -> listener.getClass().getName())
231-
.collect(Collectors.toList());
232-
logger.info(String.format("Loaded default TestExecutionListener implementations from location [%s]: %s",
233-
SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames));
207+
List<String> classNames = listeners.stream().map(Object::getClass).map(Class::getName).toList();
208+
logger.info("Loaded default TestExecutionListener implementations from location [%s]: %s"
209+
.formatted(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames));
234210
}
235211

236212
return Collections.unmodifiableList(listeners);
@@ -244,15 +220,8 @@ private List<TestExecutionListener> instantiateListeners(Class<? extends TestExe
244220
listeners.add(BeanUtils.instantiateClass(listenerClass));
245221
}
246222
catch (BeanInstantiationException ex) {
247-
if (ex.getCause() instanceof NoClassDefFoundError) {
248-
// TestExecutionListener not applicable due to a missing dependency
249-
if (logger.isDebugEnabled()) {
250-
logger.debug(String.format(
251-
"Skipping candidate TestExecutionListener [%s] due to a missing dependency. " +
252-
"Specify custom listener classes or make the default listener classes " +
253-
"and their required dependencies available. Offending class: [%s]",
254-
listenerClass.getName(), ex.getCause().getMessage()));
255-
}
223+
if (ex.getCause() instanceof NoClassDefFoundError noClassDefFoundError) {
224+
handleNoClassDefFoundError(listenerClass.getName(), noClassDefFoundError);
256225
}
257226
else {
258227
throw ex;
@@ -262,6 +231,45 @@ private List<TestExecutionListener> instantiateListeners(Class<? extends TestExe
262231
return listeners;
263232
}
264233

234+
private void handleListenerInstantiationFailure(
235+
Class<?> factoryType, String listenerClassName, Throwable failure) {
236+
237+
Throwable ex = (failure instanceof InvocationTargetException ite ?
238+
ite.getTargetException() : failure);
239+
if (ex instanceof LinkageError || ex instanceof ClassNotFoundException) {
240+
if (ex instanceof NoClassDefFoundError noClassDefFoundError) {
241+
handleNoClassDefFoundError(listenerClassName, noClassDefFoundError);
242+
}
243+
else if (logger.isDebugEnabled()) {
244+
logger.debug("""
245+
Could not load default TestExecutionListener [%s]. Specify custom \
246+
listener classes or make the default listener classes available."""
247+
.formatted(listenerClassName), ex);
248+
}
249+
}
250+
else {
251+
if (ex instanceof RuntimeException runtimeException) {
252+
throw runtimeException;
253+
}
254+
if (ex instanceof Error error) {
255+
throw error;
256+
}
257+
throw new IllegalStateException(
258+
"Failed to load default TestExecutionListener [%s].".formatted(listenerClassName), ex);
259+
}
260+
}
261+
262+
private void handleNoClassDefFoundError(String listenerClassName, NoClassDefFoundError error) {
263+
// TestExecutionListener not applicable due to a missing dependency
264+
if (logger.isDebugEnabled()) {
265+
logger.debug("""
266+
Skipping candidate TestExecutionListener [%s] due to a missing dependency. \
267+
Specify custom listener classes or make the default listener classes \
268+
and their required dependencies available. Offending class: [%s]"""
269+
.formatted(listenerClassName, error.getMessage()));
270+
}
271+
}
272+
265273
/**
266274
* {@inheritDoc}
267275
*/

0 commit comments

Comments
 (0)