Introduce ApplicationContextFailureProcessor SPI in the TCF
This commit introduces an ApplicationContextFailureProcessor SPI in the Spring TestContext Framework that allows third parties to process failures that occur while a SmartContextLoader attempts to load an ApplicationContext. SmartContextLoader implementations must introduce a try-catch block around the loading code and throw a ContextLoadException that wraps the failed ApplicationContext and the cause of the failure. Extensions of AbstractTestContextBootstrapper can configure an ApplicationContextFailureProcessor by overriding the new protected getApplicationContextFailureProcessor() method. DefaultCacheAwareContextLoaderDelegate unwraps any ContextLoadException and delegates to the configured ApplicationContextFailureProcessor for processing. Closes gh-28826
This commit is contained in:
parent
19f795a75a
commit
6bdf0bcc4a
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* Strategy for components that process failures related to application contexts
|
||||
* within the <em>Spring TestContext Framework</em>.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.0
|
||||
* @see ContextLoadException
|
||||
*/
|
||||
public interface ApplicationContextFailureProcessor {
|
||||
|
||||
/**
|
||||
* Invoked when a failure was encountered while attempting to load an
|
||||
* {@link ApplicationContext}.
|
||||
* <p>Implementations of this method must not throw any exceptions. Consequently,
|
||||
* any exception thrown by an implementation of this method will be ignored.
|
||||
* @param context the application context that did not load successfully
|
||||
* @param exception the exception caught while loading the application context
|
||||
*/
|
||||
void processLoadFailure(ApplicationContext context, Throwable exception);
|
||||
|
||||
}
|
|
@ -75,6 +75,7 @@ public interface CacheAwareContextLoaderDelegate {
|
|||
* the application context
|
||||
* @see #isContextLoaded
|
||||
* @see #closeContext
|
||||
* @see #setContextFailureProcessor
|
||||
*/
|
||||
ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration);
|
||||
|
||||
|
@ -100,4 +101,18 @@ public interface CacheAwareContextLoaderDelegate {
|
|||
*/
|
||||
void closeContext(MergedContextConfiguration mergedContextConfiguration, @Nullable HierarchyMode hierarchyMode);
|
||||
|
||||
/**
|
||||
* Set the {@link ApplicationContextFailureProcessor} to use.
|
||||
* <p>The default implementation ignores the supplied processor.
|
||||
* <p>Concrete implementations should override this method to store a reference
|
||||
* to the supplied processor and use it to process {@link ContextLoadException
|
||||
* ContextLoadExceptions} thrown from context loaders in
|
||||
* {@link #loadContext(MergedContextConfiguration)}.
|
||||
* @param contextFailureProcessor the context failure processor to use
|
||||
* @since 6.0
|
||||
*/
|
||||
default void setContextFailureProcessor(ApplicationContextFailureProcessor contextFailureProcessor) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* Exception thrown when an error occurs while a {@link SmartContextLoader}
|
||||
* attempts to load an {@link ApplicationContext}.
|
||||
*
|
||||
* <p>This exception provides access to the {@linkplain #getApplicationContext()
|
||||
* application context} that failed to load as well as the {@linkplain #getCause()
|
||||
* exception} caught while attempting to load that context.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.0
|
||||
* @see SmartContextLoader#loadContext(MergedContextConfiguration)
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ContextLoadException extends Exception {
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code ContextLoadException} for the supplied
|
||||
* {@link ApplicationContext} and {@link Exception}.
|
||||
* @param applicationContext the application context that failed to load
|
||||
* @param cause the exception caught while attempting to load that context
|
||||
*/
|
||||
public ContextLoadException(ApplicationContext applicationContext, Exception cause) {
|
||||
super(cause);
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the {@code ApplicationContext} that failed to load.
|
||||
* <p>Clients must not retain a long-lived reference to the context returned
|
||||
* from this method.
|
||||
*/
|
||||
public ApplicationContext getApplicationContext() {
|
||||
return this.applicationContext;
|
||||
}
|
||||
|
||||
}
|
|
@ -126,10 +126,23 @@ public interface SmartContextLoader extends ContextLoader {
|
|||
* closed on JVM shutdown. This allows for freeing of external resources held
|
||||
* by beans within the context — for example, temporary files.</li>
|
||||
* </ul>
|
||||
* <p>As of Spring Framework 6.0, any exception thrown while attempting to
|
||||
* load an {@code ApplicationContext} should be wrapped in a
|
||||
* {@link ContextLoadException}. Concrete implementations should therefore
|
||||
* contain a try-catch block similar to the following.
|
||||
* <pre style="code">
|
||||
* ApplicationContext context = // create context
|
||||
* try {
|
||||
* // configure and refresh context
|
||||
* }
|
||||
* catch (Exception ex) {
|
||||
* throw new ContextLoadException(context, ex);
|
||||
* }
|
||||
* </pre>
|
||||
* @param mergedConfig the merged context configuration to use to load the
|
||||
* application context
|
||||
* @return a new application context
|
||||
* @throws Exception if context loading failed
|
||||
* @throws ContextLoadException if context loading failed
|
||||
* @see #processContextConfiguration(ContextConfigurationAttributes)
|
||||
* @see #loadContextForAotProcessing(MergedContextConfiguration)
|
||||
* @see org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry)
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.springframework.test.context.aot;
|
|||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.test.context.ContextLoadException;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.context.SmartContextLoader;
|
||||
|
||||
|
@ -52,10 +53,22 @@ public interface AotContextLoader extends SmartContextLoader {
|
|||
* {@linkplain org.springframework.context.ConfigurableApplicationContext#registerShutdownHook()
|
||||
* register a JVM shutdown hook} for it. Otherwise, this method should implement
|
||||
* behavior identical to {@code loadContext(MergedContextConfiguration)}.
|
||||
* <p>Any exception thrown while attempting to load an {@code ApplicationContext}
|
||||
* should be wrapped in a {@link ContextLoadException}. Concrete implementations
|
||||
* should therefore contain a try-catch block similar to the following.
|
||||
* <pre style="code">
|
||||
* GenericApplicationContext context = // create context
|
||||
* try {
|
||||
* // configure context
|
||||
* }
|
||||
* catch (Exception ex) {
|
||||
* throw new ContextLoadException(context, ex);
|
||||
* }
|
||||
* </pre>
|
||||
* @param mergedConfig the merged context configuration to use to load the
|
||||
* application context
|
||||
* @return a new {@code GenericApplicationContext}
|
||||
* @throws Exception if context loading failed
|
||||
* @throws ContextLoadException if context loading failed
|
||||
* @see #loadContextForAotRuntime(MergedContextConfiguration, ApplicationContextInitializer)
|
||||
*/
|
||||
ApplicationContext loadContextForAotProcessing(MergedContextConfiguration mergedConfig) throws Exception;
|
||||
|
@ -67,12 +80,24 @@ public interface AotContextLoader extends SmartContextLoader {
|
|||
* <p>This method must instantiate, initialize, and
|
||||
* {@linkplain org.springframework.context.ConfigurableApplicationContext#refresh()
|
||||
* refresh} the {@code ApplicationContext}.
|
||||
* <p>Any exception thrown while attempting to load an {@code ApplicationContext}
|
||||
* should be wrapped in a {@link ContextLoadException}. Concrete implementations
|
||||
* should therefore contain a try-catch block similar to the following.
|
||||
* <pre style="code">
|
||||
* GenericApplicationContext context = // create context
|
||||
* try {
|
||||
* // configure and refresh context
|
||||
* }
|
||||
* catch (Exception ex) {
|
||||
* throw new ContextLoadException(context, ex);
|
||||
* }
|
||||
* </pre>
|
||||
* @param mergedConfig the merged context configuration to use to load the
|
||||
* application context
|
||||
* @param initializer the {@code ApplicationContextInitializer} that should
|
||||
* be applied to the context in order to recreate bean definitions
|
||||
* @return a new {@code GenericApplicationContext}
|
||||
* @throws Exception if context loading failed
|
||||
* @throws ContextLoadException if context loading failed
|
||||
* @see #loadContextForAotProcessing(MergedContextConfiguration)
|
||||
*/
|
||||
ApplicationContext loadContextForAotRuntime(MergedContextConfiguration mergedConfig,
|
||||
|
|
|
@ -39,6 +39,7 @@ import org.springframework.context.support.GenericApplicationContext;
|
|||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.test.context.BootstrapUtils;
|
||||
import org.springframework.test.context.ContextLoadException;
|
||||
import org.springframework.test.context.ContextLoader;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.context.SmartContextLoader;
|
||||
|
@ -217,9 +218,10 @@ public class TestContextAotGenerator {
|
|||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Throwable cause = (ex instanceof ContextLoadException cle ? cle.getCause() : ex);
|
||||
throw new TestContextAotException(
|
||||
"Failed to load ApplicationContext for AOT processing for test class [%s]"
|
||||
.formatted(testClass.getName()), ex);
|
||||
.formatted(testClass.getName()), cause);
|
||||
}
|
||||
}
|
||||
throw new TestContextAotException("""
|
||||
|
|
|
@ -26,7 +26,9 @@ import org.springframework.context.support.GenericApplicationContext;
|
|||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
|
||||
import org.springframework.test.context.ApplicationContextFailureProcessor;
|
||||
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
|
||||
import org.springframework.test.context.ContextLoadException;
|
||||
import org.springframework.test.context.ContextLoader;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.context.SmartContextLoader;
|
||||
|
@ -59,6 +61,9 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext
|
|||
|
||||
private final ContextCache contextCache;
|
||||
|
||||
@Nullable
|
||||
private ApplicationContextFailureProcessor contextFailureProcessor;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new {@code DefaultCacheAwareContextLoaderDelegate} using
|
||||
|
@ -110,8 +115,23 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext
|
|||
this.contextCache.put(mergedContextConfiguration, context);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Throwable cause = ex;
|
||||
if (ex instanceof ContextLoadException cle) {
|
||||
cause = cle.getCause();
|
||||
if (this.contextFailureProcessor != null) {
|
||||
try {
|
||||
this.contextFailureProcessor.processLoadFailure(cle.getApplicationContext(), cause);
|
||||
}
|
||||
catch (Throwable throwable) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Ignoring exception thrown from ApplicationContextFailureProcessor [%s]: %s"
|
||||
.formatted(this.contextFailureProcessor, throwable));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Failed to load ApplicationContext for " + mergedContextConfiguration, ex);
|
||||
"Failed to load ApplicationContext for " + mergedContextConfiguration, cause);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -134,6 +154,12 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContextFailureProcessor(ApplicationContextFailureProcessor contextFailureProcessor) {
|
||||
this.contextFailureProcessor = contextFailureProcessor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the {@link ContextCache} used by this context loader delegate.
|
||||
*/
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.springframework.context.ApplicationContextInitializer;
|
|||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigUtils;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.test.context.ContextLoadException;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.context.aot.AotContextLoader;
|
||||
import org.springframework.util.Assert;
|
||||
|
@ -159,13 +160,18 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
|
|||
validateMergedContextConfiguration(mergedConfig);
|
||||
|
||||
GenericApplicationContext context = createContext();
|
||||
prepareContext(context);
|
||||
prepareContext(context, mergedConfig);
|
||||
initializer.initialize(context);
|
||||
customizeContext(context);
|
||||
customizeContext(context, mergedConfig);
|
||||
context.refresh();
|
||||
return context;
|
||||
try {
|
||||
prepareContext(context);
|
||||
prepareContext(context, mergedConfig);
|
||||
initializer.initialize(context);
|
||||
customizeContext(context);
|
||||
customizeContext(context, mergedConfig);
|
||||
context.refresh();
|
||||
return context;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new ContextLoadException(context, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -189,25 +195,30 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
|
|||
validateMergedContextConfiguration(mergedConfig);
|
||||
|
||||
GenericApplicationContext context = createContext();
|
||||
ApplicationContext parent = mergedConfig.getParentApplicationContext();
|
||||
if (parent != null) {
|
||||
context.setParent(parent);
|
||||
try {
|
||||
ApplicationContext parent = mergedConfig.getParentApplicationContext();
|
||||
if (parent != null) {
|
||||
context.setParent(parent);
|
||||
}
|
||||
|
||||
prepareContext(context);
|
||||
prepareContext(context, mergedConfig);
|
||||
customizeBeanFactory(context.getDefaultListableBeanFactory());
|
||||
loadBeanDefinitions(context, mergedConfig);
|
||||
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
|
||||
customizeContext(context);
|
||||
customizeContext(context, mergedConfig);
|
||||
|
||||
if (!forAotProcessing) {
|
||||
context.refresh();
|
||||
context.registerShutdownHook();
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
prepareContext(context);
|
||||
prepareContext(context, mergedConfig);
|
||||
customizeBeanFactory(context.getDefaultListableBeanFactory());
|
||||
loadBeanDefinitions(context, mergedConfig);
|
||||
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
|
||||
customizeContext(context);
|
||||
customizeContext(context, mergedConfig);
|
||||
|
||||
if (!forAotProcessing) {
|
||||
context.refresh();
|
||||
context.registerShutdownHook();
|
||||
catch (Exception ex) {
|
||||
throw new ContextLoadException(context, ex);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
|||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader.FailureHandler;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.ApplicationContextFailureProcessor;
|
||||
import org.springframework.test.context.BootstrapContext;
|
||||
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
@ -494,15 +495,40 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
|
|||
/**
|
||||
* Get the {@link CacheAwareContextLoaderDelegate} to use for transparent
|
||||
* interaction with the {@code ContextCache}.
|
||||
* <p>The default implementation simply delegates to
|
||||
* {@code getBootstrapContext().getCacheAwareContextLoaderDelegate()}.
|
||||
* <p>The default implementation delegates to
|
||||
* {@code getBootstrapContext().getCacheAwareContextLoaderDelegate()} and
|
||||
* supplies the returned delegate the configured
|
||||
* {@link #getApplicationContextFailureProcessor() ApplicationContextFailureProcessor}.
|
||||
* <p>Concrete subclasses may choose to override this method to return a custom
|
||||
* {@code CacheAwareContextLoaderDelegate} implementation with custom
|
||||
* {@link org.springframework.test.context.cache.ContextCache ContextCache} support.
|
||||
* @return the context loader delegate (never {@code null})
|
||||
* @see #getApplicationContextFailureProcessor()
|
||||
*/
|
||||
protected CacheAwareContextLoaderDelegate getCacheAwareContextLoaderDelegate() {
|
||||
return getBootstrapContext().getCacheAwareContextLoaderDelegate();
|
||||
CacheAwareContextLoaderDelegate delegate = getBootstrapContext().getCacheAwareContextLoaderDelegate();
|
||||
ApplicationContextFailureProcessor contextFailureProcessor = getApplicationContextFailureProcessor();
|
||||
if (contextFailureProcessor != null) {
|
||||
delegate.setContextFailureProcessor(contextFailureProcessor);
|
||||
}
|
||||
return delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ApplicationContextFailureProcessor} to use.
|
||||
* <p>The default implementation returns {@code null}.
|
||||
* <p>Concrete subclasses may choose to override this method to provide an
|
||||
* {@code ApplicationContextFailureProcessor} that will be supplied to the
|
||||
* configured {@code CacheAwareContextLoaderDelegate} in
|
||||
* {@link #getCacheAwareContextLoaderDelegate()}.
|
||||
* @return the context failure processor to use, or {@code null} if no processor
|
||||
* should be used
|
||||
* @since 6.0
|
||||
* @see #getCacheAwareContextLoaderDelegate()
|
||||
*/
|
||||
@Nullable
|
||||
protected ApplicationContextFailureProcessor getApplicationContextFailureProcessor() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.springframework.core.io.DefaultResourceLoader;
|
|||
import org.springframework.core.io.FileSystemResourceLoader;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.test.context.ContextLoadException;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.context.aot.AotContextLoader;
|
||||
import org.springframework.test.context.support.AbstractContextLoader;
|
||||
|
@ -156,12 +157,17 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
|
|||
validateMergedContextConfiguration(webMergedConfig);
|
||||
|
||||
GenericWebApplicationContext context = createContext();
|
||||
configureWebResources(context, webMergedConfig);
|
||||
prepareContext(context, webMergedConfig);
|
||||
initializer.initialize(context);
|
||||
customizeContext(context, webMergedConfig);
|
||||
context.refresh();
|
||||
return context;
|
||||
try {
|
||||
configureWebResources(context, webMergedConfig);
|
||||
prepareContext(context, webMergedConfig);
|
||||
initializer.initialize(context);
|
||||
customizeContext(context, webMergedConfig);
|
||||
context.refresh();
|
||||
return context;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new ContextLoadException(context, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -194,24 +200,28 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
|
|||
validateMergedContextConfiguration(webMergedConfig);
|
||||
|
||||
GenericWebApplicationContext context = createContext();
|
||||
try {
|
||||
ApplicationContext parent = mergedConfig.getParentApplicationContext();
|
||||
if (parent != null) {
|
||||
context.setParent(parent);
|
||||
}
|
||||
configureWebResources(context, webMergedConfig);
|
||||
prepareContext(context, webMergedConfig);
|
||||
customizeBeanFactory(context.getDefaultListableBeanFactory(), webMergedConfig);
|
||||
loadBeanDefinitions(context, webMergedConfig);
|
||||
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
|
||||
customizeContext(context, webMergedConfig);
|
||||
|
||||
ApplicationContext parent = mergedConfig.getParentApplicationContext();
|
||||
if (parent != null) {
|
||||
context.setParent(parent);
|
||||
if (!forAotProcessing) {
|
||||
context.refresh();
|
||||
context.registerShutdownHook();
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
configureWebResources(context, webMergedConfig);
|
||||
prepareContext(context, webMergedConfig);
|
||||
customizeBeanFactory(context.getDefaultListableBeanFactory(), webMergedConfig);
|
||||
loadBeanDefinitions(context, webMergedConfig);
|
||||
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
|
||||
customizeContext(context, webMergedConfig);
|
||||
|
||||
if (!forAotProcessing) {
|
||||
context.refresh();
|
||||
context.registerShutdownHook();
|
||||
catch (Exception ex) {
|
||||
throw new ContextLoadException(context, ex);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.failures;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.platform.testkit.engine.EngineTestKit;
|
||||
|
||||
import org.springframework.beans.BeanInstantiationException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.test.context.ApplicationContextFailureProcessor;
|
||||
import org.springframework.test.context.BootstrapWith;
|
||||
import org.springframework.test.context.junit.jupiter.FailingTestCase;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.context.support.DefaultTestContextBootstrapper;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
|
||||
|
||||
/**
|
||||
* Tests for failures that occur while loading an {@link ApplicationContext}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.0
|
||||
*/
|
||||
class ContextLoadFailureTests {
|
||||
|
||||
static List<LoadFailure> loadFailures = new ArrayList<>();
|
||||
|
||||
|
||||
@BeforeEach
|
||||
@AfterEach
|
||||
void clearFailures() {
|
||||
loadFailures.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void customBootstrapperAppliesApplicationContextFailureProcessor() {
|
||||
assertThat(loadFailures).isEmpty();
|
||||
|
||||
EngineTestKit.engine("junit-jupiter")
|
||||
.selectors(selectClass(ExplosiveContextTestCase.class))//
|
||||
.execute()
|
||||
.testEvents()
|
||||
.assertStatistics(stats -> stats.started(1).succeeded(0).failed(1));
|
||||
|
||||
assertThat(loadFailures).hasSize(1);
|
||||
LoadFailure loadFailure = loadFailures.get(0);
|
||||
assertThat(loadFailure.context()).isExactlyInstanceOf(GenericApplicationContext.class);
|
||||
assertThat(loadFailure.exception())
|
||||
.isInstanceOf(BeanCreationException.class)
|
||||
.cause().isInstanceOf(BeanInstantiationException.class)
|
||||
.rootCause().isInstanceOf(StackOverflowError.class).hasMessage("Boom!");
|
||||
}
|
||||
|
||||
|
||||
@FailingTestCase
|
||||
@SpringJUnitConfig
|
||||
@BootstrapWith(CustomTestContextBootstrapper.class)
|
||||
static class ExplosiveContextTestCase {
|
||||
|
||||
@Test
|
||||
void test1() {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
String explosion() {
|
||||
throw new StackOverflowError("Boom!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class CustomTestContextBootstrapper extends DefaultTestContextBootstrapper {
|
||||
|
||||
@Override
|
||||
protected ApplicationContextFailureProcessor getApplicationContextFailureProcessor() {
|
||||
return (context, exception) -> loadFailures.add(new LoadFailure(context, exception));
|
||||
}
|
||||
}
|
||||
|
||||
record LoadFailure(ApplicationContext context, Throwable exception) {}
|
||||
|
||||
}
|
Loading…
Reference in New Issue