-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
MethodOrderer.java
333 lines (301 loc) · 11.3 KB
/
MethodOrderer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
/*
* Copyright 2015-2024 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package org.junit.jupiter.api;
import static java.util.Comparator.comparingInt;
import static org.apiguardian.api.API.Status.DEPRECATED;
import static org.apiguardian.api.API.Status.STABLE;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Comparator;
import java.util.Optional;
import org.apiguardian.api.API;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
import org.junit.platform.commons.util.ClassUtils;
/**
* {@code MethodOrderer} defines the API for ordering the <em>test methods</em>
* in a given test class.
*
* <p>In this context, the term "test method" refers to any method annotated with
* {@code @Test}, {@code @RepeatedTest}, {@code @ParameterizedTest},
* {@code @TestFactory}, or {@code @TestTemplate}.
*
* <p>A {@link MethodOrderer} can be configured <em>globally</em> for the entire
* test suite via the {@value #DEFAULT_ORDER_PROPERTY_NAME} configuration
* parameter (see the User Guide for details) or <em>locally</em> for a test
* class via the {@link TestMethodOrder @TestMethodOrder} annotation.
*
* <h2>Built-in Implementations</h2>
*
* <p>JUnit Jupiter provides the following built-in {@code MethodOrderer}
* implementations.
*
* <ul>
* <li>{@link MethodName}</li>
* <li>{@link OrderAnnotation}</li>
* <li>{@link Random}</li>
* </ul>
*
* @since 5.4
* @see TestMethodOrder
* @see MethodOrdererContext
* @see #orderMethods(MethodOrdererContext)
* @see ClassOrderer
*/
@API(status = STABLE, since = "5.7")
public interface MethodOrderer {
/**
* Property name used to set the default method orderer class name: {@value}
*
* <h4>Supported Values</h4>
*
* <p>Supported values include fully qualified class names for types that
* implement {@link org.junit.jupiter.api.MethodOrderer}.
*
* <p>If not specified, test methods will be ordered using an algorithm that
* is deterministic but intentionally non-obvious.
*
* @since 5.7
*/
@API(status = STABLE, since = "5.9")
String DEFAULT_ORDER_PROPERTY_NAME = "junit.jupiter.testmethod.order.default";
/**
* Order the methods encapsulated in the supplied {@link MethodOrdererContext}.
*
* <p>The methods to order or sort are made indirectly available via
* {@link MethodOrdererContext#getMethodDescriptors()}. Since this method
* has a {@code void} return type, the list of method descriptors must be
* modified directly.
*
* <p>For example, a simplified implementation of the {@link Random}
* {@code MethodOrderer} might look like the following.
*
* <pre class="code">
* public void orderMethods(MethodOrdererContext context) {
* Collections.shuffle(context.getMethodDescriptors());
* }
* </pre>
*
* @param context the {@code MethodOrdererContext} containing the
* {@linkplain MethodDescriptor method descriptors} to order; never {@code null}
* @see #getDefaultExecutionMode()
*/
void orderMethods(MethodOrdererContext context);
/**
* Get the <em>default</em> {@link ExecutionMode} for the test class
* configured with this {@link MethodOrderer}.
*
* <p>This method is guaranteed to be invoked after
* {@link #orderMethods(MethodOrdererContext)} which allows implementations
* of this method to determine the appropriate return value programmatically,
* potentially based on actions that were taken in {@code orderMethods()}.
*
* <p>Defaults to {@link ExecutionMode#SAME_THREAD SAME_THREAD}, since
* ordered methods are typically sorted in a fashion that would conflict
* with concurrent execution.
*
* <p>In case the ordering does not conflict with concurrent execution,
* implementations should return an empty {@link Optional} to signal that
* the engine should decide which execution mode to use.
*
* <p>Can be overridden via an explicit
* {@link org.junit.jupiter.api.parallel.Execution @Execution} declaration
* on the test class or in concrete implementations of the
* {@code MethodOrderer} API.
*
* @return the default {@code ExecutionMode}; never {@code null} but
* potentially empty
* @see #orderMethods(MethodOrdererContext)
*/
default Optional<ExecutionMode> getDefaultExecutionMode() {
return Optional.of(ExecutionMode.SAME_THREAD);
}
/**
* {@code MethodOrderer} that sorts methods alphanumerically based on their
* names using {@link String#compareTo(String)}.
*
* <p>If two methods have the same name, {@code String} representations of
* their formal parameter lists will be used as a fallback for comparing the
* methods.
*
* @since 5.4
* @deprecated as of JUnit Jupiter 5.7 in favor of {@link MethodOrderer.MethodName};
* to be removed in 6.0
*/
@API(status = DEPRECATED, since = "5.7")
@Deprecated
class Alphanumeric extends MethodName {
public Alphanumeric() {
}
}
/**
* {@code MethodOrderer} that sorts methods alphanumerically based on their
* names using {@link String#compareTo(String)}.
*
* <p>If two methods have the same name, {@code String} representations of
* their formal parameter lists will be used as a fallback for comparing the
* methods.
*
* @since 5.7
*/
@API(status = STABLE, since = "5.10")
class MethodName implements MethodOrderer {
public MethodName() {
}
/**
* Sort the methods encapsulated in the supplied
* {@link MethodOrdererContext} alphanumerically based on their names
* and formal parameter lists.
*/
@Override
public void orderMethods(MethodOrdererContext context) {
context.getMethodDescriptors().sort(comparator);
}
private static final Comparator<MethodDescriptor> comparator = Comparator.<MethodDescriptor, String> //
comparing(descriptor -> descriptor.getMethod().getName())//
.thenComparing(descriptor -> parameterList(descriptor.getMethod()));
private static String parameterList(Method method) {
return ClassUtils.nullSafeToString(method.getParameterTypes());
}
}
/**
* {@code MethodOrderer} that sorts methods alphanumerically based on their
* display names using {@link String#compareTo(String)}
*
* @since 5.7
*/
@API(status = STABLE, since = "5.10")
class DisplayName implements MethodOrderer {
public DisplayName() {
}
/**
* Sort the methods encapsulated in the supplied
* {@link MethodOrdererContext} alphanumerically based on their display
* names.
*/
@Override
public void orderMethods(MethodOrdererContext context) {
context.getMethodDescriptors().sort(comparator);
}
private static final Comparator<MethodDescriptor> comparator = Comparator.comparing(
MethodDescriptor::getDisplayName);
}
/**
* {@code MethodOrderer} that sorts methods based on the {@link Order @Order}
* annotation.
*
* <p>Any methods that are assigned the same order value will be sorted
* arbitrarily adjacent to each other.
*
* <p>Any methods not annotated with {@code @Order} will be assigned the
* {@linkplain Order#DEFAULT default order} value which will effectively cause them
* to appear at the end of the sorted list, unless certain methods are assigned
* an explicit order value greater than the default order value. Any methods
* assigned an explicit order value greater than the default order value will
* appear after non-annotated methods in the sorted list.
*/
class OrderAnnotation implements MethodOrderer {
public OrderAnnotation() {
}
/**
* Sort the methods encapsulated in the supplied
* {@link MethodOrdererContext} based on the {@link Order @Order}
* annotation.
*/
@Override
public void orderMethods(MethodOrdererContext context) {
context.getMethodDescriptors().sort(comparingInt(OrderAnnotation::getOrder));
}
private static int getOrder(MethodDescriptor descriptor) {
return descriptor.findAnnotation(Order.class).map(Order::value).orElse(Order.DEFAULT);
}
}
/**
* {@code MethodOrderer} that orders methods pseudo-randomly.
*
* <h2>Custom Seed</h2>
*
* <p>By default, the random <em>seed</em> used for ordering methods is the
* value returned by {@link System#nanoTime()} during static initialization
* of this class. In order to support repeatable builds, the value of the
* default random seed is logged at {@code CONFIG} level. In addition, a
* custom seed (potentially the default seed from the previous test plan
* execution) may be specified via the {@value ClassOrderer.Random#RANDOM_SEED_PROPERTY_NAME}
* <em>configuration parameter</em> which can be supplied via the {@code Launcher}
* API, build tools (e.g., Gradle and Maven), a JVM system property, or the JUnit
* Platform configuration file (i.e., a file named {@code junit-platform.properties}
* in the root of the class path). Consult the User Guide for further information.
*
* @see Random#RANDOM_SEED_PROPERTY_NAME
* @see java.util.Random
*/
class Random implements MethodOrderer {
private static final Logger logger = LoggerFactory.getLogger(Random.class);
/**
* Default seed, which is generated during initialization of this class
* via {@link System#nanoTime()} for reproducibility of tests.
*/
private static final long DEFAULT_SEED;
static {
DEFAULT_SEED = System.nanoTime();
logger.config(() -> "MethodOrderer.Random default seed: " + DEFAULT_SEED);
}
/**
* Property name used to set the random seed used by this
* {@code MethodOrderer}: {@value}
*
* <p>The same property is used by {@link ClassOrderer.Random} for
* consistency between the two random orderers.
*
* <h4>Supported Values</h4>
*
* <p>Supported values include any string that can be converted to a
* {@link Long} via {@link Long#valueOf(String)}.
*
* <p>If not specified or if the specified value cannot be converted to
* a {@link Long}, the default random seed will be used (see the
* {@linkplain Random class-level Javadoc} for details).
*
* @see ClassOrderer.Random
*/
public static final String RANDOM_SEED_PROPERTY_NAME = "junit.jupiter.execution.order.random.seed";
public Random() {
}
/**
* Order the methods encapsulated in the supplied
* {@link MethodOrdererContext} pseudo-randomly.
*/
@Override
public void orderMethods(MethodOrdererContext context) {
Collections.shuffle(context.getMethodDescriptors(),
new java.util.Random(getCustomSeed(context).orElse(DEFAULT_SEED)));
}
private Optional<Long> getCustomSeed(MethodOrdererContext context) {
return context.getConfigurationParameter(RANDOM_SEED_PROPERTY_NAME).map(configurationParameter -> {
Long seed = null;
try {
seed = Long.valueOf(configurationParameter);
logger.config(
() -> String.format("Using custom seed for configuration parameter [%s] with value [%s].",
RANDOM_SEED_PROPERTY_NAME, configurationParameter));
}
catch (NumberFormatException ex) {
logger.warn(ex,
() -> String.format(
"Failed to convert configuration parameter [%s] with value [%s] to a long. "
+ "Using default seed [%s] as fallback.",
RANDOM_SEED_PROPERTY_NAME, configurationParameter, DEFAULT_SEED));
}
return seed;
});
}
}
}