diff --git a/spring-test/spring-test.gradle b/spring-test/spring-test.gradle
index d8596d9110fe..d9b166b705b8 100644
--- a/spring-test/spring-test.gradle
+++ b/spring-test/spring-test.gradle
@@ -24,6 +24,7 @@ dependencies {
optional("jakarta.websocket:jakarta.websocket-api")
optional("junit:junit")
optional("org.apache.tomcat.embed:tomcat-embed-core")
+ optional("org.junit.platform:junit-platform-launcher") // for AOT processing
optional("org.junit.jupiter:junit-jupiter-api")
optional("org.testng:testng")
optional("org.aspectj:aspectjweaver")
diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/TestClassScanner.java b/spring-test/src/main/java/org/springframework/test/context/aot/TestClassScanner.java
new file mode 100644
index 000000000000..d9b6942c9f46
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/aot/TestClassScanner.java
@@ -0,0 +1,198 @@
+/*
+ * 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.aot;
+
+import java.lang.annotation.Annotation;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.platform.engine.support.descriptor.ClassSource;
+import org.junit.platform.launcher.Launcher;
+import org.junit.platform.launcher.LauncherDiscoveryRequest;
+import org.junit.platform.launcher.TestIdentifier;
+import org.junit.platform.launcher.TestPlan;
+import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
+import org.junit.platform.launcher.core.LauncherFactory;
+
+import org.springframework.core.annotation.MergedAnnotation;
+import org.springframework.core.annotation.MergedAnnotations;
+import org.springframework.test.context.BootstrapWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestContextAnnotationUtils;
+import org.springframework.util.Assert;
+
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots;
+import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames;
+import static org.springframework.core.annotation.MergedAnnotation.VALUE;
+import static org.springframework.core.annotation.MergedAnnotations.SearchStrategy.INHERITED_ANNOTATIONS;
+import static org.springframework.core.annotation.MergedAnnotations.SearchStrategy.TYPE_HIERARCHY;
+
+/**
+ * {@code TestClassScanner} scans provided classpath roots for Spring integration
+ * test classes using the JUnit Platform {@link Launcher} API which allows all
+ * registered {@link org.junit.platform.engine.TestEngine TestEngines} to discover
+ * tests according to their own rules.
+ *
+ *
The scanner currently detects the following categories of Spring integration
+ * test classes.
+ *
+ *
+ * - JUnit Jupiter: classes that register the {@code SpringExtension} via
+ * {@code @ExtendWith}.
+ * - JUnit 4: classes that register the {@code SpringJUnit4ClassRunner} or
+ * {@code SpringRunner} via {@code @RunWith}.
+ * - Generic: classes that are annotated with {@code @ContextConfiguration} or
+ * {@code @BootstrapWith}.
+ *
+ *
+ * The scanner has been tested with the following
+ * {@link org.junit.platform.engine.TestEngine TestEngines}.
+ *
+ *
+ * - JUnit Jupiter
+ * - JUnit Vintage
+ * - JUnit Platform Suite Engine
+ * - TestNG Engine for the JUnit Platform
+ *
+ *
+ * @author Sam Brannen
+ * @since 6.0
+ */
+class TestClassScanner {
+
+ // JUnit 4
+ private static final String RUN_WITH_ANNOTATION_NAME = "org.junit.runner.RunWith";
+ private static final String SPRING_JUNIT4_CLASS_RUNNER_NAME = "org.springframework.test.context.junit4.SpringJUnit4ClassRunner";
+ private static final String SPRING_RUNNER_NAME = "org.springframework.test.context.junit4.SpringRunner";
+
+ // JUnit Jupiter
+ private static final String EXTEND_WITH_ANNOTATION_NAME = "org.junit.jupiter.api.extension.ExtendWith";
+ private static final String SPRING_EXTENSION_NAME = "org.springframework.test.context.junit.jupiter.SpringExtension";
+
+
+ private final Log logger = LogFactory.getLog(TestClassScanner.class);
+
+ private final Set classpathRoots;
+
+
+ TestClassScanner(Set classpathRoots) {
+ Assert.notEmpty(classpathRoots, "'classpathRoots' must not be null or empty");
+ Assert.noNullElements(classpathRoots, "'classpathRoots' must not contain null elements");
+ this.classpathRoots = classpathRoots;
+ }
+
+
+ /**
+ * Scan the configured classpath roots for Spring integration test classes.
+ */
+ Stream> scan() {
+ return scan(new String[0]);
+ }
+
+ /**
+ * Scan the configured classpath roots for Spring integration test classes
+ * in the given packages.
+ */
+ Stream> scan(String... packageNames) {
+ Assert.notEmpty(packageNames, "'packageNames' must not be null or empty");
+ Assert.noNullElements(packageNames, "'packageNames' must not contain null elements");
+
+ if (logger.isInfoEnabled()) {
+ if (packageNames.length > 0) {
+ logger.info("Scanning for Spring test classes in packages %s in classpath roots %s"
+ .formatted(Arrays.toString(packageNames), this.classpathRoots));
+ }
+ else {
+ logger.info("Scanning for Spring test classes in all packages in classpath roots %s"
+ .formatted(this.classpathRoots));
+ }
+ }
+
+ LauncherDiscoveryRequestBuilder builder = LauncherDiscoveryRequestBuilder.request();
+ builder.selectors(selectClasspathRoots(this.classpathRoots));
+ if (packageNames.length > 0) {
+ builder.filters(includePackageNames(packageNames));
+ }
+ LauncherDiscoveryRequest request = builder.build();
+ Launcher launcher = LauncherFactory.create();
+ TestPlan testPlan = launcher.discover(request);
+
+ return testPlan.getRoots().stream()
+ .map(testPlan::getDescendants)
+ .flatMap(Set::stream)
+ .map(TestIdentifier::getSource)
+ .flatMap(Optional::stream)
+ .filter(ClassSource.class::isInstance)
+ .map(ClassSource.class::cast)
+ .map(this::getJavaClass)
+ .flatMap(Optional::stream)
+ .filter(this::isSpringTestClass)
+ .distinct();
+ }
+
+ private Optional> getJavaClass(ClassSource classSource) {
+ try {
+ return Optional.of(classSource.getJavaClass());
+ }
+ catch (Exception ex) {
+ // ignore exception
+ return Optional.empty();
+ }
+ }
+
+ private boolean isSpringTestClass(Class> clazz) {
+ boolean isSpringTestClass = (isJupiterSpringTestClass(clazz) || isJUnit4SpringTestClass(clazz) ||
+ isGenericSpringTestClass(clazz));
+ if (isSpringTestClass && logger.isTraceEnabled()) {
+ logger.trace("Found Spring test class: " + clazz.getName());
+ }
+ return isSpringTestClass;
+ }
+
+ private static boolean isJupiterSpringTestClass(Class> clazz) {
+ return MergedAnnotations.search(TYPE_HIERARCHY)
+ .withEnclosingClasses(TestContextAnnotationUtils::searchEnclosingClass)
+ .from(clazz)
+ .stream(EXTEND_WITH_ANNOTATION_NAME)
+ .map(annotation -> annotation.getClassArray(VALUE))
+ .flatMap(Arrays::stream)
+ .map(Class::getName)
+ .anyMatch(SPRING_EXTENSION_NAME::equals);
+ }
+
+ private static boolean isJUnit4SpringTestClass(Class> clazz) {
+ MergedAnnotation mergedAnnotation =
+ MergedAnnotations.from(clazz, INHERITED_ANNOTATIONS).get(RUN_WITH_ANNOTATION_NAME);
+ if (mergedAnnotation.isPresent()) {
+ String name = mergedAnnotation.getClass(VALUE).getName();
+ return (SPRING_JUNIT4_CLASS_RUNNER_NAME.equals(name) || SPRING_RUNNER_NAME.equals(name));
+ }
+ return false;
+ }
+
+ private static boolean isGenericSpringTestClass(Class> clazz) {
+ MergedAnnotations mergedAnnotations = MergedAnnotations.from(clazz, TYPE_HIERARCHY);
+ return (mergedAnnotations.isPresent(ContextConfiguration.class) ||
+ mergedAnnotations.isPresent(BootstrapWith.class));
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/package-info.java b/spring-test/src/main/java/org/springframework/test/context/aot/package-info.java
new file mode 100644
index 000000000000..d673bff93ead
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/aot/package-info.java
@@ -0,0 +1,9 @@
+/**
+ * Ahead-of-time (AOT) support for the Spring TestContext Framework.
+ */
+@NonNullApi
+@NonNullFields
+package org.springframework.test.context.aot;
+
+import org.springframework.lang.NonNullApi;
+import org.springframework.lang.NonNullFields;
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/TestClassScannerTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/TestClassScannerTests.java
new file mode 100644
index 000000000000..242467c37f4b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/TestClassScannerTests.java
@@ -0,0 +1,101 @@
+/*
+ * 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.aot;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.test.context.aot.samples.basic.BasicSpringJupiterTests;
+import org.springframework.test.context.aot.samples.basic.BasicSpringTestNGTests;
+import org.springframework.test.context.aot.samples.basic.BasicSpringVintageTests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link TestClassScanner}.
+ *
+ * @author Sam Brannen
+ * @since 6.0
+ */
+class TestClassScannerTests {
+
+ @Test
+ void scanBasicTestClasses() {
+ assertThat(scan("org.springframework.test.context.aot.samples.basic"))
+ .containsExactlyInAnyOrder(
+ BasicSpringJupiterTests.class,
+ BasicSpringJupiterTests.NestedTests.class,
+ BasicSpringVintageTests.class,
+ BasicSpringTestNGTests.class
+ );
+ }
+
+ @Test
+ void scanTestSuitesForJupiter() {
+ assertThat(scan("org.springframework.test.context.aot.samples.suites.jupiter"))
+ .containsExactlyInAnyOrder(BasicSpringJupiterTests.class, BasicSpringJupiterTests.NestedTests.class);
+ }
+
+ @Test
+ void scanTestSuitesForVintage() {
+ assertThat(scan("org.springframework.test.context.aot.samples.suites.vintage"))
+ .containsExactly(BasicSpringVintageTests.class);
+ }
+
+ @Test
+ void scanTestSuitesForTestNG() {
+ assertThat(scan("org.springframework.test.context.aot.samples.suites.testng"))
+ .containsExactly(BasicSpringTestNGTests.class);
+ }
+
+ @Test
+ void scanTestSuitesForAllTestEngines() {
+ assertThat(scan("org.springframework.test.context.aot.samples.suites.all"))
+ .containsExactlyInAnyOrder(
+ BasicSpringJupiterTests.class,
+ BasicSpringJupiterTests.NestedTests.class,
+ BasicSpringVintageTests.class,
+ BasicSpringTestNGTests.class
+ );
+ }
+
+ @Test
+ void scanTestSuitesWithNestedSuites() {
+ assertThat(scan("org.springframework.test.context.aot.samples.suites.nested"))
+ .containsExactlyInAnyOrder(
+ BasicSpringJupiterTests.class,
+ BasicSpringJupiterTests.NestedTests.class,
+ BasicSpringVintageTests.class
+ );
+ }
+
+ private Stream> scan(String... packageNames) {
+ try {
+ Set classpathRoots = Set.of(
+ Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()));
+ return new TestClassScanner(classpathRoots).scan(packageNames);
+ }
+ catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicJupiterTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicJupiterTests.java
new file mode 100644
index 000000000000..c9db1f3474ef
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicJupiterTests.java
@@ -0,0 +1,49 @@
+/*
+ * 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.aot.samples.basic;
+
+import org.junit.jupiter.api.Nested;
+
+import org.springframework.test.context.aot.samples.common.DefaultMessageService;
+import org.springframework.test.context.aot.samples.common.MessageService;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Sam Brannen
+ * @since 6.0
+ */
+class BasicJupiterTests {
+
+ private final MessageService messageService = new DefaultMessageService();
+
+ @org.junit.jupiter.api.Test
+ void test() {
+ assertThat(messageService.generateMessage()).isEqualTo("Hello, AOT!");
+ }
+
+ @Nested
+ class NestedTests {
+
+ @org.junit.jupiter.api.Test
+ void test() {
+ assertThat(messageService.generateMessage()).isEqualTo("Hello, AOT!");
+ }
+
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests.java
new file mode 100644
index 000000000000..e13a25afe3e9
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests.java
@@ -0,0 +1,61 @@
+/*
+ * 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.aot.samples.basic;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.Extension;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.aot.samples.common.MessageService;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Sam Brannen
+ * @since 6.0
+ */
+// Register an extension other than the SpringExtension to verify proper lookups
+// for repeated annotations.
+@ExtendWith(DummyExtension.class)
+@SpringJUnitConfig(BasicTestConfiguration.class)
+public class BasicSpringJupiterTests {
+
+ @org.junit.jupiter.api.Test
+ void test(@Autowired MessageService messageService) {
+ assertThat(messageService.generateMessage()).isEqualTo("Hello, AOT!");
+ }
+
+ @Nested
+ @TestPropertySource(properties = "foo=bar")
+ public class NestedTests {
+
+ @org.junit.jupiter.api.Test
+ void test(@Autowired MessageService messageService, @Value("${foo}") String foo) {
+ assertThat(messageService.generateMessage()).isEqualTo("Hello, AOT!");
+ assertThat(foo).isEqualTo("bar");
+ }
+
+ }
+
+}
+
+class DummyExtension implements Extension {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringTestNGTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringTestNGTests.java
new file mode 100644
index 000000000000..bdcee045be2c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringTestNGTests.java
@@ -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.aot.samples.basic;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.aot.samples.common.MessageService;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Sam Brannen
+ * @since 6.0
+ */
+@ContextConfiguration(classes = BasicTestConfiguration.class)
+public class BasicSpringTestNGTests extends AbstractTestNGSpringContextTests {
+
+ @Autowired
+ MessageService messageService;
+
+ @org.testng.annotations.Test
+ public void test() {
+ assertThat(messageService.generateMessage()).isEqualTo("Hello, AOT!");
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests.java
new file mode 100644
index 000000000000..6ec4ec6c61b8
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests.java
@@ -0,0 +1,44 @@
+/*
+ * 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.aot.samples.basic;
+
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.aot.samples.common.MessageService;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Sam Brannen
+ * @since 6.0
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = BasicTestConfiguration.class)
+public class BasicSpringVintageTests {
+
+ @Autowired
+ MessageService messageService;
+
+ @org.junit.Test
+ public void test() {
+ assertThat(messageService.generateMessage()).isEqualTo("Hello, AOT!");
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicTestConfiguration.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicTestConfiguration.java
new file mode 100644
index 000000000000..886c1e19bcba
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicTestConfiguration.java
@@ -0,0 +1,36 @@
+/*
+ * 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.aot.samples.basic;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.aot.samples.common.DefaultMessageService;
+import org.springframework.test.context.aot.samples.common.MessageService;
+
+/**
+ * @author Sam Brannen
+ * @since 6.0
+ */
+@Configuration(proxyBeanMethods = false)
+class BasicTestConfiguration {
+
+ @Bean
+ MessageService messageService() {
+ return new DefaultMessageService();
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicTestNGTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicTestNGTests.java
new file mode 100644
index 000000000000..6e280eb99976
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicTestNGTests.java
@@ -0,0 +1,37 @@
+/*
+ * 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.aot.samples.basic;
+
+import org.springframework.test.context.aot.samples.common.DefaultMessageService;
+import org.springframework.test.context.aot.samples.common.MessageService;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Sam Brannen
+ * @since 6.0
+ */
+public class BasicTestNGTests {
+
+ private final MessageService messageService = new DefaultMessageService();
+
+ @org.testng.annotations.Test
+ public void test() {
+ assertThat(messageService.generateMessage()).isEqualTo("Hello, AOT!");
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicVintageTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicVintageTests.java
new file mode 100644
index 000000000000..0e74eac737d7
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicVintageTests.java
@@ -0,0 +1,38 @@
+/*
+ * 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.aot.samples.basic;
+
+
+import org.springframework.test.context.aot.samples.common.DefaultMessageService;
+import org.springframework.test.context.aot.samples.common.MessageService;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Sam Brannen
+ * @since 6.0
+ */
+public class BasicVintageTests {
+
+ private final MessageService messageService = new DefaultMessageService();
+
+ @org.junit.Test
+ public void test() {
+ assertThat(messageService.generateMessage()).isEqualTo("Hello, AOT!");
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/common/DefaultMessageService.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/common/DefaultMessageService.java
new file mode 100644
index 000000000000..ac9d5a4a7bd2
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/common/DefaultMessageService.java
@@ -0,0 +1,30 @@
+/*
+ * 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.aot.samples.common;
+
+/**
+ * @author Sam Brannen
+ * @since 6.0
+ */
+public class DefaultMessageService implements MessageService {
+
+ @Override
+ public String generateMessage() {
+ return "Hello, AOT!";
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/common/MessageService.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/common/MessageService.java
new file mode 100644
index 000000000000..98f2f03ce1b6
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/common/MessageService.java
@@ -0,0 +1,27 @@
+/*
+ * 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.aot.samples.common;
+
+/**
+ * @author Sam Brannen
+ * @since 6.0
+ */
+public interface MessageService {
+
+ String generateMessage();
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/suites/all/AllEnginesTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/suites/all/AllEnginesTestSuite.java
new file mode 100644
index 000000000000..15a323024211
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/suites/all/AllEnginesTestSuite.java
@@ -0,0 +1,29 @@
+/*
+ * 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.aot.samples.suites.all;
+
+import org.junit.platform.suite.api.SelectPackages;
+import org.junit.platform.suite.api.Suite;
+
+/**
+ * @author Sam Brannen
+ * @since 6.0
+ */
+@Suite
+@SelectPackages("org.springframework.test.context.aot.samples.basic")
+public class AllEnginesTestSuite {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/suites/jupiter/JupiterTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/suites/jupiter/JupiterTestSuite.java
new file mode 100644
index 000000000000..0bc58735bedf
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/suites/jupiter/JupiterTestSuite.java
@@ -0,0 +1,31 @@
+/*
+ * 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.aot.samples.suites.jupiter;
+
+import org.junit.platform.suite.api.IncludeEngines;
+import org.junit.platform.suite.api.SelectPackages;
+import org.junit.platform.suite.api.Suite;
+
+/**
+ * @author Sam Brannen
+ * @since 6.0
+ */
+@Suite
+@IncludeEngines("junit-jupiter")
+@SelectPackages("org.springframework.test.context.aot.samples.basic")
+public class JupiterTestSuite {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/suites/nested/NestedTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/suites/nested/NestedTestSuite.java
new file mode 100644
index 000000000000..9309dbb37c2a
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/suites/nested/NestedTestSuite.java
@@ -0,0 +1,34 @@
+/*
+ * 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.aot.samples.suites.nested;
+
+import org.junit.platform.suite.api.IncludeClassNamePatterns;
+import org.junit.platform.suite.api.SelectPackages;
+import org.junit.platform.suite.api.Suite;
+
+/**
+ * @author Sam Brannen
+ * @since 6.0
+ */
+@Suite
+@IncludeClassNamePatterns(".*Suite$")
+@SelectPackages({
+ "org.springframework.test.context.aot.samples.suites.jupiter",
+ "org.springframework.test.context.aot.samples.suites.vintage"
+})
+public class NestedTestSuite {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/suites/testng/TestNGTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/suites/testng/TestNGTestSuite.java
new file mode 100644
index 000000000000..a8a8b8a27f41
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/suites/testng/TestNGTestSuite.java
@@ -0,0 +1,31 @@
+/*
+ * 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.aot.samples.suites.testng;
+
+import org.junit.platform.suite.api.IncludeEngines;
+import org.junit.platform.suite.api.SelectPackages;
+import org.junit.platform.suite.api.Suite;
+
+/**
+ * @author Sam Brannen
+ * @since 6.0
+ */
+@Suite
+@IncludeEngines("testng")
+@SelectPackages("org.springframework.test.context.aot.samples.basic")
+public class TestNGTestSuite {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/suites/vintage/VintageTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/suites/vintage/VintageTestSuite.java
new file mode 100644
index 000000000000..dab5e5c843a2
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/suites/vintage/VintageTestSuite.java
@@ -0,0 +1,31 @@
+/*
+ * 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.aot.samples.suites.vintage;
+
+import org.junit.platform.suite.api.IncludeEngines;
+import org.junit.platform.suite.api.SelectPackages;
+import org.junit.platform.suite.api.Suite;
+
+/**
+ * @author Sam Brannen
+ * @since 6.0
+ */
+@Suite
+@IncludeEngines("junit-vintage")
+@SelectPackages("org.springframework.test.context.aot.samples.basic")
+public class VintageTestSuite {
+}
diff --git a/spring-test/src/test/resources/log4j2-test.xml b/spring-test/src/test/resources/log4j2-test.xml
index 89d254091a47..8fc877aff3f8 100644
--- a/spring-test/src/test/resources/log4j2-test.xml
+++ b/spring-test/src/test/resources/log4j2-test.xml
@@ -14,6 +14,7 @@
+
diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml
index 24bbed66a8e2..26ed87a7d6a3 100644
--- a/src/checkstyle/checkstyle-suppressions.xml
+++ b/src/checkstyle/checkstyle-suppressions.xml
@@ -77,7 +77,7 @@
-
+
@@ -90,7 +90,7 @@
-
+