From 3e5aa8d7344f3ec42a460bec36f8f24f937a2753 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 3 Aug 2023 10:59:42 +0300 Subject: [PATCH] Fail on error by default during test AOT processing Prior to this commit, if an error was encountered during build-time AOT processing, the error was logged at WARN/DEBUG level, and processing continued. With this commit, test AOT processing now fails on error by default. In addition, the `failOnError` mode can be disabled by setting the `spring.test.aot.processing.failOnError` Spring/System property to `false`. Closes gh-30977 --- .../modules/ROOT/pages/appendix.adoc | 5 ++ .../testing/testcontext-framework/aot.adoc | 17 ++++- .../context/aot/TestContextAotGenerator.java | 41 +++++++++-- .../test/context/aot/AotIntegrationTests.java | 2 +- .../aot/TestContextAotGeneratorUnitTests.java | 68 +++++++++++++++++++ 5 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorUnitTests.java diff --git a/framework-docs/modules/ROOT/pages/appendix.adoc b/framework-docs/modules/ROOT/pages/appendix.adoc index c3074a81a63f..c6c7d9d89815 100644 --- a/framework-docs/modules/ROOT/pages/appendix.adoc +++ b/framework-docs/modules/ROOT/pages/appendix.adoc @@ -55,6 +55,11 @@ for details. {api-spring-framework}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`] for details. +| `spring.test.aot.processing.failOnError` +| A boolean flag that controls whether errors encountered during AOT processing in the +_Spring TestContext Framework_ should result in an exception that fails the overall process. +See xref:testing/testcontext-framework/aot.adoc[Ahead of Time Support for Tests]. + | `spring.test.constructor.autowire.mode` | The default _test constructor autowire mode_ to use if `@TestConstructor` is not present on a test class. See xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[Changing the default test constructor autowire mode]. diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/aot.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/aot.adoc index 5f9c7c97f669..b6dcc2b2475c 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/aot.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/aot.adoc @@ -19,7 +19,22 @@ following features. use an AOT-optimized `ApplicationContext` that participates transparently with the xref:testing/testcontext-framework/ctx-management/caching.adoc[context cache]. -[WARNING] +[TIP] +==== +By default, if an error is encountered during build-time AOT processing, an exception +will be thrown, and the overall process will fail immediately. + +If you would prefer that build-time AOT processing continue after errors are encountered, +you can disable the `failOnError` mode which results in errors being logged at `WARN` +level or with greater detail at `DEBUG` level. + +The `failOnError` mode can be disabled from the command line or a build script by setting +a JVM system property named `spring.test.aot.processing.failOnError` to `false`. As an +alternative, you can set the same property via the +xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism. +==== + +[NOTE] ==== The `@ContextHierarchy` annotation is currently not supported in AOT mode. ==== diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java index 4cce32599b96..b1fabb79f31d 100644 --- a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java +++ b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java @@ -43,6 +43,7 @@ import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.context.aot.ApplicationContextAotGenerator; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.SpringProperties; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; @@ -56,6 +57,7 @@ import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; import static org.springframework.aot.hint.MemberCategory.INVOKE_DECLARED_CONSTRUCTORS; import static org.springframework.aot.hint.MemberCategory.INVOKE_PUBLIC_METHODS; @@ -70,8 +72,26 @@ */ public class TestContextAotGenerator { + /** + * JVM system property used to set the {@code failOnError} flag: {@value}. + *

The {@code failOnError} flag controls whether errors encountered during + * AOT processing in the Spring TestContext Framework should result + * in an exception that fails the overall process. + *

Defaults to {@code true}. + *

Supported values include {@code true} or {@code false}, ignoring case. + * For example, the default may be changed to {@code false} by supplying + * the following JVM system property via the command line. + *

-Dspring.test.aot.processing.failOnError=false
+ *

May alternatively be configured via the + * {@link org.springframework.core.SpringProperties SpringProperties} + * mechanism. + * @since 6.1 + */ + public static final String FAIL_ON_ERROR_PROPERTY_NAME = "spring.test.aot.processing.failOnError"; + private static final Log logger = LogFactory.getLog(TestContextAotGenerator.class); + private final ApplicationContextAotGenerator aotGenerator = new ApplicationContextAotGenerator(); private final AotServices testRuntimeHintsRegistrars; @@ -85,13 +105,14 @@ public class TestContextAotGenerator { private final RuntimeHints runtimeHints; - private final boolean failOnError; + final boolean failOnError; /** * Create a new {@link TestContextAotGenerator} that uses the supplied * {@link GeneratedFiles}. * @param generatedFiles the {@code GeneratedFiles} to use + * @see #TestContextAotGenerator(GeneratedFiles, RuntimeHints) */ public TestContextAotGenerator(GeneratedFiles generatedFiles) { this(generatedFiles, new RuntimeHints()); @@ -100,11 +121,15 @@ public TestContextAotGenerator(GeneratedFiles generatedFiles) { /** * Create a new {@link TestContextAotGenerator} that uses the supplied * {@link GeneratedFiles} and {@link RuntimeHints}. + *

This constructor looks up the value of the {@code failOnError} flag via + * the {@value #FAIL_ON_ERROR_PROPERTY_NAME} property, defaulting to + * {@code true} if the property is not set. * @param generatedFiles the {@code GeneratedFiles} to use * @param runtimeHints the {@code RuntimeHints} to use + * @see #TestContextAotGenerator(GeneratedFiles, RuntimeHints, boolean) */ public TestContextAotGenerator(GeneratedFiles generatedFiles, RuntimeHints runtimeHints) { - this(generatedFiles, runtimeHints, false); + this(generatedFiles, runtimeHints, getFailOnErrorFlag()); } /** @@ -114,9 +139,9 @@ public TestContextAotGenerator(GeneratedFiles generatedFiles, RuntimeHints runti * @param runtimeHints the {@code RuntimeHints} to use * @param failOnError {@code true} if errors encountered during AOT processing * should result in an exception that fails the overall process - * @since 6.0.12 + * @since 6.1 */ - TestContextAotGenerator(GeneratedFiles generatedFiles, RuntimeHints runtimeHints, boolean failOnError) { + public TestContextAotGenerator(GeneratedFiles generatedFiles, RuntimeHints runtimeHints, boolean failOnError) { this.testRuntimeHintsRegistrars = AotServices.factories().load(TestRuntimeHintsRegistrar.class); this.generatedFiles = generatedFiles; this.runtimeHints = runtimeHints; @@ -368,4 +393,12 @@ private void registerDeclaredConstructors(Class type) { this.runtimeHints.reflection().registerType(type, INVOKE_DECLARED_CONSTRUCTORS); } + private static boolean getFailOnErrorFlag() { + String failOnError = SpringProperties.getProperty(FAIL_ON_ERROR_PROPERTY_NAME); + if (StringUtils.hasText(failOnError)) { + return Boolean.parseBoolean(failOnError.trim()); + } + return true; + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.java index c7b749fc9a73..18d87c1d7eb7 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.java @@ -87,7 +87,7 @@ void endToEndTests() { // AOT BUILD-TIME: PROCESSING InMemoryGeneratedFiles generatedFiles = new InMemoryGeneratedFiles(); - TestContextAotGenerator generator = new TestContextAotGenerator(generatedFiles, new RuntimeHints(), true); + TestContextAotGenerator generator = new TestContextAotGenerator(generatedFiles, new RuntimeHints()); generator.processAheadOfTime(testClasses); List sourceFiles = generatedFiles.getGeneratedFiles(Kind.SOURCE).keySet().stream().toList(); diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorUnitTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorUnitTests.java new file mode 100644 index 000000000000..c837242617c8 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorUnitTests.java @@ -0,0 +1,68 @@ +/* + * 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.aot; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import org.springframework.core.SpringProperties; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.context.aot.TestContextAotGenerator.FAIL_ON_ERROR_PROPERTY_NAME; + +/** + * Unit tests for {@link TestContextAotGenerator}. + * + * @author Sam Brannen + * @since 6.1 + */ +class TestContextAotGeneratorUnitTests { + + @BeforeEach + @AfterEach + void resetFlag() { + SpringProperties.setProperty(FAIL_ON_ERROR_PROPERTY_NAME, null); + } + + @Test + void failOnErrorEnabledByDefault() { + assertThat(createGenerator().failOnError).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = {"true", " True\t"}) + void failOnErrorEnabledViaSpringProperty(String value) { + SpringProperties.setProperty(FAIL_ON_ERROR_PROPERTY_NAME, value); + assertThat(createGenerator().failOnError).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = {"false", " False\t", "x"}) + void failOnErrorDisabledViaSpringProperty(String value) { + SpringProperties.setProperty(FAIL_ON_ERROR_PROPERTY_NAME, value); + assertThat(createGenerator().failOnError).isFalse(); + } + + + private static TestContextAotGenerator createGenerator() { + return new TestContextAotGenerator(null); + } + +}