Browse Source
This set of commits introduces ApplicationContext "failure threshold" support in the Spring TestContext Framework (TCF). Specifically, this new feature avoids repeated attempts to load a failing ApplicationContext in the TCF, based on a failure threshold which defaults to 1 but can be configured via a system property. See individual commits for details. Closes gh-14182pull/30636/head
8 changed files with 318 additions and 17 deletions
@ -0,0 +1,23 @@
@@ -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`. |
||||
@ -0,0 +1,174 @@
@@ -0,0 +1,174 @@
|
||||
/* |
||||
* 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 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.BeforeEach; |
||||
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; |
||||
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 |
||||
*/ |
||||
class ContextFailureThresholdTests { |
||||
|
||||
private static final AtomicInteger loadCount = new AtomicInteger(0); |
||||
|
||||
|
||||
@BeforeEach |
||||
@AfterEach |
||||
void resetFlag() { |
||||
loadCount.set(0); |
||||
SpringProperties.setProperty(CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME, null); |
||||
} |
||||
|
||||
@Test |
||||
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 |
||||
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 |
||||
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 { |
||||
|
||||
FailingConfig() { |
||||
loadCount.incrementAndGet(); |
||||
} |
||||
|
||||
@Bean |
||||
String explosiveString() { |
||||
throw new RuntimeException("Boom!"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue