/*
 * Copyright 2015-2019 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
 *
 * http://www.eclipse.org/legal/epl-v20.html
 */

package org.junit.jupiter.api;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;

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;

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 @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, or @TestTemplate.

Built-in Implementations

JUnit Jupiter provides the following built-in MethodOrderer implementations.

See Also:
Since:5.4
/** * {@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}. * * <h4>Built-in Implementations</h4> * * <p>JUnit Jupiter provides the following built-in {@code MethodOrderer} * implementations. * * <ul> * <li>{@link Alphanumeric}</li> * <li>{@link OrderAnnotation}</li> * <li>{@link Random}</li> * </ul> * * @since 5.4 * @see TestMethodOrder * @see MethodOrdererContext * @see #orderMethods(MethodOrdererContext) */
@API(status = EXPERIMENTAL, since = "5.4") public interface MethodOrderer {
Order the methods encapsulated in the supplied MethodOrdererContext.

The methods to order or sort are made indirectly available via MethodOrdererContext.getMethodDescriptors(). Since this method has a void return type, the list of method descriptors must be modified directly.

For example, a simplified implementation of the Random MethodOrderer might look like the following.

public void orderMethods(MethodOrdererContext context) {
    Collections.shuffle(context.getMethodDescriptors());
}
Params:
  • context – the MethodOrdererContext containing the method descriptors to order; never null
See Also:
/** * 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 * {@link MethodDescriptor method descriptors} to order; never {@code null} * @see #getDefaultExecutionMode() */
void orderMethods(MethodOrdererContext context);
Get the default ExecutionMode for the test class configured with this MethodOrderer.

This method is guaranteed to be invoked after orderMethods(MethodOrdererContext) which allows implementations of this method to determine the appropriate return value programmatically, potentially based on actions that were taken in orderMethods().

Defaults to 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 Optional to signal that the engine should decide which execution mode to use.

Can be overridden via an explicit @Execution declaration on the test class or in concrete implementations of the MethodOrderer API.

See Also:
Returns:the default ExecutionMode; never null but potentially empty
/** * 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); }
MethodOrderer that sorts methods alphanumerically based on their names using String.compareTo(String).

If two methods have the same name, String representations of their formal parameter lists will be used as a fallback for comparing the methods.

/** * {@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. */
class Alphanumeric implements MethodOrderer {
Sort the methods encapsulated in the supplied MethodOrdererContext alphanumerically based on their names and formal parameter lists.
/** * 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 = (descriptor1, descriptor2) -> { Method method1 = descriptor1.getMethod(); Method method2 = descriptor2.getMethod(); int result = method1.getName().compareTo(method2.getName()); if (result != 0) { return result; } // else return parameterList(method1).compareTo(parameterList(method2)); }; private static String parameterList(Method method) { return ClassUtils.nullSafeToString(method.getParameterTypes()); } }
MethodOrderer that sorts methods based on the @Order annotation.

Any methods that are assigned the same order value will be sorted arbitrarily adjacent to each other.

Any methods not annotated with @Order will be assigned a default order value of Integer.MAX_VALUE which will effectively cause them to appear at the end of the sorted list.

/** * {@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 a default * order value of {@link Integer#MAX_VALUE} which will effectively cause them to * appear at the end of the sorted list. */
class OrderAnnotation implements MethodOrderer {
Sort the methods encapsulated in the supplied MethodOrdererContext based on the @Order annotation.
/** * 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(comparator); } private static final Comparator<MethodDescriptor> comparator = // (descriptor1, descriptor2) -> Integer.compare(getOrder(descriptor1), getOrder(descriptor2)); private static int getOrder(MethodDescriptor descriptor) { return descriptor.findAnnotation(Order.class).map(Order::value).orElse(Integer.MAX_VALUE); } }
MethodOrderer that orders methods pseudo-randomly and allows for concurrent execution by default.

Custom Seed

By default, the random seed used for ordering methods is the value returned by System.nanoTime(). In order to produce repeatable builds, a custom seed may be specified via the junit.jupiter.execution.order.random.seed configuration parameter which can be supplied via the Launcher API, build tools (e.g., Gradle and Maven), a JVM system property, or the JUnit Platform configuration file (i.e., a file named junit-platform.properties in the root of the class path). Consult the User Guide for further information.

See Also:
/** * {@code MethodOrderer} that orders methods pseudo-randomly and allows for * concurrent execution by default. * * <h4>Custom Seed</h4> * * <p>By default, the random <em>seed</em> used for ordering methods is the * value returned by {@link System#nanoTime()}. In order to produce repeatable * builds, a custom seed may be specified via the * {@link Random#RANDOM_SEED_PROPERTY_NAME junit.jupiter.execution.order.random.seed} * <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 #getDefaultExecutionMode() * @see Random#RANDOM_SEED_PROPERTY_NAME * @see java.util.Random */
class Random implements MethodOrderer { private static final Logger logger = LoggerFactory.getLogger(Random.class);
Property name used to set the random seed used by this MethodOrderer: "junit.jupiter.execution.order.random.seed"

Supported Values

Supported values include any string that can be converted to a Long via Long.valueOf(String).

If not specified or if the specified value cannot be converted to a Long, System.nanoTime() will be used as the random seed.

/** * Property name used to set the random seed used by this * {@code MethodOrderer}: {@value} * * <h3>Supported Values</h3> * * <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 {@code Long}, {@link System#nanoTime()} will be used as the random * seed. */
public static final String RANDOM_SEED_PROPERTY_NAME = "junit.jupiter.execution.order.random.seed"; private boolean usingCustomSeed = false;
Order the methods encapsulated in the supplied MethodOrdererContext pseudo-randomly.
/** * Order the methods encapsulated in the supplied * {@link MethodOrdererContext} pseudo-randomly. */
@Override public void orderMethods(MethodOrdererContext context) { Long seed = null; Optional<String> configurationParameter = context.getConfigurationParameter(RANDOM_SEED_PROPERTY_NAME); if (configurationParameter.isPresent()) { String value = configurationParameter.get(); try { seed = Long.valueOf(value); this.usingCustomSeed = true; logger.config( () -> String.format("Using custom seed for configuration parameter [%s] with value [%s].", RANDOM_SEED_PROPERTY_NAME, value)); } catch (NumberFormatException ex) { logger.warn(ex, () -> String.format("Failed to convert configuration parameter [%s] with value [%s] to a long. " + "Using System.nanoTime() as fallback.", RANDOM_SEED_PROPERTY_NAME, value)); } } if (seed == null) { seed = System.nanoTime(); } Collections.shuffle(context.getMethodDescriptors(), new java.util.Random(seed)); }
Get the default ExecutionMode for the test class.

If a custom seed has been specified, this method returns SAME_THREAD in order to ensure that the results are repeatable across executions of the test plan. Otherwise, this method returns CONCURRENT to allow concurrent execution of randomly ordered methods by default.

Returns:SAME_THREAD if a custom seed has been configured; otherwise, CONCURRENT
/** * Get the <em>default</em> {@link ExecutionMode} for the test class. * * <p>If a custom seed has been specified, this method returns * {@link ExecutionMode#SAME_THREAD SAME_THREAD} in order to ensure that * the results are repeatable across executions of the test plan. * Otherwise, this method returns {@link ExecutionMode#CONCURRENT * CONCURRENT} to allow concurrent execution of randomly ordered methods * by default. * * @return {@code SAME_THREAD} if a custom seed has been configured; * otherwise, {@code CONCURRENT} */
@Override public Optional<ExecutionMode> getDefaultExecutionMode() { return this.usingCustomSeed ? Optional.of(ExecutionMode.SAME_THREAD) : Optional.empty(); } } }