org.junit.jupiter.api.MethodOrderer Maven / Gradle / Ivy
/*
* Copyright 2015-2020 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.EXPERIMENTAL;
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 test methods
* in a given test class.
*
* In this context, the term "test method" refers to any method annotated with
* {@code @Test}, {@code @RepeatedTest}, {@code @ParameterizedTest},
* {@code @TestFactory}, or {@code @TestTemplate}.
*
*
Built-in Implementations
*
* JUnit Jupiter provides the following built-in {@code MethodOrderer}
* implementations.
*
*
* - {@link MethodName}
* - {@link OrderAnnotation}
* - {@link Random}
*
*
* @since 5.4
* @see TestMethodOrder
* @see MethodOrdererContext
* @see #orderMethods(MethodOrdererContext)
*/
@API(status = STABLE, since = "5.7")
public interface MethodOrderer {
/**
* Order the methods encapsulated in the supplied {@link MethodOrdererContext}.
*
* 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.
*
*
For example, a simplified implementation of the {@link Random}
* {@code MethodOrderer} might look like the following.
*
*
* public void orderMethods(MethodOrdererContext context) {
* Collections.shuffle(context.getMethodDescriptors());
* }
*
*
* @param context the {@code MethodOrdererContext} containing the
* {@link MethodDescriptor method descriptors} to order; never {@code null}
* @see #getDefaultExecutionMode()
*/
void orderMethods(MethodOrdererContext context);
/**
* Get the default {@link ExecutionMode} for the test class
* configured with this {@link MethodOrderer}.
*
* 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()}.
*
*
Defaults to {@link ExecutionMode#SAME_THREAD SAME_THREAD}, since
* ordered methods are typically sorted in a fashion that would conflict
* with concurrent execution.
*
*
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.
*
*
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 getDefaultExecutionMode() {
return Optional.of(ExecutionMode.SAME_THREAD);
}
/**
* {@code MethodOrderer} that sorts methods alphanumerically based on their
* names using {@link String#compareTo(String)}.
*
* 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.
* This class has been deprecated in favor of
* {@link MethodOrderer.MethodName} and will be removed in 6.0
*
* @since 5.4
* @deprecated Please use {@link MethodOrderer.MethodName} instead.
*/
@API(status = DEPRECATED, since = "5.7")
@Deprecated
class Alphanumeric extends MethodName {
}
/**
* {@code MethodOrderer} that sorts methods alphanumerically based on their
* names using {@link String#compareTo(String)}.
*
* 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 = EXPERIMENTAL, since = "5.7")
class MethodName implements MethodOrderer {
/**
* 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 comparator = Comparator. //
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 = EXPERIMENTAL, since = "5.7")
class DisplayName implements MethodOrderer {
/**
* 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 comparator = Comparator.comparing(
MethodDescriptor::getDisplayName);
}
/**
* {@code MethodOrderer} that sorts methods based on the {@link Order @Order}
* annotation.
*
* Any methods that are assigned the same order value will be sorted
* arbitrarily adjacent to each other.
*
*
Any methods not annotated with {@code @Order} will be assigned the
* {@link org.junit.jupiter.api.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 {
/**
* 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.
*
*
Custom Seed
*
* By default, the random seed 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 INFO} level. In addition, a
* custom seed (potentially the default seed from the previous test plan
* execution) may be specified via the {@link Random#RANDOM_SEED_PROPERTY_NAME
* junit.jupiter.execution.order.random.seed} configuration parameter
* 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.info(() -> "MethodOrderer.Random default seed: " + DEFAULT_SEED);
}
/**
* Property name used to set the random seed used by this
* {@code MethodOrderer}: {@value}
*
*
Supported Values
*
* Supported values include any string that can be converted to a
* {@link Long} via {@link Long#valueOf(String)}.
*
*
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).
*/
public static final String RANDOM_SEED_PROPERTY_NAME = "junit.jupiter.execution.order.random.seed";
/**
* 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 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;
});
}
}
}