From c7ca5c81c319f9676436f96becdb72307ea3e1a5 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 8 Jun 2023 15:41:32 +0200 Subject: [PATCH 1/5] Avoid repeated attempts to load failing ApplicationContext in the TCF This commit introduces initial support for a new "context failure threshold" feature in the Spring TestContext Framework (TCF). Specifically, DefaultCacheAwareContextLoaderDelegate now tracks how many times a failure occurs when attempting to load an ApplicationContext and preemptively throws an IllegalStateException for subsequent attempts to load the same context if the configured failure threshold has been exceeded. See gh-14182 --- ...efaultCacheAwareContextLoaderDelegate.java | 30 +++++++ .../cache/ContextFailureThresholdTests.java | 80 +++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 spring-test/src/test/java/org/springframework/test/context/cache/ContextFailureThresholdTests.java diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java index c985de20d4b..d5b3426476b 100644 --- a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java +++ b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java @@ -16,7 +16,9 @@ package org.springframework.test.context.cache; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -60,6 +62,12 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext private static final Log logger = LogFactory.getLog(DefaultCacheAwareContextLoaderDelegate.class); + /** + * The default failure threshold for errors encountered while attempting to + * load an {@link ApplicationContext}: {@value}. + * @since 6.1 + */ + private static final int DEFAULT_FAILURE_THRESHOLD = 1; /** * Default static cache of Spring application contexts. @@ -74,6 +82,19 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext private final ContextCache contextCache; + /** + * Map of context keys to context load failure counts. + * @since 6.1 + */ + private final Map failureCounts = new HashMap<>(32); + + /** + * The configured failure threshold for errors encountered while attempting to + * load an {@link ApplicationContext}. + * @since 6.1 + */ + private final int failureThreshold; + /** * Construct a new {@code DefaultCacheAwareContextLoaderDelegate} using @@ -95,6 +116,7 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext public DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache) { Assert.notNull(contextCache, "ContextCache must not be null"); this.contextCache = contextCache; + this.failureThreshold = DEFAULT_FAILURE_THRESHOLD; } @@ -112,6 +134,13 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext synchronized (this.contextCache) { ApplicationContext context = this.contextCache.get(mergedConfig); if (context == null) { + Integer failureCount = this.failureCounts.getOrDefault(mergedConfig, 0); + if (failureCount >= this.failureThreshold) { + throw new IllegalStateException(""" + ApplicationContext failure threshold (%d) exceeded: \ + skipping repeated attempt to load context for %s""" + .formatted(this.failureThreshold, mergedConfig)); + } try { if (mergedConfig instanceof AotMergedContextConfiguration aotMergedConfig) { context = loadContextInAotMode(aotMergedConfig); @@ -126,6 +155,7 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext this.contextCache.put(mergedConfig, context); } catch (Exception ex) { + this.failureCounts.put(mergedConfig, ++failureCount); Throwable cause = ex; if (ex instanceof ContextLoadException cle) { cause = cle.getCause(); diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ContextFailureThresholdTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ContextFailureThresholdTests.java new file mode 100644 index 00000000000..72f778d1ee4 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/cache/ContextFailureThresholdTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2023 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.cache; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.springframework.test.context.cache.ContextCacheTestUtils.assertContextCacheStatistics; +import static org.springframework.test.context.cache.ContextCacheTestUtils.resetContextCache; + +/** + * @author Sam Brannen + * @since 6.1 + */ +@SpringJUnitConfig +@TestMethodOrder(OrderAnnotation.class) +@Disabled +class ContextFailureThresholdTests { + + @BeforeAll + static void verifyInitialCacheState() { + resetContextCache(); + assertContextCacheStatistics("BeforeAll", 0, 0, 0); + } + + @AfterAll + static void verifyFinalCacheState() { + assertContextCacheStatistics("AfterAll", 0, 0, 3); + resetContextCache(); + } + + @Test + @Order(1) + void test1() { + } + + @Test + @Order(2) + void test2() { + } + + @Test + @Order(3) + void test3() { + } + + + @Configuration + static class FailingConfig { + + @Bean + String explosiveString() { + throw new RuntimeException("Boom!"); + } + } + +} From 52ae97cffc6bd31466afcf48d36c7cf90e72489f Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 8 Jun 2023 18:23:13 +0200 Subject: [PATCH 2/5] Make test context failure threshold configurable This commit makes the failure threshold value configurable via a JVM system property or Spring property named "spring.test.context.failure.threshold". See gh-14182 --- .../CacheAwareContextLoaderDelegate.java | 33 +++++++++++++++ .../test/context/cache/ContextCacheUtils.java | 40 +++++++++++++++---- ...efaultCacheAwareContextLoaderDelegate.java | 17 ++++---- 3 files changed, 74 insertions(+), 16 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java b/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java index 03fc7eabc55..fc2a4cc8326 100644 --- a/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java +++ b/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java @@ -36,6 +36,31 @@ import org.springframework.test.annotation.DirtiesContext.HierarchyMode; */ public interface CacheAwareContextLoaderDelegate { + /** + * The default failure threshold for errors encountered while attempting to + * load an application context: {@value}. + * @since 6.1 + * @see #CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME + */ + int DEFAULT_CONTEXT_FAILURE_THRESHOLD = 1; + + /** + * System property used to configure the failure threshold for errors + * encountered while attempting to load an application context: {@value}. + *

May alternatively be configured via the + * {@link org.springframework.core.SpringProperties} mechanism. + *

Implementations of {@code CacheAwareContextLoaderDelegate} are not + * required to support this feature. Consult the documentation of the + * corresponding implementation for details. Note, however, that the standard + * {@code CacheAwareContextLoaderDelegate} implementation in Spring supports + * this feature. + * @since 6.1 + * @see #DEFAULT_CONTEXT_FAILURE_THRESHOLD + * @see #loadContext(MergedContextConfiguration) + */ + String CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME = "spring.test.context.failure.threshold"; + + /** * Determine if the {@linkplain ApplicationContext application context} for * the supplied {@link MergedContextConfiguration} has been loaded (i.e., @@ -72,6 +97,13 @@ public interface CacheAwareContextLoaderDelegate { * mechanism, catch any exception thrown by the {@link ContextLoader}, and * delegate to each of the configured failure processors to process the context * load failure if the exception is an instance of {@link ContextLoadException}. + *

As of Spring Framework 6.1, implementations of this method are encouraged + * to support the failure threshold feature. Specifically, if repeated + * attempts are made to load an application context and that application + * context consistently fails to load — for example, due to a configuration + * error that prevents the context from successfully loading — this + * method should preemptively throw an {@link IllegalStateException} if the + * configured failure threshold has been exceeded. *

The cache statistics should be logged by invoking * {@link org.springframework.test.context.cache.ContextCache#logStatistics()}. * @param mergedConfig the merged context configuration to use to load the @@ -81,6 +113,7 @@ public interface CacheAwareContextLoaderDelegate { * the application context * @see #isContextLoaded * @see #closeContext + * @see #CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME */ ApplicationContext loadContext(MergedContextConfiguration mergedConfig); diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java index b89f4774a7d..659361d017d 100644 --- a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2023 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. @@ -17,10 +17,11 @@ package org.springframework.test.context.cache; import org.springframework.core.SpringProperties; +import org.springframework.test.context.CacheAwareContextLoaderDelegate; import org.springframework.util.StringUtils; /** - * Collection of utilities for working with {@link ContextCache ContextCaches}. + * Collection of utilities for working with context caching. * * @author Sam Brannen * @since 4.3 @@ -30,17 +31,40 @@ public abstract class ContextCacheUtils { /** * Retrieve the maximum size of the {@link ContextCache}. *

Uses {@link SpringProperties} to retrieve a system property or Spring - * property named {@code spring.test.context.cache.maxSize}. - *

Falls back to the value of the {@link ContextCache#DEFAULT_MAX_CONTEXT_CACHE_SIZE} + * property named {@value ContextCache#MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME}. + *

Defaults to {@value ContextCache#DEFAULT_MAX_CONTEXT_CACHE_SIZE} * if no such property has been set or if the property is not an integer. * @return the maximum size of the context cache * @see ContextCache#MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME */ public static int retrieveMaxCacheSize() { + String propertyName = ContextCache.MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME; + int defaultValue = ContextCache.DEFAULT_MAX_CONTEXT_CACHE_SIZE; + return retrieveProperty(propertyName, defaultValue); + } + + /** + * Retrieve the failure threshold for application context loading. + *

Uses {@link SpringProperties} to retrieve a system property or Spring + * property named {@value CacheAwareContextLoaderDelegate#CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME}. + *

Defaults to {@value CacheAwareContextLoaderDelegate#DEFAULT_CONTEXT_FAILURE_THRESHOLD} + * if no such property has been set or if the property is not an integer. + * @return the failure threshold + * @since 6.1 + * @see CacheAwareContextLoaderDelegate#CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME + * @see CacheAwareContextLoaderDelegate#DEFAULT_CONTEXT_FAILURE_THRESHOLD + */ + public static int retrieveContextFailureThreshold() { + String propertyName = CacheAwareContextLoaderDelegate.CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME; + int defaultValue = CacheAwareContextLoaderDelegate.DEFAULT_CONTEXT_FAILURE_THRESHOLD; + return retrieveProperty(propertyName, defaultValue); + } + + private static int retrieveProperty(String key, int defaultValue) { try { - String maxSize = SpringProperties.getProperty(ContextCache.MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME); - if (StringUtils.hasText(maxSize)) { - return Integer.parseInt(maxSize.trim()); + String value = SpringProperties.getProperty(key); + if (StringUtils.hasText(value)) { + return Integer.parseInt(value.trim()); } } catch (Exception ex) { @@ -48,7 +72,7 @@ public abstract class ContextCacheUtils { } // Fallback - return ContextCache.DEFAULT_MAX_CONTEXT_CACHE_SIZE; + return defaultValue; } } diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java index d5b3426476b..722b0890a0c 100644 --- a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java +++ b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java @@ -62,13 +62,6 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext private static final Log logger = LogFactory.getLog(DefaultCacheAwareContextLoaderDelegate.class); - /** - * The default failure threshold for errors encountered while attempting to - * load an {@link ApplicationContext}: {@value}. - * @since 6.1 - */ - private static final int DEFAULT_FAILURE_THRESHOLD = 1; - /** * Default static cache of Spring application contexts. */ @@ -114,9 +107,17 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext * @see #DefaultCacheAwareContextLoaderDelegate() */ public DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache) { + this(contextCache, ContextCacheUtils.retrieveContextFailureThreshold()); + } + + /** + * @since 6.1 + */ + DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache, int failureThreshold) { Assert.notNull(contextCache, "ContextCache must not be null"); + Assert.isTrue(failureThreshold > 0, "'failureThreshold' must be positive"); this.contextCache = contextCache; - this.failureThreshold = DEFAULT_FAILURE_THRESHOLD; + this.failureThreshold = failureThreshold; } From 56f3bd86cf35a1f1a48a846c06c09e633b22ed51 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 9 Jun 2023 14:39:41 +0200 Subject: [PATCH 3/5] Add integration tests for context failure threshold support See gh-14182 --- .../cache/ContextFailureThresholdTests.java | 144 +++++++++++++++--- 1 file changed, 119 insertions(+), 25 deletions(-) diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ContextFailureThresholdTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ContextFailureThresholdTests.java index 72f778d1ee4..a240637e16c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/cache/ContextFailureThresholdTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/cache/ContextFailureThresholdTests.java @@ -16,64 +16,158 @@ package org.springframework.test.context.cache; +import java.util.concurrent.atomic.AtomicInteger; + import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; +import org.junit.platform.testkit.engine.EngineTestKit; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.SpringProperties; +import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.springframework.test.context.CacheAwareContextLoaderDelegate.CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME; +import static org.springframework.test.context.CacheAwareContextLoaderDelegate.DEFAULT_CONTEXT_FAILURE_THRESHOLD; import static org.springframework.test.context.cache.ContextCacheTestUtils.assertContextCacheStatistics; import static org.springframework.test.context.cache.ContextCacheTestUtils.resetContextCache; /** + * Integration tests for context failure threshold support. + * * @author Sam Brannen * @since 6.1 */ -@SpringJUnitConfig -@TestMethodOrder(OrderAnnotation.class) -@Disabled class ContextFailureThresholdTests { - @BeforeAll - static void verifyInitialCacheState() { - resetContextCache(); - assertContextCacheStatistics("BeforeAll", 0, 0, 0); - } + private static final AtomicInteger loadCount = new AtomicInteger(0); + - @AfterAll - static void verifyFinalCacheState() { - assertContextCacheStatistics("AfterAll", 0, 0, 3); - resetContextCache(); + @BeforeEach + @AfterEach + void resetFlag() { + loadCount.set(0); + SpringProperties.setProperty(CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME, null); } @Test - @Order(1) - void test1() { + void defaultThreshold() { + assertThat(loadCount.get()).isZero(); + + EngineTestKit.engine("junit-jupiter")// + .selectors(selectClass(PassingTestCase.class))// 2 passing + .selectors(selectClass(FailingTestCase.class))// 3 failing + .execute()// + .testEvents()// + .assertStatistics(stats -> stats.started(5).succeeded(2).failed(3)); + assertThat(loadCount.get()).isEqualTo(DEFAULT_CONTEXT_FAILURE_THRESHOLD); } @Test - @Order(2) - void test2() { + void customThreshold() { + assertThat(loadCount.get()).isZero(); + + int threshold = 2; + SpringProperties.setProperty(CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME, Integer.toString(threshold)); + + EngineTestKit.engine("junit-jupiter")// + .selectors(selectClass(PassingTestCase.class))// 2 passing + .selectors(selectClass(FailingTestCase.class))// 3 failing + .execute()// + .testEvents()// + .assertStatistics(stats -> stats.started(5).succeeded(2).failed(3)); + assertThat(loadCount.get()).isEqualTo(threshold); } @Test - @Order(3) - void test3() { + void thresholdEffectivelyDisabled() { + assertThat(loadCount.get()).isZero(); + + SpringProperties.setProperty(CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME, "999999"); + + EngineTestKit.engine("junit-jupiter")// + .selectors(selectClass(PassingTestCase.class))// 2 passing + .selectors(selectClass(FailingTestCase.class))// 3 failing + .execute()// + .testEvents()// + .assertStatistics(stats -> stats.started(5).succeeded(2).failed(3)); + assertThat(loadCount.get()).isEqualTo(3); + } + + + @SpringJUnitConfig + @TestExecutionListeners(DependencyInjectionTestExecutionListener.class) + static class PassingTestCase { + + @BeforeAll + static void verifyInitialCacheState() { + resetContextCache(); + assertContextCacheStatistics("BeforeAll", 0, 0, 0); + } + + @AfterAll + static void verifyFinalCacheState() { + assertContextCacheStatistics("AfterAll", 1, 1, 1); + resetContextCache(); + } + + @Test + void test1() {} + + @Test + void test2() {} + + @Configuration + static class PassingConfig { + } } + @SpringJUnitConfig + @TestExecutionListeners(DependencyInjectionTestExecutionListener.class) + @TestMethodOrder(OrderAnnotation.class) + static class FailingTestCase { + + @BeforeAll + static void verifyInitialCacheState() { + resetContextCache(); + assertContextCacheStatistics("BeforeAll", 0, 0, 0); + } + + @AfterAll + static void verifyFinalCacheState() { + assertContextCacheStatistics("AfterAll", 0, 0, 3); + resetContextCache(); + } + + @Test + void test1() {} + + @Test + void test2() {} + + @Test + void test3() {} + + @Configuration + static class FailingConfig { - @Configuration - static class FailingConfig { + FailingConfig() { + loadCount.incrementAndGet(); + } - @Bean - String explosiveString() { - throw new RuntimeException("Boom!"); + @Bean + String explosiveString() { + throw new RuntimeException("Boom!"); + } } } From c8bb7ded5d4ac4aa8355c49694464284a115ed7d Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 9 Jun 2023 14:50:54 +0200 Subject: [PATCH 4/5] Improve Javadoc for DefaultCacheAwareContextLoaderDelegate See gh-14182 --- ...efaultCacheAwareContextLoaderDelegate.java | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java index 722b0890a0c..3db2cb62599 100644 --- a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java +++ b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java @@ -43,7 +43,7 @@ import org.springframework.test.context.util.TestContextSpringFactoriesUtils; import org.springframework.util.Assert; /** - * Default implementation of the {@link CacheAwareContextLoaderDelegate} interface. + * Default implementation of the {@link CacheAwareContextLoaderDelegate} strategy. * *

To use a static {@link DefaultContextCache}, invoke the * {@link #DefaultCacheAwareContextLoaderDelegate()} constructor; otherwise, @@ -55,6 +55,11 @@ import org.springframework.util.Assert; * SpringFactoriesLoader} mechanism and delegates to them in * {@link #loadContext(MergedContextConfiguration)} to process context load failures. * + *

As of Spring Framework 6.1, this class supports the failure threshold + * feature described in {@link CacheAwareContextLoaderDelegate#loadContext}, + * delegating to {@link ContextCacheUtils#retrieveContextFailureThreshold()} to + * obtain the threshold value to use. + * * @author Sam Brannen * @since 4.1 */ @@ -90,11 +95,11 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext /** - * Construct a new {@code DefaultCacheAwareContextLoaderDelegate} using - * a static {@link DefaultContextCache}. - *

This default cache is static so that each context can be cached - * and reused for all subsequent tests that declare the same unique - * context configuration within the same JVM process. + * Construct a new {@code DefaultCacheAwareContextLoaderDelegate} using a + * static {@link DefaultContextCache}. + *

The default cache is static so that each context can be cached and + * reused for all subsequent tests that declare the same unique context + * configuration within the same JVM process. * @see #DefaultCacheAwareContextLoaderDelegate(ContextCache) */ public DefaultCacheAwareContextLoaderDelegate() { @@ -102,18 +107,22 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext } /** - * Construct a new {@code DefaultCacheAwareContextLoaderDelegate} using - * the supplied {@link ContextCache}. + * Construct a new {@code DefaultCacheAwareContextLoaderDelegate} using the + * supplied {@link ContextCache} and the default or user-configured context + * failure threshold. * @see #DefaultCacheAwareContextLoaderDelegate() + * @see ContextCacheUtils#retrieveContextFailureThreshold() */ public DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache) { this(contextCache, ContextCacheUtils.retrieveContextFailureThreshold()); } /** + * Construct a new {@code DefaultCacheAwareContextLoaderDelegate} using the + * supplied {@link ContextCache} and context failure threshold. * @since 6.1 */ - DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache, int failureThreshold) { + private DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache, int failureThreshold) { Assert.notNull(contextCache, "ContextCache must not be null"); Assert.isTrue(failureThreshold > 0, "'failureThreshold' must be positive"); this.contextCache = contextCache; From bff81d305678dc2d936fee61afe41924b7ef38f0 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 9 Jun 2023 17:34:51 +0200 Subject: [PATCH 5/5] Document test context failure threshold support in reference manual See gh-14182 --- framework-docs/modules/ROOT/nav.adoc | 1 + .../modules/ROOT/pages/appendix.adoc | 5 ++++ .../testcontext-framework/ctx-management.adoc | 1 + .../ctx-management/failure-threshold.adoc | 23 +++++++++++++++++++ 4 files changed, 30 insertions(+) create mode 100644 framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/failure-threshold.adoc diff --git a/framework-docs/modules/ROOT/nav.adoc b/framework-docs/modules/ROOT/nav.adoc index c0aa14fcb7e..932ab6a0e93 100644 --- a/framework-docs/modules/ROOT/nav.adoc +++ b/framework-docs/modules/ROOT/nav.adoc @@ -130,6 +130,7 @@ **** xref:testing/testcontext-framework/ctx-management/web.adoc[] **** xref:testing/testcontext-framework/ctx-management/web-mocks.adoc[] **** xref:testing/testcontext-framework/ctx-management/caching.adoc[] +**** xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[] **** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[] *** xref:testing/testcontext-framework/fixture-di.adoc[] *** xref:testing/testcontext-framework/web-scoped-beans.adoc[] diff --git a/framework-docs/modules/ROOT/pages/appendix.adoc b/framework-docs/modules/ROOT/pages/appendix.adoc index 64e97d84ee0..8c9e1b74766 100644 --- a/framework-docs/modules/ROOT/pages/appendix.adoc +++ b/framework-docs/modules/ROOT/pages/appendix.adoc @@ -64,6 +64,11 @@ on a test class. See xref:testing/annotations/integration-junit-jupiter.adoc#int | The maximum size of the context cache in the _Spring TestContext Framework_. See xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching]. +| `spring.test.context.failure.threshold` +| The failure threshold for errors encountered while attempting to load an `ApplicationContext` +in the _Spring TestContext Framework_. See +xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[Context Failure Threshold]. + | `spring.test.enclosing.configuration` | The default _enclosing configuration inheritance mode_ to use if `@NestedTestConfiguration` is not present on a test class. See diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management.adoc index 30ce7bd59df..61fe27babeb 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management.adoc @@ -119,5 +119,6 @@ advanced use cases. * xref:testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc[Context Configuration with Dynamic Property Sources] * xref:testing/testcontext-framework/ctx-management/web.adoc[Loading a `WebApplicationContext`] * xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching] +* xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[Context Failure Threshold] * xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[Context Hierarchies] diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/failure-threshold.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/failure-threshold.adoc new file mode 100644 index 00000000000..b4cda245011 --- /dev/null +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/failure-threshold.adoc @@ -0,0 +1,23 @@ +[[testcontext-ctx-management-failure-threshold]] += Context Failure Threshold + +As of Spring Framework 6.1, a context _failure threshold_ policy is in place which helps +avoid repeated attempts to load a failing `ApplicationContext`. By default, the failure +threshold is set to `1` which means that only one attempt will be made to load an +`ApplicationContext` for a given context cache key (see +xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching]). Any +subsequent attempt to load the `ApplicationContext` for the same context cache key will +result in an immediate `IllegalStateException` with an error message which explains that +the attempt was preemptively skipped. This behavior allows individual test classes and +test suites to fail faster by avoiding repeated attempts to load an `ApplicationContext` +that will never successfully load -- for example, due to a configuration error or a missing +external resource that prevents the context from loading in the current environment. + +You can configure the context failure threshold from the command line or a build script +by setting a JVM system property named `spring.test.context.failure.threshold` with a +positive integer value. As an alternative, you can set the same property via the +xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism. + +NOTE: If you wish to effectively disable the context failure threshold, you can set the +property to a very large value. For example, from the command line you could set the +system property via `-Dspring.test.context.failure.threshold=1000000`.