Skip to content

Commit 11ddd87

Browse files
committed
Avoid inner class cycle detection for non-matching predicate
JUnit 5.6 introduced inner class cycle detection in ReflectionSupport.findNestedClasses() (see #2039). Unfortunately, the cycle detection introduced a regression for test classes that contain classes with inner class cycles when those inner classes do not match the predicate supplied to findNestedClasses() (e.g., "is annotated with @nested"). Consequently, an exception is thrown for any inner class cycled detected, even if the cycle would not adversely affect test discovery or execution. This commit fixes this regression by avoiding inner class cycle detection for candidate classes that do match the predicate. For example, within JUnit Jupiter inner class cycle detection is now only performed for inner classes annotated with @nested. Fixes #2249
1 parent 5cbfed5 commit 11ddd87

File tree

4 files changed

+53
-12
lines changed

4 files changed

+53
-12
lines changed

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
*Scope:* Bug fixes since 5.6.1
77

8-
For a complete list of all _closed_ issues and pull requests for this release, consult
9-
the link:{junit5-repo}+/milestone/48?closed=1+[5.6.2] milestone page in the JUnit repository
8+
For a complete list of all _closed_ issues and pull requests for this release, consult the
9+
link:{junit5-repo}+/milestone/48?closed=1+[5.6.2] milestone page in the JUnit repository
1010
on GitHub.
1111

1212

@@ -15,15 +15,19 @@ on GitHub.
1515

1616
==== Bug Fixes
1717

18-
* ❓
18+
* `ReflectionSupport.findNestedClasses()` no longer detects inner class cycles for classes
19+
that do not match the supplied `Predicate`. For example, JUnit Jupiter no longer throws
20+
an exception if an inner class cycle is detected in a nested class hierarchy whose inner
21+
classes are not annotated with `@Nested`.
1922

2023

2124
[[release-notes-5.6.2-junit-jupiter]]
2225
=== JUnit Jupiter
2326

2427
==== Bug Fixes
2528

26-
* ❓
29+
* Test discovery no longer halts with an exception for inner class hierarchies that form a
30+
cycle if such inner classes are not annotated with `@Nested`.
2731

2832

2933
[[release-notes-5.6.2-junit-vintage]]

junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTestsTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,15 @@ void privateStaticTestClassEvaluatesToFalse() {
8484
assertFalse(isTestClassWithTests.test(PrivateStaticTestCase.class));
8585
}
8686

87+
/**
88+
* @see https://github.com/junit-team/junit5/issues/2249
89+
*/
90+
@Test
91+
void recursiveHierarchies() {
92+
assertTrue(isTestClassWithTests.test(OuterClass.class));
93+
assertFalse(isTestClassWithTests.test(OuterClass.RecursiveInnerClass.class));
94+
}
95+
8796
// -------------------------------------------------------------------------
8897

8998
private class PrivateClassWithTestMethod {
@@ -143,6 +152,22 @@ void test() {
143152
}
144153
}
145154

155+
static class OuterClass {
156+
157+
@Nested
158+
class InnerClass {
159+
160+
@Test
161+
void test() {
162+
}
163+
}
164+
165+
// Intentionally commented out so that RecursiveInnerClass is NOT a candidate test class
166+
// @Nested
167+
class RecursiveInnerClass extends OuterClass {
168+
}
169+
}
170+
146171
}
147172

148173
// -----------------------------------------------------------------------------

junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,34 +1004,38 @@ public static List<Class<?>> findNestedClasses(Class<?> clazz, Predicate<Class<?
10041004
Preconditions.notNull(predicate, "Predicate must not be null");
10051005

10061006
Set<Class<?>> candidates = new LinkedHashSet<>();
1007-
findNestedClasses(clazz, candidates);
1008-
return candidates.stream().filter(predicate).collect(toUnmodifiableList());
1007+
findNestedClasses(clazz, predicate, candidates);
1008+
return Collections.unmodifiableList(new ArrayList<>(candidates));
10091009
}
10101010

1011-
private static void findNestedClasses(Class<?> clazz, Set<Class<?>> candidates) {
1011+
private static void findNestedClasses(Class<?> clazz, Predicate<Class<?>> predicate, Set<Class<?>> candidates) {
10121012
if (!isSearchable(clazz)) {
10131013
return;
10141014
}
10151015

1016-
detectInnerClassCycle(clazz);
1016+
if (isInnerClass(clazz) && predicate.test(clazz)) {
1017+
detectInnerClassCycle(clazz);
1018+
}
10171019

10181020
try {
10191021
// Candidates in current class
10201022
for (Class<?> nestedClass : clazz.getDeclaredClasses()) {
1021-
detectInnerClassCycle(nestedClass);
1022-
candidates.add(nestedClass);
1023+
if (predicate.test(nestedClass)) {
1024+
detectInnerClassCycle(nestedClass);
1025+
candidates.add(nestedClass);
1026+
}
10231027
}
10241028
}
10251029
catch (NoClassDefFoundError error) {
10261030
logger.debug(error, () -> "Failed to retrieve declared classes for " + clazz.getName());
10271031
}
10281032

10291033
// Search class hierarchy
1030-
findNestedClasses(clazz.getSuperclass(), candidates);
1034+
findNestedClasses(clazz.getSuperclass(), predicate, candidates);
10311035

10321036
// Search interface hierarchy
10331037
for (Class<?> ifc : clazz.getInterfaces()) {
1034-
findNestedClasses(ifc, candidates);
1038+
findNestedClasses(ifc, predicate, candidates);
10351039
}
10361040
}
10371041

platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,14 @@ void findNestedClassesWithSeeminglyRecursiveHierarchies() {
690690
assertThat(findNestedClasses(AbstractOuterClass.class))//
691691
.containsExactly(AbstractOuterClass.InnerClass.class);
692692

693+
// OuterClass contains recursive hierarchies, but the non-matching
694+
// predicate should prevent cycle detection.
695+
// See https://github.com/junit-team/junit5/issues/2249
696+
assertThat(ReflectionUtils.findNestedClasses(OuterClass.class, clazz -> false)).isEmpty();
697+
// RecursiveInnerInnerClass is part of a recursive hierarchy, but the non-matching
698+
// predicate should prevent cycle detection.
699+
assertThat(ReflectionUtils.findNestedClasses(RecursiveInnerInnerClass.class, clazz -> false)).isEmpty();
700+
693701
// Sibling types don't actually result in cycles.
694702
assertThat(findNestedClasses(StaticNestedSiblingClass.class))//
695703
.containsExactly(AbstractOuterClass.InnerClass.class);

0 commit comments

Comments
 (0)