Skip to content

Commit fc2168b

Browse files
authored
Inherit @TestMethodOrder and introduce Default orderer implementations (#4842)
* Inherit `@TestMethodOrder` to enclosed `@Nested` classes * Align implementations of ClassOrderingVisitor and MethodOrderingVisitor - Use a cache for the `DescriptorWrapperOrderer` in both cases - Include test class in warning messages in both cases * Introduce `MethodOrderer.Default` for reverting back to default ordering * Introduce `ClassOrderer.Default` for reverting back to default ordering Resolves #4731.
1 parent 7041459 commit fc2168b

File tree

18 files changed

+439
-116
lines changed

18 files changed

+439
-116
lines changed

documentation/src/docs/asciidoc/link-attributes.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,14 @@ endif::[]
112112
:Assumptions: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Assumptions.html[org.junit.jupiter.api.Assumptions]
113113
:AutoClose: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/AutoClose.html[@AutoClose]
114114
:ClassOrderer_ClassName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.ClassName.html[ClassOrderer.ClassName]
115+
:ClassOrderer_Default: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.Default.html[ClassOrderer.Default]
115116
:ClassOrderer_DisplayName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.DisplayName.html[ClassOrderer.DisplayName]
116117
:ClassOrderer_OrderAnnotation: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.OrderAnnotation.html[ClassOrderer.OrderAnnotation]
117118
:ClassOrderer_Random: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.Random.html[ClassOrderer.Random]
118119
:ClassOrderer: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.html[ClassOrderer]
119120
:ClassTemplate: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassTemplate.html[@ClassTemplate]
120121
:Disabled: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Disabled.html[@Disabled]
122+
:MethodOrderer_Default: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.Default.html[MethodOrderer.Default]
121123
:MethodOrderer_DisplayName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.DisplayName.html[MethodOrderer.DisplayName]
122124
:MethodOrderer_MethodName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.MethodName.html[MethodOrderer.MethodName]
123125
:MethodOrderer_OrderAnnotation: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.OrderAnnotation.html[MethodOrderer.OrderAnnotation]

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ repository on GitHub.
7575
* Non-printable control characters in display names are now replaced with alternative
7676
representations. Please refer to the
7777
<<../user-guide/index.adoc#writing-tests-display-names, User Guide>> for details.
78+
* For consistency with `@TestClassOrder`, `@TestMethodOrder` annotations specified on a
79+
test class are now inherited by its `@Nested` inner classes, recursively.
80+
* Introduce `MethodOrderer.Default` and `ClassOrderer.Default` for reverting back to
81+
default ordering on a `@Nested` class and its `@Nested` inner classes when an enclosing
82+
class specifies a different orderer via `@TestMethodOrder` or `@TestClassOrder`,
83+
respectively.
7884

7985

8086
[[release-notes-6.0.0-RC1-junit-vintage]]

documentation/src/docs/asciidoc/user-guide/writing-tests.adoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,11 @@ following built-in `MethodOrderer` implementations.
10371037
* `{MethodOrderer_Random}`: orders test methods _pseudo-randomly_ and supports
10381038
configuration of a custom _seed_
10391039

1040+
The `MethodOrderer` configured on a test class is inherited by the `@Nested` test classes
1041+
it contains, recursively. If you want to avoid that a `@Nested` test class uses the same
1042+
`MethodOrderer` as its enclosing class, you can specify `{MethodOrderer_Default}` together
1043+
with `{TestMethodOrder}`.
1044+
10401045
NOTE: See also: <<extensions-execution-order-wrapping-behavior>>
10411046

10421047
The following example demonstrates how to guarantee that test methods are executed in the
@@ -1125,6 +1130,8 @@ To configure test class execution order _locally_ for `@Nested` test classes, de
11251130
want to order, and supply a class reference to the `ClassOrderer` implementation you would
11261131
like to use directly in the `@TestClassOrder` annotation. The configured `ClassOrderer`
11271132
will be applied recursively to `@Nested` test classes and their `@Nested` test classes.
1133+
If you want to avoid that a `@Nested` test class uses the same `ClassOrderer` as its
1134+
enclosing class, you can specify `{ClassOrderer_Default}` together with `@TestClassOrder`.
11281135
Note that a local `@TestClassOrder` declaration always overrides an inherited
11291136
`@TestClassOrder` declaration or a `ClassOrderer` configured globally via the
11301137
`junit.jupiter.testclass.order.default` configuration parameter.

junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@
1111
package org.junit.jupiter.api;
1212

1313
import static java.util.Comparator.comparingInt;
14+
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
1415
import static org.apiguardian.api.API.Status.STABLE;
1516

1617
import java.util.Collections;
1718
import java.util.Comparator;
1819

1920
import org.apiguardian.api.API;
21+
import org.junit.platform.commons.JUnitException;
2022
import org.junit.platform.commons.logging.Logger;
2123
import org.junit.platform.commons.logging.LoggerFactory;
2224

@@ -46,6 +48,7 @@
4648
*
4749
* <ul>
4850
* <li>{@link ClassOrderer.ClassName}</li>
51+
* <li>{@link ClassOrderer.Default}</li>
4952
* <li>{@link ClassOrderer.DisplayName}</li>
5053
* <li>{@link ClassOrderer.OrderAnnotation}</li>
5154
* <li>{@link ClassOrderer.Random}</li>
@@ -97,6 +100,35 @@ public interface ClassOrderer {
97100
*/
98101
void orderClasses(ClassOrdererContext context);
99102

103+
/**
104+
* {@code ClassOrderer} that allows to explicitly specify that the default
105+
* ordering should be applied.
106+
*
107+
* <p>If the {@value #DEFAULT_ORDER_PROPERTY_NAME} is set, specifying this
108+
* {@code ClassOrderer} has the same effect as referencing the configured
109+
* class directly. Otherwise, it has the same effect as not specifying any
110+
* {@code ClassOrderer}.
111+
*
112+
* <p>This class can be used to reset the {@code ClassOrderer} for a
113+
* {@link Nested @Nested} class and its {@code @Nested} inner classes,
114+
* recursively, when a {@code ClassOrderer} is configured using
115+
* {@link TestClassOrder @TestClassOrder} on an enclosing class.
116+
*
117+
* @since 6.0
118+
*/
119+
@API(status = EXPERIMENTAL, since = "6.0")
120+
final class Default implements ClassOrderer {
121+
122+
private Default() {
123+
throw new JUnitException("This class must not be instantiated");
124+
}
125+
126+
@Override
127+
public void orderClasses(ClassOrdererContext context) {
128+
// never called
129+
}
130+
}
131+
100132
/**
101133
* {@code ClassOrderer} that sorts classes alphanumerically based on their
102134
* fully qualified names using {@link String#compareTo(String)}.

junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
package org.junit.jupiter.api;
1212

1313
import static java.util.Comparator.comparingInt;
14+
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
1415
import static org.apiguardian.api.API.Status.STABLE;
1516

1617
import java.lang.reflect.Method;
@@ -20,6 +21,7 @@
2021

2122
import org.apiguardian.api.API;
2223
import org.junit.jupiter.api.parallel.ExecutionMode;
24+
import org.junit.platform.commons.JUnitException;
2325
import org.junit.platform.commons.logging.Logger;
2426
import org.junit.platform.commons.logging.LoggerFactory;
2527
import org.junit.platform.commons.util.ClassUtils;
@@ -43,6 +45,7 @@
4345
* implementations.
4446
*
4547
* <ul>
48+
* <li>{@link Default}</li>
4649
* <li>{@link MethodName}</li>
4750
* <li>{@link OrderAnnotation}</li>
4851
* <li>{@link Random}</li>
@@ -125,6 +128,35 @@ default Optional<ExecutionMode> getDefaultExecutionMode() {
125128
return Optional.of(ExecutionMode.SAME_THREAD);
126129
}
127130

131+
/**
132+
* {@code MethodOrderer} that allows to explicitly specify that the default
133+
* ordering should be applied.
134+
*
135+
* <p>If the {@value #DEFAULT_ORDER_PROPERTY_NAME} is set, specifying this
136+
* {@code MethodOrderer} has the same effect as referencing the configured
137+
* class directly. Otherwise, it has the same effect as not specifying any
138+
* {@code MethodOrderer}.
139+
*
140+
* <p>This class can be used to reset the {@code MethodOrderer} for a
141+
* {@link Nested @Nested} class and its {@code @Nested} inner classes,
142+
* recursively, when a {@code MethodOrderer} is configured using
143+
* {@link TestMethodOrder @TestMethodOrder} on an enclosing class.
144+
*
145+
* @since 6.0
146+
*/
147+
@API(status = EXPERIMENTAL, since = "6.0")
148+
final class Default implements MethodOrderer {
149+
150+
private Default() {
151+
throw new JUnitException("This class must not be instantiated");
152+
}
153+
154+
@Override
155+
public void orderMethods(MethodOrdererContext context) {
156+
// never called
157+
}
158+
}
159+
128160
/**
129161
* {@code MethodOrderer} that sorts methods alphanumerically based on their
130162
* names using {@link String#compareTo(String)}.

junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestClassOrder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
* <p>If {@code @TestClassOrder} is not explicitly declared on a test class,
3030
* inherited from a parent class, declared on a test interface implemented by
3131
* a test class, or inherited from an {@linkplain Class#getEnclosingClass() enclosing
32-
* class}, {@code @Nested} test classes will be executed in arbitrary order.
32+
* class}, {@code @Nested} test classes will be ordered using a default
33+
* algorithm that is deterministic but intentionally nonobvious.
3334
*
3435
* <p>As an alternative to {@code @TestClassOrder}, a global {@link ClassOrderer}
3536
* can be configured for the entire test suite via the

junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestMethodOrder.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@
3232
* {@code @TestFactory}, or {@code @TestTemplate}.
3333
*
3434
* <p>If {@code @TestMethodOrder} is not explicitly declared on a test class,
35-
* inherited from a parent class, or declared on a test interface implemented by
36-
* a test class, test methods will be ordered using a default algorithm that is
35+
* inherited from a parent class, declared on a test interface implemented by
36+
* a test class, or inherited from an {@linkplain Class#getEnclosingClass() enclosing
37+
* class}, test methods will be ordered using a default algorithm that is
3738
* deterministic but intentionally nonobvious.
3839
*
3940
* <p>As an alternative to {@code @TestMethodOrder}, a global {@link MethodOrderer}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2015-2025 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.jupiter.engine.config;
12+
13+
import java.util.Optional;
14+
15+
import org.junit.platform.engine.ConfigurationParameters;
16+
17+
/**
18+
* @since 6.0
19+
*/
20+
interface ConfigurationParameterConverter<T> {
21+
22+
default T getOrDefault(ConfigurationParameters configParams, String key, T defaultValue) {
23+
return get(configParams, key).orElse(defaultValue);
24+
}
25+
26+
Optional<T> get(ConfigurationParameters configurationParameters, String key);
27+
28+
}

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010

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

13+
import static java.util.function.Predicate.isEqual;
1314
import static org.apiguardian.api.API.Status.INTERNAL;
1415
import static org.junit.jupiter.api.io.CleanupMode.ALWAYS;
1516
import static org.junit.jupiter.api.io.TempDir.DEFAULT_CLEANUP_MODE_PROPERTY_NAME;
1617
import static org.junit.jupiter.api.io.TempDir.DEFAULT_FACTORY_PROPERTY_NAME;
18+
import static org.junit.jupiter.engine.config.FilteringConfigurationParameterConverter.exclude;
1719

1820
import java.util.List;
1921
import java.util.Optional;
@@ -53,28 +55,30 @@ public class DefaultJupiterConfiguration implements JupiterConfiguration {
5355
"junit.jupiter.params.arguments.conversion.locale.format" //
5456
);
5557

56-
private static final EnumConfigurationParameterConverter<ExecutionMode> executionModeConverter = //
58+
private static final ConfigurationParameterConverter<ExecutionMode> executionModeConverter = //
5759
new EnumConfigurationParameterConverter<>(ExecutionMode.class, "parallel execution mode");
5860

59-
private static final EnumConfigurationParameterConverter<Lifecycle> lifecycleConverter = //
61+
private static final ConfigurationParameterConverter<Lifecycle> lifecycleConverter = //
6062
new EnumConfigurationParameterConverter<>(Lifecycle.class, "test instance lifecycle mode");
6163

62-
private static final InstantiatingConfigurationParameterConverter<DisplayNameGenerator> displayNameGeneratorConverter = //
64+
private static final ConfigurationParameterConverter<DisplayNameGenerator> displayNameGeneratorConverter = //
6365
new InstantiatingConfigurationParameterConverter<>(DisplayNameGenerator.class, "display name generator");
6466

65-
private static final InstantiatingConfigurationParameterConverter<MethodOrderer> methodOrdererConverter = //
66-
new InstantiatingConfigurationParameterConverter<>(MethodOrderer.class, "method orderer");
67+
private static final ConfigurationParameterConverter<MethodOrderer> methodOrdererConverter = //
68+
exclude(isEqual(MethodOrderer.Default.class.getName()),
69+
new InstantiatingConfigurationParameterConverter<>(MethodOrderer.class, "method orderer"));
6770

68-
private static final InstantiatingConfigurationParameterConverter<ClassOrderer> classOrdererConverter = //
69-
new InstantiatingConfigurationParameterConverter<>(ClassOrderer.class, "class orderer");
71+
private static final ConfigurationParameterConverter<ClassOrderer> classOrdererConverter = //
72+
exclude(isEqual(ClassOrderer.Default.class.getName()),
73+
new InstantiatingConfigurationParameterConverter<>(ClassOrderer.class, "class orderer"));
7074

71-
private static final EnumConfigurationParameterConverter<CleanupMode> cleanupModeConverter = //
75+
private static final ConfigurationParameterConverter<CleanupMode> cleanupModeConverter = //
7276
new EnumConfigurationParameterConverter<>(CleanupMode.class, "cleanup mode");
7377

7478
private static final InstantiatingConfigurationParameterConverter<TempDirFactory> tempDirFactoryConverter = //
7579
new InstantiatingConfigurationParameterConverter<>(TempDirFactory.class, "temp dir factory");
7680

77-
private static final EnumConfigurationParameterConverter<ExtensionContextScope> extensionContextScopeConverter = //
81+
private static final ConfigurationParameterConverter<ExtensionContextScope> extensionContextScopeConverter = //
7882
new EnumConfigurationParameterConverter<>(ExtensionContextScope.class, "extension context scope");
7983

8084
private final ConfigurationParameters configurationParameters;

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* @since 5.4
2727
*/
2828
@API(status = INTERNAL, since = "5.8")
29-
public class EnumConfigurationParameterConverter<E extends Enum<E>> {
29+
public class EnumConfigurationParameterConverter<E extends Enum<E>> implements ConfigurationParameterConverter<E> {
3030

3131
private static final Logger logger = LoggerFactory.getLogger(EnumConfigurationParameterConverter.class);
3232

@@ -38,14 +38,14 @@ public EnumConfigurationParameterConverter(Class<E> enumType, String enumDisplay
3838
this.enumDisplayName = enumDisplayName;
3939
}
4040

41-
public Optional<E> get(ExtensionContext extensionContext, String key) {
42-
return extensionContext.getConfigurationParameter(key, value -> convert(key, value));
41+
@Override
42+
public Optional<E> get(ConfigurationParameters configParams, String key) {
43+
return configParams.get(key) //
44+
.map(value -> convert(key, value));
4345
}
4446

45-
E getOrDefault(ConfigurationParameters configParams, String key, E defaultValue) {
46-
return configParams.get(key) //
47-
.map(value -> convert(key, value)) //
48-
.orElse(defaultValue);
47+
public Optional<E> get(ExtensionContext extensionContext, String key) {
48+
return extensionContext.getConfigurationParameter(key, value -> convert(key, value));
4949
}
5050

5151
private E convert(String key, String value) {

0 commit comments

Comments
 (0)