Repository: junit-team/junit5 (language: java)

Issue:
`@DisplayNameGeneration` is not discovered on runtime enclosing type for `@Nested` test class
## Overview

`@DisplayNameGeneration` is currently discovered on the current test, a superclass, or the enclosing class in which a `Nested` test class is declared. However, the runtime type of an enclosing instance is not always the class in which a `@Nested` test class is declared.

## Example

The examples here are copies of those in #4130 except that `@IndicativeSentencesGeneration` has been moved from `AbstractBaseTests` to `ScenarioOneTests`.

```java
abstract class AbstractBaseTests {

	@Nested
	class NestedTests {

		@Test
		void test() {
		}
	}
}
```

```java
@IndicativeSentencesGeneration
class ScenarioOneTests extends AbstractBaseTests {
}
```

## Actual Behavior

When running `ScenarioOneTests`, we currently see the following display names, since `@IndicativeSentencesGeneration` is not honored (i.e., the `@DisplayNameGeneration(IndicativeSentences.class)` meta-annotation is not discovered).

- `ScenarioOneTests`
  - `NestedTests`
    - `test()`

## Expected Behavior

When running `ScenarioOneTests`, we would expect the following display names **before** #4130 is resolved.

- `ScenarioOneTests`
  - `AbstractBaseTests, NestedTests`
    - `AbstractBaseTests, NestedTests, test()`

When running `ScenarioOneTests`, we would expect the following display names **after** #4130 is resolved.

- `ScenarioOneTests`
  - `ScenarioOneTests, NestedTests`
    - `ScenarioOneTests, NestedTests, test()`

## Related Issues

- #4130 

## Deliverables

- [x] Determine if it is possible to support this use case
- [x] If it is possible, ensure that `@DisplayNameGeneration` is discovered on the runtime enclosing type for a `@Nested` test class.



Discussion / hints:
**Team decision:** Investigate feasibility after #4130 has been addressed.
hello @sbrannen, I would love to work on this issue, would you please assign it to me?
it will be solved by adding somethings 
@abdelmawla56 I'd be happy to but #4130 should be done first because we think there'll be a large overlap. That's why this issue is currently labeled as "blocked".

Fix patch:
diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc
index 0abbde8c9af2..ff501e489fcf 100644
--- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc
+++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc
@@ -32,7 +32,8 @@ JUnit repository on GitHub.
 [[release-notes-5.12.0-M1-junit-platform-deprecations-and-breaking-changes]]
 ==== Deprecations and Breaking Changes
 
-* ❓
+* `SearchOption` and `AnnotationSupport.findAnnotation(Class, Class, SearchOption)` from
+  `junit-platform-commons` have been deprecated.
 
 [[release-notes-5.12.0-M1-junit-platform-new-features-and-improvements]]
 ==== New Features and Improvements
@@ -75,6 +76,8 @@ JUnit repository on GitHub.
   to the most recent test or container that was started or has written output.
 * New public interface `ClasspathScanner` allowing third parties to provide a custom
   implementation for scanning the classpath for classes and resources.
+* New `AnnotationSupport.findAnnotation(Class, Class, List)` method to support searching
+  for an annotation on an inner class and its runtime enclosing instance types.
 
 
 [[release-notes-5.12.0-M1-junit-jupiter]]
@@ -87,6 +90,9 @@ JUnit repository on GitHub.
   to `DisplayNameGenerator` implementations. Prior to this change, such generators were
   only able to access the enclosing class in which `@Nested` was declared, but they could
   not access the concrete runtime type of the enclosing instance.
+* `@DisplayNameGeneration` annotations are now discovered on the _runtime_ enclosing types
+  of `@Nested` test classes instead of the compile-time enclosing class in which the
+  `@Nested` class was _declared_.
 
 [[release-notes-5.12.0-M1-junit-jupiter-deprecations-and-breaking-changes]]
 ==== Deprecations and Breaking Changes
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java
index 89f7784d8c57..9852c79bffd5 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java
@@ -13,6 +13,7 @@
 import static java.util.Collections.emptyList;
 import static org.apiguardian.api.API.Status.DEPRECATED;
 import static org.apiguardian.api.API.Status.EXPERIMENTAL;
