Skip to content

Commit cb577cf

Browse files
authored
Order nested classes declared in same enclosing type deterministically (#4839)
* The methods `findNestedClasses` and `streamNestedClasses` in `ReflectionSupport` now return nested classes declared in the same enclosing class or interface ordered in a deterministic but intentionally nonobvious way. * For consistency with test methods, `@Nested` classes declared in the same enclosing class or interface are now ordered in a deterministic but intentionally nonobvious way.
1 parent 8307cd4 commit cb577cf

File tree

4 files changed

+60
-3
lines changed

4 files changed

+60
-3
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC1.adoc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ repository on GitHub.
2121
[[release-notes-6.0.0-RC1-junit-platform-deprecations-and-breaking-changes]]
2222
==== Deprecations and Breaking Changes
2323

24-
* ❓
24+
* The methods `findNestedClasses` and `streamNestedClasses` in `ReflectionSupport` now
25+
return nested classes declared in the same enclosing class or interface ordered in a
26+
deterministic but intentionally nonobvious way.
2527

2628
[[release-notes-6.0.0-RC1-junit-platform-new-features-and-improvements]]
2729
==== New Features and Improvements
@@ -47,7 +49,9 @@ repository on GitHub.
4749
[[release-notes-6.0.0-RC1-junit-jupiter-deprecations-and-breaking-changes]]
4850
==== Deprecations and Breaking Changes
4951

50-
* ❓
52+
* For consistency with test methods, `@Nested` classes declared in the same enclosing
53+
class or interface are now ordered in a deterministic but intentionally nonobvious
54+
way.
5155

5256
[[release-notes-6.0.0-RC1-junit-jupiter-new-features-and-improvements]]
5357
==== New Features and Improvements

junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,10 @@ public static Stream<Method> streamMethods(Class<?> clazz, Predicate<Method> pre
632632
* <p>This method does <strong>not</strong> search for nested classes
633633
* recursively.
634634
*
635+
* <p>Nested classes declared in the same enclosing class or interface will
636+
* be ordered using an algorithm that is deterministic but intentionally
637+
* nonobvious.
638+
*
635639
* <p>As of JUnit Platform 1.6, this method detects cycles in <em>inner</em>
636640
* class hierarchies &mdash; from the supplied class up to the outermost
637641
* enclosing class &mdash; and throws a {@link JUnitException} if such a cycle
@@ -658,6 +662,10 @@ public static List<Class<?>> findNestedClasses(Class<?> clazz, Predicate<Class<?
658662
* <p>This method does <strong>not</strong> search for nested classes
659663
* recursively.
660664
*
665+
* <p>Nested classes declared in the same enclosing class or interface will
666+
* be ordered using an algorithm that is deterministic but intentionally
667+
* nonobvious.
668+
*
661669
* <p>As of JUnit Platform 1.6, this method detects cycles in <em>inner</em>
662670
* class hierarchies &mdash; from the supplied class up to the outermost
663671
* enclosing class &mdash; and throws a {@link JUnitException} if such a cycle

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1182,7 +1182,7 @@ private static void visitAllNestedClasses(Class<?> clazz, Predicate<Class<?>> pr
11821182

11831183
try {
11841184
// Candidates in current class
1185-
for (Class<?> nestedClass : clazz.getDeclaredClasses()) {
1185+
for (Class<?> nestedClass : toSortedMutableList(clazz.getDeclaredClasses())) {
11861186
if (predicate.test(nestedClass)) {
11871187
consumer.accept(nestedClass);
11881188
if (detectInnerClassCycle(nestedClass, errorHandling)) {
@@ -1698,6 +1698,10 @@ private static List<Method> toSortedMutableList(Method[] methods) {
16981698
return toSortedMutableList(methods, ReflectionUtils::defaultMethodSorter);
16991699
}
17001700

1701+
private static List<Class<?>> toSortedMutableList(Class<?>[] fields) {
1702+
return toSortedMutableList(fields, ReflectionUtils::defaultClassSorter);
1703+
}
1704+
17011705
private static <T> List<T> toSortedMutableList(T[] items, Comparator<? super T> comparator) {
17021706
List<T> result = new ArrayList<>(items.length);
17031707
Collections.addAll(result, items);
@@ -1730,6 +1734,19 @@ private static int defaultMethodSorter(Method method1, Method method2) {
17301734
return comparison;
17311735
}
17321736

1737+
/**
1738+
* Class comparator to achieve deterministic but nonobvious order.
1739+
*/
1740+
private static int defaultClassSorter(Class<?> class1, Class<?> class2) {
1741+
String name1 = class1.getName();
1742+
String name2 = class2.getName();
1743+
int comparison = Integer.compare(name1.hashCode(), name2.hashCode());
1744+
if (comparison == 0) {
1745+
comparison = name1.compareTo(name2);
1746+
}
1747+
return comparison;
1748+
}
1749+
17331750
private static List<Method> getInterfaceMethods(Class<?> clazz, HierarchyTraversalMode traversalMode) {
17341751
List<Method> allInterfaceMethods = new ArrayList<>();
17351752
for (Class<?> ifc : clazz.getInterfaces()) {

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,15 @@ void findNestedClassesWithRecursiveHierarchies() {
11521152
runnable4, runnable4, runnable4, runnable5, runnable5, runnable5).parallel().forEach(Runnable::run);
11531153
}
11541154

1155+
@Test
1156+
void findNestedClassesWithMultipleNestedClasses() {
1157+
var nestedClasses = findNestedClasses(OuterClassWithMultipleNestedClasses.class);
1158+
1159+
assertThat(nestedClasses) //
1160+
.map(Class::getSimpleName) //
1161+
.containsExactly("Beta", "Zeta", "Eta", "Alpha", "Delta", "Gamma", "Theta", "Epsilon");
1162+
}
1163+
11551164
private static List<Class<?>> findNestedClasses(Class<?> clazz) {
11561165
return ReflectionUtils.findNestedClasses(clazz, c -> true);
11571166
}
@@ -2326,4 +2335,23 @@ class InnerClassImplementingInterface implements InterfaceWithNestedClass {
23262335
}
23272336
}
23282337

2338+
static class OuterClassWithMultipleNestedClasses {
2339+
class Alpha {
2340+
}
2341+
class Beta {
2342+
}
2343+
class Gamma {
2344+
}
2345+
class Delta {
2346+
}
2347+
class Epsilon {
2348+
}
2349+
class Zeta {
2350+
}
2351+
class Eta {
2352+
}
2353+
class Theta {
2354+
}
2355+
}
2356+
23292357
}

0 commit comments

Comments
 (0)