Skip to content

Commit 11d9969

Browse files
committed
Optimize search for existence of @Nested test classes
1 parent 1f1556a commit 11d9969

File tree

3 files changed

+93
-10
lines changed

3 files changed

+93
-10
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515
import static org.junit.platform.commons.support.ModifierSupport.isAbstract;
1616
import static org.junit.platform.commons.support.ModifierSupport.isNotAbstract;
1717
import static org.junit.platform.commons.support.ModifierSupport.isNotPrivate;
18+
import static org.junit.platform.commons.util.ReflectionUtils.isMethodPresent;
19+
import static org.junit.platform.commons.util.ReflectionUtils.isNestedClassPresent;
1820

1921
import java.lang.reflect.Method;
2022
import java.util.function.Predicate;
2123

2224
import org.apiguardian.api.API;
2325
import org.junit.jupiter.api.ClassTemplate;
2426
import org.junit.jupiter.api.Nested;
25-
import org.junit.platform.commons.support.ReflectionSupport;
2627
import org.junit.platform.commons.util.ReflectionUtils;
2728
import org.junit.platform.engine.DiscoveryIssue;
2829
import org.junit.platform.engine.support.descriptor.ClassSource;
@@ -79,12 +80,11 @@ public boolean isValidStandaloneTestClass(Class<?> candidate) {
7980
}
8081

8182
private boolean hasTestOrTestFactoryOrTestTemplateMethods(Class<?> candidate) {
82-
return ReflectionUtils.isMethodPresent(candidate, this.isTestOrTestFactoryOrTestTemplateMethod);
83+
return isMethodPresent(candidate, this.isTestOrTestFactoryOrTestTemplateMethod);
8384
}
8485

8586
private boolean hasNestedTests(Class<?> candidate) {
86-
// TODO [#242] Add ReflectionSupport.isNestedClassPresent(candidate, TestClassPredicates::isValidNestedTestClass)
87-
return !ReflectionSupport.findNestedClasses(candidate, this.isAnnotatedWithNestedAndValid).isEmpty();
87+
return isNestedClassPresent(candidate, this.isAnnotatedWithNestedAndValid);
8888
}
8989