+import static org.apiguardian.api.API.Status.INTERNAL;
 import static org.apiguardian.api.API.Status.STABLE;
 import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation;
 import static org.junit.platform.commons.support.ModifierSupport.isStatic;
@@ -24,7 +25,6 @@
 
 import org.apiguardian.api.API;
 import org.junit.platform.commons.support.ReflectionSupport;
-import org.junit.platform.commons.support.SearchOption;
 import org.junit.platform.commons.util.ClassUtils;
 import org.junit.platform.commons.util.Preconditions;
 
@@ -315,23 +315,24 @@ public IndicativeSentences() {
 
 		@Override
 		public String generateDisplayNameForClass(Class<?> testClass) {
-			return getGeneratorFor(testClass).generateDisplayNameForClass(testClass);
+			return getGeneratorFor(testClass, emptyList()).generateDisplayNameForClass(testClass);
 		}
 
 		@Override
 		public String generateDisplayNameForNestedClass(List<Class<?>> enclosingInstanceTypes, Class<?> nestedClass) {
-			return getSentenceBeginning(enclosingInstanceTypes, nestedClass);
+			return getSentenceBeginning(nestedClass, enclosingInstanceTypes);
 		}
 
 		@Override
 		public String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
 				Method testMethod) {
-			return getSentenceBeginning(enclosingInstanceTypes, testClass) + getFragmentSeparator(testClass)
-					+ getGeneratorFor(testClass).generateDisplayNameForMethod(enclosingInstanceTypes, testClass,
-						testMethod);
+			return getSentenceBeginning(testClass, enclosingInstanceTypes)
+					+ getFragmentSeparator(testClass, enclosingInstanceTypes)
+					+ getGeneratorFor(testClass, enclosingInstanceTypes).generateDisplayNameForMethod(
+						enclosingInstanceTypes, testClass, testMethod);
 		}
 
