Skip to content

Commit

Permalink
Skip ContextCustomizerFactory that cannot be loaded
Browse files Browse the repository at this point in the history
With this commit, if a ContextCustomizerFactory cannot be loaded due to
a ClassNotFoundException or NoClassDefFoundError, we skip it and log a
DEBUG message.

This aligns with the "skipping" logic already in place for
TestExecutionListeners.

Closes gh-29034
  • Loading branch information
sbrannen committed Aug 28, 2022
1 parent ab20a18 commit 92b5827
Showing 1 changed file with 45 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,9 @@ public final List<TestExecutionListener> getTestExecutionListeners() {
* @see SpringFactoriesLoader#load(Class, FailureHandler)
*/
protected List<TestExecutionListener> getDefaultTestExecutionListeners() {
List<TestExecutionListener> listeners = SpringFactoriesLoader.forDefaultResourceLocation()
.load(TestExecutionListener.class, this::handleListenerInstantiationFailure);
SpringFactoriesLoader loader = SpringFactoriesLoader.forDefaultResourceLocation(getClass().getClassLoader());
List<TestExecutionListener> listeners =
loader.load(TestExecutionListener.class, this::handleInstantiationFailure);
if (logger.isDebugEnabled()) {
logger.debug("Loaded default TestExecutionListener implementations from location [%s]: %s"
.formatted(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames(listeners)));
Expand All @@ -219,7 +220,7 @@ private List<TestExecutionListener> instantiateListeners(Class<? extends TestExe
catch (BeanInstantiationException ex) {
Throwable cause = ex.getCause();
if (cause instanceof ClassNotFoundException || cause instanceof NoClassDefFoundError) {
logSkippedListener(listenerClass.getName(), cause);
logSkippedComponent(TestExecutionListener.class, listenerClass.getName(), cause);
}
else {
throw ex;
Expand All @@ -229,46 +230,6 @@ 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 ClassNotFoundException || ex instanceof NoClassDefFoundError) {
logSkippedListener(listenerClassName, ex);
}
else if (ex instanceof LinkageError) {
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 logSkippedListener(String listenerClassName, Throwable ex) {
// 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, ex.getMessage()));
}
}

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -440,8 +401,9 @@ private Set<ContextCustomizer> getContextCustomizers(Class<?> testClass,
* @see SpringFactoriesLoader#loadFactories
*/
protected List<ContextCustomizerFactory> getContextCustomizerFactories() {
SpringFactoriesLoader loader = SpringFactoriesLoader.forDefaultResourceLocation(getClass().getClassLoader());
List<ContextCustomizerFactory> factories =
SpringFactoriesLoader.loadFactories(ContextCustomizerFactory.class, getClass().getClassLoader());
loader.load(ContextCustomizerFactory.class, this::handleInstantiationFailure);
if (logger.isDebugEnabled()) {
logger.debug("Loaded ContextCustomizerFactory implementations from location [%s]: %s"
.formatted(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames(factories)));
Expand Down Expand Up @@ -570,6 +532,45 @@ protected MergedContextConfiguration processMergedContextConfiguration(MergedCon
}


private void handleInstantiationFailure(
Class<?> factoryType, String factoryImplementationName, Throwable failure) {

Throwable ex = (failure instanceof InvocationTargetException ite ?
ite.getTargetException() : failure);
if (ex instanceof ClassNotFoundException || ex instanceof NoClassDefFoundError) {
logSkippedComponent(factoryType, factoryImplementationName, ex);
}
else if (ex instanceof LinkageError) {
if (logger.isDebugEnabled()) {
logger.debug("""
Could not load %1$s [%2$s]. Specify custom %1$s classes or make the default %1$s classes \
available.""".formatted(factoryType.getSimpleName(), factoryImplementationName), ex);
}
}
else {
if (ex instanceof RuntimeException runtimeException) {
throw runtimeException;
}
if (ex instanceof Error error) {
throw error;
}
throw new IllegalStateException(
"Failed to load %s [%s].".formatted(factoryType.getSimpleName(), factoryImplementationName), ex);
}
}

private void logSkippedComponent(Class<?> factoryType, String factoryImplementationName, Throwable ex) {
// TestExecutionListener/ContextCustomizerFactory not applicable due to a missing dependency
if (logger.isDebugEnabled()) {
logger.debug("""
Skipping candidate %1$s [%2$s] due to a missing dependency. \
Specify custom %1$s classes or make the default %1$s classes \
and their required dependencies available. Offending class: [%3$s]"""
.formatted(factoryType.getSimpleName(), factoryImplementationName, ex.getMessage()));
}
}


private static List<String> classNames(List<?> components) {
return components.stream().map(Object::getClass).map(Class::getName).toList();
}
Expand Down

0 comments on commit 92b5827

Please sign in to comment.