9090
private static Condition<Class<?>> isNotPrivateUnlessAbstract(String prefix, DiscoveryIssueReporter issueReporter) {

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

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,10 +1236,35 @@ public static List<Class<?>> findNestedClasses(Class<?> clazz, Predicate<Class<?
12361236
Preconditions.notNull(predicate, "Predicate must not be null");
12371237

12381238
Set<Class<?>> candidates = new LinkedHashSet<>();
1239-
findNestedClasses(clazz, predicate, candidates);
1239+
visitNestedClasses(clazz, predicate, nestedClass -> {
1240+
candidates.add(nestedClass);
1241+
return true;
1242+
});
12401243
return Collections.unmodifiableList(new ArrayList<>(candidates));
12411244
}
12421245

1246+
/**
1247+
* Determine if a nested class within the supplied class, or inherited by the
1248+
* supplied class, that conforms to the supplied predicate is present.
1249+
*
1250+
* <p>This method does <strong>not</strong> search for nested classes
1251+
* recursively.
1252+
*
1253+
* @param clazz the class to be searched; never {@code null}
1254+
* @param predicate the predicate against which the list of nested classes is
1255+
* checked; never {@code null}
1256+
* @return {@code true} if such a nested class is present
1257+
* @throws JUnitException if a cycle is detected within an inner class hierarchy
1258+
*/
1259+
@API(status = INTERNAL, since = "1.13")
1260+
public static boolean isNestedClassPresent(Class<?> clazz, Predicate<Class<?>> predicate) {
1261+
Preconditions.notNull(clazz, "Class must not be null");
1262+
Preconditions.notNull(predicate, "Predicate must not be null");
1263+
1264+
boolean visitorWasNotCalled = visitNestedClasses(clazz, predicate, __ -> false);
1265+
return !visitorWasNotCalled;
1266+
}
1267+
12431268
/**
12441269
* since 1.10
12451270
* @see org.junit.platform.commons.support.ReflectionSupport#streamNestedClasses(Class, Predicate)
@@ -1248,9 +1273,10 @@ public static Stream<Class<?>> streamNestedClasses(Class<?> clazz, Predicate<Cla
12481273
return findNestedClasses(clazz, predicate).stream();
12491274
}
12501275

1251-
private static void findNestedClasses(Class<?> clazz, Predicate<Class<?>> predicate, Set<Class<?>> candidates) {
1276+
private static boolean visitNestedClasses(Class<?> clazz, Predicate<Class<?>> predicate,
1277+
Visitor<Class<?>> visitor) {
12521278
if (!isSearchable(clazz)) {
1253-
return;
1279+
return true;
12541280
}
12551281

12561282
if (isInnerClass(clazz) && predicate.test(clazz)) {
@@ -1262,7 +1288,10 @@ private static void findNestedClasses(Class<?> clazz, Predicate<Class<?>> predic
12621288
for (Class<?> nestedClass : clazz.getDeclaredClasses()) {
12631289
if (predicate.test(nestedClass)) {
12641290
detectInnerClassCycle(nestedClass);
1265-
candidates.add(nestedClass);
1291+
boolean shouldContinue = visitor.accept(nestedClass);
1292+
if (!shouldContinue) {
1293+
return false;
1294+
}
12661295
}
12671296
}
12681297
}
@@ -1271,12 +1300,20 @@ private static void findNestedClasses(Class<?> clazz, Predicate<Class<?>> predic
12711300
}
12721301

12731302
// Search class hierarchy
1274-
findNestedClasses(clazz.getSuperclass(), predicate, candidates);
1303+
boolean shouldContinue = visitNestedClasses(clazz.getSuperclass(), predicate, visitor);
1304+
if (!shouldContinue) {
1305+
return false;
1306+
}
12751307

12761308
// Search interface hierarchy
12771309
for (Class<?> ifc : clazz.getInterfaces()) {
1278-
findNestedClasses(ifc, predicate, candidates);
1310+
shouldContinue = visitNestedClasses(ifc, predicate, visitor);
1311+
if (!shouldContinue) {
1312+
return false;
1313+
}
12791314
}
1315+
1316+
return true;
12801317
}
12811318

12821319
/**
@@ -2073,4 +2110,14 @@ private static boolean getLegacySearchSemanticsFlag() {
20732110
return isTrue;
20742111
}
20752112

2113+
private interface Visitor<T> {
2114+
2115+
/**
2116+
* @return {@code true} if the visitor should continue searching;
2117+
* {@code false} if the visitor should stop
2118+
*/
2119+
boolean accept(T value);
2120+
2121+
}
2122+
20762123
}

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,24 +1036,43 @@ void findNestedClassesPreconditions() {
10361036
// @formatter:on
10371037
}
10381038

1039+
@Test
1040+
void isNestedClassPresentPreconditions() {
1041+
// @formatter:off
1042+
assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.isNestedClassPresent(null, null));
1043+
assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.isNestedClassPresent(null, clazz -> true));
1044+
assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.isNestedClassPresent(getClass(), null));
1045+
// @formatter:on
1046+
}
1047+
10391048
@Test
10401049
void findNestedClasses() {
10411050
// @formatter:off
10421051
assertThat(findNestedClasses(Object.class)).isEmpty();
1052+
assertThat(isNestedClassPresent(Object.class)).isFalse();
10431053

10441054
assertThat(findNestedClasses(ClassWithNestedClasses.class))
10451055
.containsOnly(Nested1.class, Nested2.class, Nested3.class);
1056+
assertThat(isNestedClassPresent(ClassWithNestedClasses.class))
1057+
.isTrue();
10461058

10471059
assertThat(ReflectionUtils.findNestedClasses(ClassWithNestedClasses.class, clazz -> clazz.getName().contains("1")))
10481060
.containsExactly(Nested1.class);
1061+
assertThat(ReflectionUtils.isNestedClassPresent(ClassWithNestedClasses.class, clazz -> clazz.getName().contains("1")))
1062+
.isTrue();
10491063

10501064
assertThat(ReflectionUtils.findNestedClasses(ClassWithNestedClasses.class, ReflectionUtils::isStatic))
10511065
.containsExactly(Nested3.class);
1066+
assertThat(ReflectionUtils.isNestedClassPresent(ClassWithNestedClasses.class, ReflectionUtils::isStatic))
1067+
.isTrue();
10521068

10531069
assertThat(findNestedClasses(ClassExtendingClassWithNestedClasses.class))
10541070
.containsOnly(Nested1.class, Nested2.class, Nested3.class, Nested4.class, Nested5.class);
1071+
assertThat(isNestedClassPresent(ClassExtendingClassWithNestedClasses.class))
1072+
.isTrue();
10551073

10561074
assertThat(findNestedClasses(ClassWithNestedClasses.Nested1.class)).isEmpty();
1075+
assertThat(isNestedClassPresent(ClassWithNestedClasses.Nested1.class)).isFalse();
10571076
// @formatter:on
10581077
}
10591078