-		private String getSentenceBeginning(List<Class<?>> enclosingInstanceTypes, Class<?> testClass) {
+		private String getSentenceBeginning(Class<?> testClass, List<Class<?>> enclosingInstanceTypes) {
 			Class<?> enclosingClass = enclosingInstanceTypes.isEmpty() ? null
 					: enclosingInstanceTypes.get(enclosingInstanceTypes.size() - 1);
 			boolean topLevelTestClass = (enclosingClass == null || isStatic(testClass));
@@ -342,33 +343,35 @@ private String getSentenceBeginning(List<Class<?>> enclosingInstanceTypes, Class
 				if (displayName.isPresent()) {
 					return displayName.get();
 				}
-				Class<? extends DisplayNameGenerator> generatorClass = findDisplayNameGeneration(testClass)//
-						.map(DisplayNameGeneration::value)//
-						.filter(not(IndicativeSentences.class))//
-						.orElse(null);
+				Class<? extends DisplayNameGenerator> generatorClass = findDisplayNameGeneration(testClass,
+					enclosingInstanceTypes)//
+							.map(DisplayNameGeneration::value)//
+							.filter(not(IndicativeSentences.class))//
+							.orElse(null);
 				if (generatorClass != null) {
 					return getDisplayNameGenerator(generatorClass).generateDisplayNameForClass(testClass);
 				}
 				return generateDisplayNameForClass(testClass);
 			}
 
+			List<Class<?>> remainingEnclosingInstanceTypes = enclosingInstanceTypes.isEmpty() ? emptyList()
+					: enclosingInstanceTypes.subList(0, enclosingInstanceTypes.size() - 1);
+
 			// Only build prefix based on the enclosing class if the enclosing
 			// class is also configured to use the IndicativeSentences generator.
-			boolean buildPrefix = findDisplayNameGeneration(enclosingClass)//
+			boolean buildPrefix = findDisplayNameGeneration(enclosingClass, remainingEnclosingInstanceTypes)//
 					.map(DisplayNameGeneration::value)//
 					.filter(IndicativeSentences.class::equals)//
 					.isPresent();
 
-			List<Class<?>> remainingEnclosingInstanceTypes = enclosingInstanceTypes.isEmpty() ? emptyList()
-					: enclosingInstanceTypes.subList(0, enclosingInstanceTypes.size() - 1);
-
 			String prefix = (buildPrefix
-					? getSentenceBeginning(remainingEnclosingInstanceTypes, enclosingClass)
-							+ getFragmentSeparator(testClass)
+					? getSentenceBeginning(enclosingClass, remainingEnclosingInstanceTypes)
+							+ getFragmentSeparator(testClass, enclosingInstanceTypes)
 					: "");
 
-			return prefix + displayName.orElseGet(() -> getGeneratorFor(testClass).generateDisplayNameForNestedClass(
-				remainingEnclosingInstanceTypes, testClass));
+			return prefix + displayName.orElseGet(
+				() -> getGeneratorFor(testClass, enclosingInstanceTypes).generateDisplayNameForNestedClass(
+					remainingEnclosingInstanceTypes, testClass));
 		}
 
 		/**
@@ -381,10 +384,12 @@ private String getSentenceBeginning(List<Class<?>> enclosingInstanceTypes, Class
 		 * will be used.
 		 *
 		 * @param testClass the test class to search on for {@code @IndicativeSentencesGeneration}
+		 * @param enclosingInstanceTypes the runtime types of the enclosing
+		 * instances; never {@code null}
 		 * @return the sentence fragment separator
 		 */
-		private static String getFragmentSeparator(Class<?> testClass) {
-			return findIndicativeSentencesGeneration(testClass)//
+		private static String getFragmentSeparator(Class<?> testClass, List<Class<?>> enclosingInstanceTypes) {
+			return findIndicativeSentencesGeneration(testClass, enclosingInstanceTypes)//
 					.map(IndicativeSentencesGeneration::separator)//
 					.orElse(IndicativeSentencesGeneration.DEFAULT_SEPARATOR);
 		}
@@ -399,10 +404,12 @@ private static String getFragmentSeparator(Class<?> testClass) {
 		 * will be used.
 		 *
 		 * @param testClass the test class to search on for {@code @IndicativeSentencesGeneration}
+		 * @param enclosingInstanceTypes the runtime types of the enclosing
+		 * instances; never {@code null}
 		 * @return the {@code DisplayNameGenerator} instance to use
 		 */
-		private static DisplayNameGenerator getGeneratorFor(Class<?> testClass) {
-			return findIndicativeSentencesGeneration(testClass)//
+		private static DisplayNameGenerator getGeneratorFor(Class<?> testClass, List<Class<?>> enclosingInstanceTypes) {
+			return findIndicativeSentencesGeneration(testClass, enclosingInstanceTypes)//
 					.map(IndicativeSentencesGeneration::generator)//
 					.filter(not(IndicativeSentences.class))//
 					.map(DisplayNameGenerator::getDisplayNameGenerator)//
@@ -412,26 +419,32 @@ private static DisplayNameGenerator getGeneratorFor(Class<?> testClass) {
 		/**
 		 * Find the first {@code DisplayNameGeneration} annotation that is either
 		 * <em>directly present</em>, <em>meta-present</em>, or <em>indirectly present</em>
-		 * on the supplied {@code testClass} or on an enclosing class.
+		 * on the supplied {@code testClass} or on an enclosing instance type.
 		 *
 		 * @param testClass the test class on which to find the annotation; never {@code null}
+		 * @param enclosingInstanceTypes the runtime types of the enclosing
+		 * instances; never {@code null}
 		 * @return an {@code Optional} containing the annotation, potentially empty if not found
 		 */
-		private static Optional<DisplayNameGeneration> findDisplayNameGeneration(Class<?> testClass) {
-			return findAnnotation(testClass, DisplayNameGeneration.class, SearchOption.INCLUDE_ENCLOSING_CLASSES);
+		@API(status = INTERNAL, since = "5.12")
+		private static Optional<DisplayNameGeneration> findDisplayNameGeneration(Class<?> testClass,
+				List<Class<?>> enclosingInstanceTypes) {
+			return findAnnotation(testClass, DisplayNameGeneration.class, enclosingInstanceTypes);
 		}
 
 		/**
 		 * Find the first {@code IndicativeSentencesGeneration} annotation that is either
 		 * <em>directly present</em>, <em>meta-present</em>, or <em>indirectly present</em>
-		 * on the supplied {@code testClass} or on an enclosing class.
+		 * on the supplied {@code testClass} or on an enclosing instance type.
 		 *
 		 * @param testClass the test class on which to find the annotation; never {@code null}
+		 * @param enclosingInstanceTypes the runtime types of the enclosing
+		 * instances; never {@code null}
 		 * @return an {@code Optional} containing the annotation, potentially empty if not found
 		 */
-		private static Optional<IndicativeSentencesGeneration> findIndicativeSentencesGeneration(Class<?> testClass) {
-			return findAnnotation(testClass, IndicativeSentencesGeneration.class,
-				SearchOption.INCLUDE_ENCLOSING_CLASSES);
+		private static Optional<IndicativeSentencesGeneration> findIndicativeSentencesGeneration(Class<?> testClass,
+				List<Class<?>> enclosingInstanceTypes) {
+			return findAnnotation(testClass, IndicativeSentencesGeneration.class, enclosingInstanceTypes);
 		}
 
 		private static Predicate<Class<?>> not(Class<?> clazz) {
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java
index 76b65ef5e49a..c2c0903d91a8 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java
@@ -14,9 +14,10 @@
 
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Method;
+import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
-import java.util.function.Function;
+import java.util.function.BiFunction;
 import java.util.function.Supplier;
 
 import org.junit.jupiter.api.DisplayName;
@@ -30,7 +31,6 @@
 import org.junit.platform.commons.logging.Logger;
 import org.junit.platform.commons.logging.LoggerFactory;
 import org.junit.platform.commons.support.ReflectionSupport;
-import org.junit.platform.commons.support.SearchOption;
 import org.junit.platform.commons.util.Preconditions;
 import org.junit.platform.commons.util.StringUtils;
 
@@ -97,33 +97,43 @@ static String determineDisplayNameForMethod(Supplier<List<Class<?>>> enclosingIn
 	}
 
 	static Supplier<String> createDisplayNameSupplierForClass(Class<?> testClass, JupiterConfiguration configuration) {
-		return createDisplayNameSupplier(testClass, configuration,
-			generator -> generator.generateDisplayNameForClass(testClass));
+		return createDisplayNameSupplier(Collections::emptyList, testClass, configuration,
+			(generator, __) -> generator.generateDisplayNameForClass(testClass));
 	}
 
-	static Supplier<String> createDisplayNameSupplierForNestedClass(Supplier<List<Class<?>>> enclosingInstanceTypes,
-			Class<?> testClass, JupiterConfiguration configuration) {
-		return createDisplayNameSupplier(testClass, configuration,
-			generator -> generator.generateDisplayNameForNestedClass(enclosingInstanceTypes.get(), testClass));
+	static Supplier<String> createDisplayNameSupplierForNestedClass(
+			Supplier<List<Class<?>>> enclosingInstanceTypesSupplier, Class<?> testClass,
+			JupiterConfiguration configuration) {
+		return createDisplayNameSupplier(enclosingInstanceTypesSupplier, testClass, configuration,
+			(generator, enclosingInstanceTypes) -> generator.generateDisplayNameForNestedClass(enclosingInstanceTypes,
+				testClass));
 	}
 
-	private static Supplier<String> createDisplayNameSupplierForMethod(Supplier<List<Class<?>>> enclosingInstanceTypes,
-			Class<?> testClass, Method testMethod, JupiterConfiguration configuration) {
-		return createDisplayNameSupplier(testClass, configuration,
-			generator -> generator.generateDisplayNameForMethod(enclosingInstanceTypes.get(), testClass, testMethod));
+	private static Supplier<String> createDisplayNameSupplierForMethod(
+			Supplier<List<Class<?>>> enclosingInstanceTypesSupplier, Class<?> testClass, Method testMethod,
+			JupiterConfiguration configuration) {
+		return createDisplayNameSupplier(enclosingInstanceTypesSupplier, testClass, configuration,
+			(generator, enclosingInstanceTypes) -> generator.generateDisplayNameForMethod(enclosingInstanceTypes,
+				testClass, testMethod));
 	}
 
-	private static Supplier<String> createDisplayNameSupplier(Class<?> testClass, JupiterConfiguration configuration,
-			Function<DisplayNameGenerator, String> generatorFunction) {
-		return () -> findDisplayNameGenerator(testClass) //
-				.map(generatorFunction) //
-				.orElseGet(() -> generatorFunction.apply(configuration.getDefaultDisplayNameGenerator()));
+	private static Supplier<String> createDisplayNameSupplier(Supplier<List<Class<?>>> enclosingInstanceTypesSupplier,
+			Class<?> testClass, JupiterConfiguration configuration,
+			BiFunction<DisplayNameGenerator, List<Class<?>>, String> generatorFunction) {
+		return () -> {
+			List<Class<?>> enclosingInstanceTypes = enclosingInstanceTypesSupplier.get();
+			return findDisplayNameGenerator(enclosingInstanceTypes, testClass) //
+					.map(it -> generatorFunction.apply(it, enclosingInstanceTypes)) //
+					.orElseGet(() -> generatorFunction.apply(configuration.getDefaultDisplayNameGenerator(),
+						enclosingInstanceTypes));
+		};
 	}
 
-	private static Optional<DisplayNameGenerator> findDisplayNameGenerator(Class<?> testClass) {
+	private static Optional<DisplayNameGenerator> findDisplayNameGenerator(List<Class<?>> enclosingInstanceTypes,
+			Class<?> testClass) {
 		Preconditions.notNull(testClass, "Test class must not be null");
 
-		return findAnnotation(testClass, DisplayNameGeneration.class, SearchOption.INCLUDE_ENCLOSING_CLASSES) //
+		return findAnnotation(testClass, DisplayNameGeneration.class, enclosingInstanceTypes) //
 				.map(DisplayNameGeneration::value) //
 				.map(displayNameGeneratorClass -> {
 					if (displayNameGeneratorClass == Standard.class) {
diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java
index 3066916f3503..d5265aea9d05 100644
--- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java
@@ -10,8 +10,9 @@
 
 package org.junit.platform.commons.support;
 
+import static org.apiguardian.api.API.Status.DEPRECATED;
+import static org.apiguardian.api.API.Status.EXPERIMENTAL;
 import static org.apiguardian.api.API.Status.MAINTAINED;
-import static org.apiguardian.api.API.Status.STABLE;
 
 import java.lang.annotation.Annotation;
 import java.lang.reflect.AnnotatedElement;
@@ -163,8 +164,13 @@ public static <A extends Annotation> Optional<A> findAnnotation(AnnotatedElement
 	 * @since 1.8
 	 * @see SearchOption
 	 * @see #findAnnotation(AnnotatedElement, Class)
+	 * @deprecated Use {@link #findAnnotation(Class, Class, List)}
+	 * (for {@code SearchOption.DEFAULT}) or
+	 * {@link #findAnnotation(Class, Class, List)} (for
+	 * {@code SearchOption.INCLUDE_ENCLOSING_CLASSES}) instead
 	 */
-	@API(status = STABLE, since = "1.10")
+	@Deprecated
+	@API(status = DEPRECATED, since = "1.12")
 	public static <A extends Annotation> Optional<A> findAnnotation(Class<?> clazz, Class<A> annotationType,
 			SearchOption searchOption) {
 
@@ -174,6 +180,55 @@ public static <A extends Annotation> Optional<A> findAnnotation(Class<?> clazz,
 			searchOption == SearchOption.INCLUDE_ENCLOSING_CLASSES);
 	}
 
+	/**
+	 * Find the first annotation of the specified type that is either
+	 * <em>directly present</em>, <em>meta-present</em>, or <em>indirectly
+	 * present</em> on the supplied class.
+	 *
+	 * <p>If the annotation is neither <em>directly present</em> nor <em>meta-present</em>
+	 * on the class, this method will additionally search on interfaces implemented
+	 * by the class before searching for an annotation that is <em>indirectly present</em>
+	 * on the class (i.e., within the class inheritance hierarchy).
+	 *
+	 * <p>If the annotation still has not been found, this method will optionally
+	 * search recursively through the supplied enclosing instance types, starting
+	 * at the innermost enclosing class (the last one in the supplied list of
+	 * {@code enclosingInstanceTypes}).
+	 *
+	 * @implNote The classes supplied as {@code enclosingInstanceTypes} may
+	 * differ from the classes returned from invocations of
+	 * {@link Class#getEnclosingClass()} &mdash; for example, when a nested test
+	 * class is inherited from a superclass.
+	 *
+	 * @param <A> the annotation type
+	 * @param clazz the class on which to search for the annotation; may be {@code null}
+	 * @param annotationType the annotation type to search for; never {@code null}
+	 * @param enclosingInstanceTypes the runtime types of the enclosing
+	 * instances for the class, ordered from outermost to innermost,
+	 * excluding {@code clazz}; never {@code null}
+	 * @return an {@code Optional} containing the annotation; never {@code null} but
+	 * potentially empty if {@code clazz} is not an inner class
+	 * @since 1.12
+	 * @see #findAnnotation(AnnotatedElement, Class)
+	 */
+	@API(status = EXPERIMENTAL, since = "1.12")
+	public static <A extends Annotation> Optional<A> findAnnotation(Class<?> clazz, Class<A> annotationType,
+			List<Class<?>> enclosingInstanceTypes) {
+
+		Preconditions.notNull(enclosingInstanceTypes, "enclosingInstanceTypes must not be null");
+
+		Optional<A> annotation = findAnnotation(clazz, annotationType);
+		if (!annotation.isPresent()) {
+			for (int i = enclosingInstanceTypes.size() - 1; i >= 0; i--) {
+				annotation = findAnnotation(enclosingInstanceTypes.get(i), annotationType);
+				if (annotation.isPresent()) {
+					break;
+				}
+			}
+		}
+		return annotation;
+	}
+
 	/**
 	 * Find all <em>repeatable</em> {@linkplain Annotation annotations} of the
 	 * supplied {@code annotationType} that are either <em>present</em>,
diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java
index a4d21d210871..4152d14e8220 100644
--- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java
@@ -10,7 +10,7 @@
 
 package org.junit.platform.commons.support;
 
-import static org.apiguardian.api.API.Status.STABLE;
+import static org.apiguardian.api.API.Status.DEPRECATED;
 
 import org.apiguardian.api.API;
 
@@ -20,8 +20,10 @@
  * @since 1.8
  * @see #DEFAULT
  * @see #INCLUDE_ENCLOSING_CLASSES
+ * @deprecated because there is only a single non-deprecated search option left
  */
-@API(status = STABLE, since = "1.10")
+@Deprecated
+@API(status = DEPRECATED, since = "1.12")
 public enum SearchOption {
 
 	/**
@@ -37,7 +39,11 @@ public enum SearchOption {
 	 * Search the inheritance hierarchy as with the {@link #DEFAULT} search option
 	 * but also search the {@linkplain Class#getEnclosingClass() enclosing class}
 	 * hierarchy for <em>inner classes</em> (i.e., a non-static member classes).
+	 *
+	 * @deprecated because it is preferable to inspect the runtime enclosing
+	 * types of a class rather than where they are declared.
 	 */
+	@Deprecated @API(status = DEPRECATED, since = "1.12")
 	INCLUDE_ENCLOSING_CLASSES
 
 }


Test patch:
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java
index c7a1fe90efc6..898dfc1ea9e8 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java
@@ -215,6 +215,16 @@ void indicativeSentencesRuntimeEnclosingType() {
 		);
 	}
 
+	@Test
+	void indicativeSentencesOnSubClass() {
+		check(IndicativeSentencesOnSubClassScenarioOneTestCase.class, //
+			"CONTAINER: IndicativeSentencesOnSubClassScenarioOneTestCase", //
+			"CONTAINER: IndicativeSentencesOnSubClassScenarioOneTestCase -> Level 1", //
+			"CONTAINER: IndicativeSentencesOnSubClassScenarioOneTestCase -> Level 1 -> Level 2", //
+			"TEST: IndicativeSentencesOnSubClassScenarioOneTestCase -> Level 1 -> Level 2 -> this is a test"//
+		);
+	}
+
 	private void check(Class<?> testClass, String... expectedDisplayNames) {
 		var request = request().selectors(selectClass(testClass)).build();
 		var descriptors = discoverTests(request).getDescendants();
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesOnSubClassScenarioOneTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesOnSubClassScenarioOneTestCase.java
new file mode 100644
index 000000000000..5ad3e99cdcce
--- /dev/null
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesOnSubClassScenarioOneTestCase.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2015-2025 the original author or authors.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package org.junit.jupiter.api;
+
+/**
+ * @since 5.12
+ */
+@IndicativeSentencesGeneration(separator = " -> ", generator = DisplayNameGenerator.ReplaceUnderscores.class)
+class IndicativeSentencesOnSubClassScenarioOneTestCase extends IndicativeSentencesOnSubClassTestCase {
+}
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesOnSubClassTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesOnSubClassTestCase.java
new file mode 100644
index 000000000000..1127970070d1
--- /dev/null
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesOnSubClassTestCase.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2015-2025 the original author or authors.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package org.junit.jupiter.api;
+
+/**
+ * @since 5.12
+ */
+@DisplayName("Base Scenario")
+abstract class IndicativeSentencesOnSubClassTestCase {
+
+	@Nested
+	class Level_1 {
+
+		@Nested
+		class Level_2 {
+
+			@Test
+			void this_is_a_test() {
+			}
+		}
+	}
+}
diff --git a/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java
index 88cf94db32ed..b882977606a1 100644
--- a/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java
+++ b/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java
@@ -20,6 +20,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.List;
 import java.util.Optional;
 
 import org.junit.jupiter.api.Tag;
@@ -80,16 +81,24 @@ void findAnnotationOnElementDelegates() {
 			AnnotationSupport.findAnnotation(element, Override.class));
 	}
 
+	@SuppressWarnings("deprecation")
 	@Test
-	void findAnnotationOnClassPreconditions() {
+	void findAnnotationOnClassWithSearchModePreconditions() {
 		assertPreconditionViolationException("annotationType",
 			() -> AnnotationSupport.findAnnotation(Probe.class, null, SearchOption.INCLUDE_ENCLOSING_CLASSES));
 		assertPreconditionViolationException("SearchOption",
-			() -> AnnotationSupport.findAnnotation(Probe.class, Override.class, null));
+			() -> AnnotationSupport.findAnnotation(Probe.class, Override.class, (SearchOption) null));
 	}
 
 	@Test
-	void findAnnotationOnClassDelegates() {
+	void findAnnotationOnClassWithEnclosingInstanceTypesPreconditions() {
+		assertPreconditionViolationException("enclosingInstanceTypes",
+			() -> AnnotationSupport.findAnnotation(Probe.class, Override.class, (List<Class<?>>) null));
+	}
+
+	@SuppressWarnings("deprecation")
+	@Test
+	void findAnnotationOnClassWithSearchModeDelegates() {
 		Class<?> clazz = Probe.class;
 		assertEquals(AnnotationUtils.findAnnotation(clazz, Tag.class, false),
 			AnnotationSupport.findAnnotation(clazz, Tag.class, SearchOption.DEFAULT));
@@ -111,6 +120,16 @@ void findAnnotationOnClassDelegates() {
 			AnnotationSupport.findAnnotation(clazz, Override.class, SearchOption.INCLUDE_ENCLOSING_CLASSES));
 	}
 
+	@Test
+	void findAnnotationOnClassWithEnclosingInstanceTypes() {
+		assertThat(AnnotationSupport.findAnnotation(Probe.class, Tag.class, List.of())) //
+				.contains(Probe.class.getDeclaredAnnotation(Tag.class));
+		assertThat(AnnotationSupport.findAnnotation(Probe.InnerClass.class, Tag.class, List.of())) //
+				.isEmpty();
+		assertThat(AnnotationSupport.findAnnotation(Probe.InnerClass.class, Tag.class, List.of(Probe.class))) //
+				.contains(Probe.class.getDeclaredAnnotation(Tag.class));
+	}
+
 	@Test
 	void findPublicAnnotatedFieldsPreconditions() {
 		assertPreconditionViolationException("Class",
@@ -301,6 +320,7 @@ void aMethod() {
 		void bMethod() {
 		}
 
+		@SuppressWarnings("InnerClassMayBeStatic")
 		class InnerClass {
 		}