1010
1111package org .junit .jupiter .engine .discovery ;
1212
13+ import static java .util .Comparator .comparing ;
1314import static java .util .stream .Collectors .toCollection ;
15+ import static java .util .stream .Collectors .toList ;
1416
1517import java .util .ArrayList ;
16- import java .util .LinkedHashSet ;
18+ import java .util .HashMap ;
1719import java .util .List ;
18- import java .util .Set ;
20+ import java .util .Map ;
1921import java .util .function .Consumer ;
2022import java .util .function .Function ;
21- import java .util .stream .Collectors ;
2223import java .util .stream .Stream ;
2324
2425import org .junit .jupiter .engine .descriptor .ClassBasedTestDescriptor ;
@@ -60,9 +61,8 @@ protected void doWithMatchingDescriptor(Class<PARENT> parentTestDescriptorType,
6061 protected void orderChildrenTestDescriptors (TestDescriptor parentTestDescriptor , Class <CHILD > matchingChildrenType ,
6162 Function <CHILD , WRAPPER > descriptorWrapperFactory , DescriptorWrapperOrderer descriptorWrapperOrderer ) {
6263
63- Set <? extends TestDescriptor > children = parentTestDescriptor .getChildren ();
64-
65- List <WRAPPER > matchingDescriptorWrappers = children .stream ()//
64+ List <WRAPPER > matchingDescriptorWrappers = parentTestDescriptor .getChildren ()//
65+ .stream ()//
6666 .filter (matchingChildrenType ::isInstance )//
6767 .map (matchingChildrenType ::cast )//
6868 .map (descriptorWrapperFactory )//
@@ -74,50 +74,33 @@ protected void orderChildrenTestDescriptors(TestDescriptor parentTestDescriptor,
7474 }
7575
7676 if (descriptorWrapperOrderer .canOrderWrappers ()) {
77- List <TestDescriptor > nonMatchingTestDescriptors = children .stream ()//
78- .filter (childTestDescriptor -> !matchingChildrenType .isInstance (childTestDescriptor ))//
79- .collect (Collectors .toList ());
80-
81- // Make a local copy for later validation
82- Set <WRAPPER > originalWrappers = new LinkedHashSet <>(matchingDescriptorWrappers );
83-
84- descriptorWrapperOrderer .orderWrappers (matchingDescriptorWrappers );
85-
86- int difference = matchingDescriptorWrappers .size () - originalWrappers .size ();
87- if (difference > 0 ) {
88- descriptorWrapperOrderer .logDescriptorsAddedWarning (difference );
89- }
90- else if (difference < 0 ) {
91- descriptorWrapperOrderer .logDescriptorsRemovedWarning (difference );
92- }
93-
94- Set <TestDescriptor > orderedTestDescriptors = matchingDescriptorWrappers .stream ()//
95- .filter (originalWrappers ::contains )//
96- .map (AbstractAnnotatedDescriptorWrapper ::getTestDescriptor )//
97- .collect (toCollection (LinkedHashSet ::new ));
98-
99- // There is currently no way to removeAll or addAll children at once, so we
100- // first remove them all and then add them all back.
101- Stream .concat (orderedTestDescriptors .stream (), nonMatchingTestDescriptors .stream ())//
102- .forEach (parentTestDescriptor ::removeChild );
103-
104- // If we are ordering children of type ClassBasedTestDescriptor, that means we
105- // are ordering top-level classes or @Nested test classes. Thus, the
106- // nonMatchingTestDescriptors list is either empty (for top-level classes) or
107- // contains only local test methods (for @Nested classes) which must be executed
108- // before tests in @Nested test classes. So we add the test methods before adding
109- // the @Nested test classes.
110- if (matchingChildrenType == ClassBasedTestDescriptor .class ) {
111- Stream .concat (nonMatchingTestDescriptors .stream (), orderedTestDescriptors .stream ())//
112- .forEach (parentTestDescriptor ::addChild );
113- }
114- // Otherwise, we add the ordered descriptors before the non-matching descriptors,
115- // which is the case when we are ordering test methods. In other words, local
116- // test methods always get added before @Nested test classes.
117- else {
118- Stream .concat (orderedTestDescriptors .stream (), nonMatchingTestDescriptors .stream ())//
119- .forEach (parentTestDescriptor ::addChild );
120- }
77+ parentTestDescriptor .orderChildren (children -> {
78+ Stream <TestDescriptor > nonMatchingTestDescriptors = children .stream ()//
79+ .filter (childTestDescriptor -> !matchingChildrenType .isInstance (childTestDescriptor ));
80+
81+ descriptorWrapperOrderer .orderWrappers (matchingDescriptorWrappers );
82+
83+ Stream <TestDescriptor > orderedTestDescriptors = matchingDescriptorWrappers .stream ()//
84+ .map (AbstractAnnotatedDescriptorWrapper ::getTestDescriptor );
85+
86+ // If we are ordering children of type ClassBasedTestDescriptor, that means we
87+ // are ordering top-level classes or @Nested test classes. Thus, the
88+ // nonMatchingTestDescriptors list is either empty (for top-level classes) or
89+ // contains only local test methods (for @Nested classes) which must be executed
90+ // before tests in @Nested test classes. So we add the test methods before adding
91+ // the @Nested test classes.
92+ if (matchingChildrenType == ClassBasedTestDescriptor .class ) {
93+ return Stream .concat (nonMatchingTestDescriptors , orderedTestDescriptors )//
94+ .collect (toList ());
95+ }
96+ // Otherwise, we add the ordered descriptors before the non-matching descriptors,
97+ // which is the case when we are ordering test methods. In other words, local
98+ // test methods always get added before @Nested test classes.
99+ else {
100+ return Stream .concat (orderedTestDescriptors , nonMatchingTestDescriptors )//
101+ .collect (toList ());
102+ }
103+ });
121104 }
122105
123106 // Recurse through the children in order to support ordering for @Nested test classes.
@@ -167,7 +150,32 @@ private boolean canOrderWrappers() {
167150 }
168151
169152 private void orderWrappers (List <WRAPPER > wrappers ) {
170- this .orderingAction .accept (wrappers );
153+ List <WRAPPER > orderedWrappers = new ArrayList <>(wrappers );
154+ this .orderingAction .accept (orderedWrappers );
155+ Map <Object , Integer > distinctWrappersToIndex = distinctWrappersToIndex (orderedWrappers );
156+
157+ int difference = orderedWrappers .size () - wrappers .size ();
158+ int distinctDifference = distinctWrappersToIndex .size () - wrappers .size ();
159+ if (difference > 0 ) { // difference >= distinctDifference
160+ logDescriptorsAddedWarning (difference );
161+ }
162+ if (distinctDifference < 0 ) { // distinctDifference <= difference
163+ logDescriptorsRemovedWarning (distinctDifference );
164+ }
165+
166+ wrappers .sort (comparing (wrapper -> distinctWrappersToIndex .getOrDefault (wrapper , -1 )));
167+ }
168+
169+ private Map <Object , Integer > distinctWrappersToIndex (List <?> wrappers ) {
170+ Map <Object , Integer > toIndex = new HashMap <>();
171+ for (int i = 0 ; i < wrappers .size (); i ++) {
172+ // Avoid ClassCastException if a misbehaving ordering action added a non-WRAPPER
173+ Object wrapper = wrappers .get (i );
174+ if (!toIndex .containsKey (wrapper )) {
175+ toIndex .put (wrapper , i );
176+ }
177+ }
178+ return toIndex ;
171179 }
172180
173181 private void logDescriptorsAddedWarning (int number ) {
0 commit comments