Skip to content

Commit caf05bf

Browse files
authored
Avoid reporting discovery issues for composed @Nested annotations (#4684)
Fixes #4681.
1 parent 8d96621 commit caf05bf

File tree

7 files changed

+103
-16
lines changed

7 files changed

+103
-16
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ repository on GitHub.
3535
[[release-notes-5.13.3-junit-jupiter-bug-fixes]]
3636
==== Bug Fixes
3737

38-
* ❓
38+
* Stop reporting discovery issues for composed annotation classes that are meta-annotated
39+
with `@Nested`.
3940

4041
[[release-notes-5.13.3-junit-jupiter-deprecations-and-breaking-changes]]
4142
==== Deprecations and Breaking Changes

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/TestClassPredicates.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static org.junit.platform.commons.util.ReflectionUtils.isMethodPresent;
2020
import static org.junit.platform.commons.util.ReflectionUtils.isNestedClassPresent;
2121

22+
import java.lang.annotation.Annotation;
2223
import java.lang.reflect.Method;
2324
import java.util.HashSet;
2425
import java.util.Set;
@@ -42,8 +43,9 @@
4243
@API(status = INTERNAL, since = "5.13")
4344
public class TestClassPredicates {
4445

45-
public final Predicate<Class<?>> isAnnotatedWithNested = testClass -> isAnnotated(testClass, Nested.class);
46-
public final Predicate<Class<?>> isAnnotatedWithClassTemplate = testClass -> isAnnotated(testClass,
46+
public final Predicate<Class<?>> isAnnotatedWithNested = candidate -> isAnnotatedButNotComposed(candidate,
47+
Nested.class);
48+
public final Predicate<Class<?>> isAnnotatedWithClassTemplate = candidate -> isAnnotatedButNotComposed(candidate,
4749
ClassTemplate.class);
4850

4951
public final Predicate<Class<?>> isAnnotatedWithNestedAndValid = candidate -> this.isAnnotatedWithNested.test(
@@ -144,4 +146,8 @@ private static DiscoveryIssue createIssue(String prefix, Class<?> testClass, Str
144146
.source(ClassSource.from(testClass)) //
145147
.build();
146148
}
149+
150+
private static boolean isAnnotatedButNotComposed(Class<?> candidate, Class<? extends Annotation> annotationType) {
151+
return !candidate.isAnnotation() && isAnnotated(candidate, annotationType);
152+
}
147153
}

jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
package org.junit.jupiter.engine;
1212

1313
import static kotlin.jvm.JvmClassMappingKt.getJavaClass;
14+
import static org.assertj.core.api.Assertions.assertThat;
1415
import static org.junit.jupiter.api.Assertions.fail;
1516
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
1617
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod;
@@ -23,6 +24,7 @@
2324

2425
import org.junit.platform.engine.DiscoveryIssue.Severity;
2526
import org.junit.platform.engine.DiscoverySelector;
27+
import org.junit.platform.engine.TestDescriptor;
2628
import org.junit.platform.engine.UniqueId;
2729
import org.junit.platform.launcher.LauncherDiscoveryRequest;
2830
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
@@ -67,6 +69,12 @@ protected EngineExecutionResults executeTests(LauncherDiscoveryRequest request)
6769
return EngineTestKit.execute(this.engine, request);
6870
}
6971

72+
protected TestDescriptor discoverTestsWithoutIssues(LauncherDiscoveryRequest request) {
73+
var results = discoverTests(request);
74+
assertThat(results.getDiscoveryIssues()).isEmpty();
75+
return results.getEngineDescriptor();
76+
}
77+
7078
protected EngineDiscoveryResults discoverTestsForClass(Class<?> testClass) {
7179
return discoverTests(selectClass(testClass));
7280
}

jupiter-tests/src/test/java/org/junit/jupiter/engine/ClassTemplateInvocationTests.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor;
8989
import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor;
9090
import org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor;
91+
import org.junit.jupiter.params.ParameterizedClass;
9192
import org.junit.jupiter.params.ParameterizedTest;
9293
import org.junit.jupiter.params.provider.ValueSource;
9394
import org.junit.platform.engine.TestTag;
@@ -979,8 +980,10 @@ void templateWithPreparations() {
979980

980981
@Test
981982
void propagatesTagsFromEnclosingClassesToNestedClassTemplates() {
982-
var engineDescriptor = discoverTestsForClass(
983-
NestedClassTemplateWithTagOnEnclosingClassTestCase.class).getEngineDescriptor();
983+
var request = defaultRequest() //
984+
.selectors(selectClass(NestedClassTemplateWithTagOnEnclosingClassTestCase.class)) //
985+
.build();
986+
var engineDescriptor = discoverTestsWithoutIssues(request);
984987
var classDescriptor = getOnlyElement(engineDescriptor.getChildren());
985988
var nestedClassTemplateDescriptor = getOnlyElement(classDescriptor.getChildren());
986989

@@ -990,6 +993,17 @@ void propagatesTagsFromEnclosingClassesToNestedClassTemplates() {
990993
.containsExactlyInAnyOrder("top-level", "nested");
991994
}
992995

996+
@Test
997+
void ignoresComposedAnnotations() {
998+
var request = defaultRequest() //
999+
.selectors(selectClass(ParameterizedClass.class)) //
1000+
.build();
1001+
1002+
var engineDescriptor = discoverTestsWithoutIssues(request);
1003+
1004+
assertThat(engineDescriptor.getDescendants()).isEmpty();
1005+
}
1006+
9931007
// -------------------------------------------------------------------
9941008

9951009
private static Stream<String> allReportEntryValues(EngineExecutionResults results) {

jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@
1919
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage;
2020
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId;
2121
import static org.junit.platform.launcher.LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME;
22-
import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request;
2322
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
2423
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;
2524

25+
import java.lang.annotation.ElementType;
26+
import java.lang.annotation.Retention;
27+
import java.lang.annotation.RetentionPolicy;
28+
import java.lang.annotation.Target;
2629
import java.util.List;
2730
import java.util.function.Consumer;
2831
import java.util.regex.Pattern;
@@ -38,6 +41,7 @@
3841
import org.junit.jupiter.engine.NestedTestClassesTests.OuterClass.NestedClass.RecursiveNestedSiblingClass;
3942
import org.junit.jupiter.params.ParameterizedTest;
4043
import org.junit.jupiter.params.provider.MethodSource;
44+
import org.junit.jupiter.params.provider.ValueSource;
4145
import org.junit.platform.engine.DiscoveryIssue.Severity;
4246
import org.junit.platform.engine.TestDescriptor;
4347
import org.junit.platform.engine.support.descriptor.ClassSource;
@@ -56,8 +60,8 @@ class NestedTestClassesTests extends AbstractJupiterTestEngineTests {
5660

5761
@Test
5862
void nestedTestsAreCorrectlyDiscovered() {
59-
LauncherDiscoveryRequest request = request().selectors(selectClass(TestCaseWithNesting.class)).build();
60-
TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor();
63+
LauncherDiscoveryRequest request = defaultRequest().selectors(selectClass(TestCaseWithNesting.class)).build();
64+
TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request);
6165
assertEquals(5, engineDescriptor.getDescendants().size(), "# resolved test descriptors");
6266
}
6367

@@ -93,8 +97,9 @@ static List<Named<Consumer<LauncherDiscoveryRequestBuilder>>> nestedTestsAreExec
9397

9498
@Test
9599
void doublyNestedTestsAreCorrectlyDiscovered() {
96-
LauncherDiscoveryRequest request = request().selectors(selectClass(TestCaseWithDoubleNesting.class)).build();
97-
TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor();
100+
LauncherDiscoveryRequest request = defaultRequest().selectors(
101+
selectClass(TestCaseWithDoubleNesting.class)).build();
102+
TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request);
98103
assertEquals(8, engineDescriptor.getDescendants().size(), "# resolved test descriptors");
99104
}
100105

@@ -245,6 +250,22 @@ void doesNotReportDiscoveryIssueForAbstractInnerClass() {
245250
assertThat(discoveryIssues).isEmpty();
246251
}
247252

253+
@Test
254+
void nestedTestsWithCustomAnnotationAreCorrectlyDiscovered() {
255+
LauncherDiscoveryRequest request = defaultRequest().selectors(
256+
selectClass(CustomAnnotationTestCase.class)).build();
257+
TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request);
258+
assertEquals(3, engineDescriptor.getDescendants().size(), "# resolved test descriptors");
259+
}
260+
261+
@ParameterizedTest
262+
@ValueSource(classes = { TopLevelComposedNested.class, CustomAnnotationTestCase.MyNested.class })
263+
void ignoresComposedAnnotations(Class<?> annotationType) {
264+
LauncherDiscoveryRequest request = defaultRequest().selectors(selectClass(annotationType)).build();
265+
TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request);
266+
assertEquals(0, engineDescriptor.getDescendants().size(), "# resolved test descriptors");
267+
}
268+
248269
private void assertNestedCycle(Class<?> start, Class<?> from, Class<?> to) {
249270
var results = executeTestsForClass(start);
250271
var expectedMessage = "Cause: org.junit.platform.commons.JUnitException: Detected cycle in inner class hierarchy between %s and %s".formatted(
@@ -450,4 +471,23 @@ class NestedTests extends AbstractInnerClass {
450471
}
451472
}
452473

474+
static class CustomAnnotationTestCase {
475+
476+
@Nested
477+
@Retention(RetentionPolicy.RUNTIME)
478+
@Target(ElementType.TYPE)
479+
@interface MyNested {
480+
}
481+
482+
@SuppressWarnings({ "JUnitMalformedDeclaration", "InnerClassMayBeStatic" })
483+
@MyNested
484+
class Inner {
485+
486+
@Test
487+
void test() {
488+
489+
}
490+
}
491+
}
492+
453493
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2015-2025 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.jupiter.engine;
12+
13+
import java.lang.annotation.ElementType;
14+
import java.lang.annotation.Retention;
15+
import java.lang.annotation.RetentionPolicy;
16+
import java.lang.annotation.Target;
17+
18+
import org.junit.jupiter.api.Nested;
19+
20+
@Nested
21+
@Retention(RetentionPolicy.RUNTIME)
22+
@Target(ElementType.TYPE)
23+
public @interface TopLevelComposedNested {
24+
}

jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -361,12 +361,6 @@ void reportsWarningsForBlankDisplayNames() throws Exception {
361361
.contains(org.junit.platform.engine.support.descriptor.MethodSource.from(method));
362362
}
363363

364-
private TestDescriptor discoverTestsWithoutIssues(LauncherDiscoveryRequest request) {
365-
var results = super.discoverTests(request);
366-
assertThat(results.getDiscoveryIssues()).isEmpty();
367-
return results.getEngineDescriptor();
368-
}
369-
370364
// -------------------------------------------------------------------
371365

372366
@SuppressWarnings("unused")

0 commit comments

Comments
 (0)