Skip to content

Commit a866c01

Browse files
committed
Ensure @Nested classes are executed after sibling test methods (#4603)
In order to validate them, classpath scanning was changed to find them in 5.13.0. However, that caused `@Nested` classes to be added to their parent descriptors before their sibling test methods. This made the order of execution in classes containing nested test classes dependent on how they were discovered unless a `MethodOrderer` was configured in which case `MethodOrderingVisitor` ensured test methods came before `@Nested` test classes on the same level. This commit changes `MethodOrderingVisitor` to also ensure this ordering constraint in case no `MethodOrderer` is configured. Resolves #4600. (cherry picked from commit d0d6071)
1 parent 2d58467 commit a866c01

File tree

3 files changed

+56
-6
lines changed

3 files changed

+56
-6
lines changed

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

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

38-
* ❓
38+
* The 5.13.0 release introduced a regression regarding the execution order in test classes
39+
containing both test methods and `@Nested` test classes. When classpath scanning was
40+
used during test discovery -- for example, when resolving a package selector for a
41+
`@Suite` class -- test methods in `@Nested` classes were executed _before_ test methods
42+
in their enclosing class. This undesired change in behavior has now been reverted so
43+
that tests in `@Nested` test classes are always executed _after_ tests in enclosing test
44+
classes again.
3945

4046
[[release-notes-5.13.1-junit-jupiter-deprecations-and-breaking-changes]]
4147
==== Deprecations and Breaking Changes

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010

1111
package org.junit.jupiter.engine.discovery;
1212

13+
import static java.util.Comparator.comparing;
1314
import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation;
1415
import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated;
1516

1617
import java.util.List;
1718
import java.util.Optional;
1819
import java.util.function.Consumer;
20+
import java.util.function.UnaryOperator;
1921

2022
import org.junit.jupiter.api.MethodOrderer;
2123
import org.junit.jupiter.api.Order;
@@ -40,6 +42,9 @@ class MethodOrderingVisitor extends AbstractOrderingVisitor {
4042
private final JupiterConfiguration configuration;
4143
private final Condition<MethodBasedTestDescriptor> noOrderAnnotation;
4244

45+
// Not a static field to avoid initialization at build time for GraalVM
46+
private final UnaryOperator<List<TestDescriptor>> methodsBeforeNestedClassesOrderer;
47+
4348
MethodOrderingVisitor(JupiterConfiguration configuration, DiscoveryIssueReporter issueReporter) {
4449
super(issueReporter);
4550
this.configuration = configuration;
@@ -52,6 +57,7 @@ class MethodOrderingVisitor extends AbstractOrderingVisitor {
5257
.source(MethodSource.from(testDescriptor.getTestMethod())) //
5358
.build();
5459
});
60+
this.methodsBeforeNestedClassesOrderer = createMethodsBeforeNestedClassesOrderer();
5561
}
5662

5763
@Override
@@ -82,6 +88,7 @@ private void orderContainedMethods(ClassBasedTestDescriptor classBasedTestDescri
8288

8389
private void orderContainedMethods(ClassBasedTestDescriptor classBasedTestDescriptor, Class<?> testClass,
8490
Optional<MethodOrderer> methodOrderer) {
91+
8592
DescriptorWrapperOrderer<?, DefaultMethodDescriptor> descriptorWrapperOrderer = createDescriptorWrapperOrderer(
8693
testClass, methodOrderer);
8794

@@ -91,6 +98,11 @@ private void orderContainedMethods(ClassBasedTestDescriptor classBasedTestDescri
9198
DefaultMethodDescriptor::new, //
9299
descriptorWrapperOrderer);
93100

101+
if (methodOrderer.isEmpty()) {
102+
// If there is an orderer, this is ensured by the call above
103+
classBasedTestDescriptor.orderChildren(methodsBeforeNestedClassesOrderer);
104+
}
105+
94106
// Note: MethodOrderer#getDefaultExecutionMode() is guaranteed
95107
// to be invoked after MethodOrderer#orderMethods().
96108
methodOrderer //
@@ -130,4 +142,12 @@ private Optional<Consumer<MethodBasedTestDescriptor>> toValidationAction(Optiona
130142
return Optional.of(noOrderAnnotation::check);
131143
}
132144

145+
private static UnaryOperator<List<TestDescriptor>> createMethodsBeforeNestedClassesOrderer() {
146+
var methodsFirst = comparing(MethodBasedTestDescriptor.class::isInstance).reversed();
147+
return children -> {
148+
children.sort(methodsFirst);
149+
return children;
150+
};
151+
}
152+
133153
}

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

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,36 @@
1313
import static org.assertj.core.api.Assertions.assertThat;
1414
import static org.junit.jupiter.api.Assertions.assertAll;
1515
import static org.junit.jupiter.api.Assertions.assertEquals;
16+
import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns;
1617
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
1718
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod;
19+
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage;
1820
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId;
1921
import static org.junit.platform.launcher.LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME;
2022
import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request;
2123
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
2224
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;
2325

2426
import java.util.List;
27+
import java.util.function.Consumer;
28+
import java.util.regex.Pattern;
2529

2630
import org.junit.jupiter.api.AfterEach;
2731
import org.junit.jupiter.api.Assertions;
2832
import org.junit.jupiter.api.BeforeEach;
33+
import org.junit.jupiter.api.Named;
2934
import org.junit.jupiter.api.Nested;
3035
import org.junit.jupiter.api.Test;
3136
import org.junit.jupiter.engine.NestedTestClassesTests.OuterClass.NestedClass;
3237
import org.junit.jupiter.engine.NestedTestClassesTests.OuterClass.NestedClass.RecursiveNestedClass;
3338
import org.junit.jupiter.engine.NestedTestClassesTests.OuterClass.NestedClass.RecursiveNestedSiblingClass;
39+
import org.junit.jupiter.params.ParameterizedTest;
40+
import org.junit.jupiter.params.provider.MethodSource;
3441
import org.junit.platform.engine.DiscoveryIssue.Severity;
3542
import org.junit.platform.engine.TestDescriptor;
3643
import org.junit.platform.engine.support.descriptor.ClassSource;
3744
import org.junit.platform.launcher.LauncherDiscoveryRequest;
45+
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
3846
import org.junit.platform.testkit.engine.EngineExecutionResults;
3947
import org.junit.platform.testkit.engine.Events;
4048

@@ -53,20 +61,36 @@ void nestedTestsAreCorrectlyDiscovered() {
5361
assertEquals(5, engineDescriptor.getDescendants().size(), "# resolved test descriptors");
5462
}
5563

56-
@Test
57-
void nestedTestsAreExecuted() {
58-
EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithNesting.class);
59-
Events containers = executionResults.containerEvents();
60-
Events tests = executionResults.testEvents();
64+
@ParameterizedTest(name = "{0}")
65+
@MethodSource
66+
void nestedTestsAreExecutedInTheRightOrder(Consumer<LauncherDiscoveryRequestBuilder> configurer) {
67+
EngineExecutionResults executionResults = executeTests(configurer);
6168

69+
Events tests = executionResults.testEvents();
6270
assertEquals(3, tests.started().count(), "# tests started");
6371
assertEquals(2, tests.succeeded().count(), "# tests succeeded");
6472
assertEquals(1, tests.failed().count(), "# tests failed");
6573

74+
assertThat(tests.started().map(it -> it.getTestDescriptor().getDisplayName())) //
75+
.containsExactlyInAnyOrder("someTest()", "successful()", "failing()") //
76+
.containsSubsequence("someTest()", "successful()") //
77+
.containsSubsequence("someTest()", "failing()");
78+
79+
Events containers = executionResults.containerEvents();
6680
assertEquals(3, containers.started().count(), "# containers started");
6781
assertEquals(3, containers.finished().count(), "# containers finished");
6882
}
6983

84+
static List<Named<Consumer<LauncherDiscoveryRequestBuilder>>> nestedTestsAreExecutedInTheRightOrder() {
85+
return List.of( //
86+
Named.of("class selector", request -> request //
87+
.selectors(selectClass(TestCaseWithNesting.class))),
88+
Named.of("package selector", request -> request //
89+
.selectors(selectPackage(TestCaseWithNesting.class.getPackageName())) //
90+
.filters(includeClassNamePatterns(Pattern.quote(TestCaseWithNesting.class.getName()) + ".*"))) //
91+
);
92+
}
93+
7094
@Test
7195
void doublyNestedTestsAreCorrectlyDiscovered() {
7296
LauncherDiscoveryRequest request = request().selectors(selectClass(TestCaseWithDoubleNesting.class)).build();

0 commit comments

Comments
 (0)