@@ -1064,26 +1083,39 @@ void findNestedClasses() {
10641083
void findNestedClassesWithSeeminglyRecursiveHierarchies() {
10651084
assertThat(findNestedClasses(AbstractOuterClass.class))//
10661085
.containsExactly(AbstractOuterClass.InnerClass.class);
1086+
assertThat(isNestedClassPresent(AbstractOuterClass.class))//
1087+
.isTrue();
10671088

10681089
// OuterClass contains recursive hierarchies, but the non-matching
10691090
// predicate should prevent cycle detection.
10701091
// See https://github.com/junit-team/junit5/issues/2249
10711092
assertThat(ReflectionUtils.findNestedClasses(OuterClass.class, clazz -> false)).isEmpty();
1093+
assertThat(ReflectionUtils.isNestedClassPresent(OuterClass.class, clazz -> false)).isFalse();
1094+
10721095
// RecursiveInnerInnerClass is part of a recursive hierarchy, but the non-matching
10731096
// predicate should prevent cycle detection.
10741097
assertThat(ReflectionUtils.findNestedClasses(RecursiveInnerInnerClass.class, clazz -> false)).isEmpty();
1098+
assertThat(ReflectionUtils.isNestedClassPresent(RecursiveInnerInnerClass.class, clazz -> false)).isFalse();
10751099

10761100
// Sibling types don't actually result in cycles.
10771101
assertThat(findNestedClasses(StaticNestedSiblingClass.class))//
10781102
.containsExactly(AbstractOuterClass.InnerClass.class);
1103+
assertThat(isNestedClassPresent(StaticNestedSiblingClass.class))//
1104+
.isTrue();
10791105
assertThat(findNestedClasses(InnerSiblingClass.class))//
10801106
.containsExactly(AbstractOuterClass.InnerClass.class);
1107+
assertThat(isNestedClassPresent(InnerSiblingClass.class))//
1108+
.isTrue();
10811109

10821110
// Interfaces with static nested classes
10831111
assertThat(findNestedClasses(OuterClassImplementingInterface.class))//
10841112
.containsExactly(InnerClassImplementingInterface.class, Nested4.class);
1113+
assertThat(isNestedClassPresent(OuterClassImplementingInterface.class))//
1114+
.isTrue();
10851115
assertThat(findNestedClasses(InnerClassImplementingInterface.class))//
10861116
.containsExactly(Nested4.class);
1117+
assertThat(isNestedClassPresent(InnerClassImplementingInterface.class))//
1118+
.isTrue();
10871119
}
10881120

10891121
/**
@@ -1105,6 +1137,10 @@ private static List<Class<?>> findNestedClasses(Class<?> clazz) {
11051137
return ReflectionUtils.findNestedClasses(clazz, c -> true);
11061138
}
11071139

1140+
private static boolean isNestedClassPresent(Class<?> clazz) {
1141+
return ReflectionUtils.isNestedClassPresent(clazz, c -> true);
1142+
}
1143+
11081144
private void assertNestedCycle(Class<?> from, Class<?> to) {
11091145
assertNestedCycle(from, from, to);
11101146
}

0 commit comments

Comments
 (0)