All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.junit.jupiter.api.MethodOrderer Maven / Gradle / Ivy

There is a newer version: 5.11.4
Show newest version
/*
 * 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;

/**
 * {@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 Alphanumeric}
  • *
  • {@link OrderAnnotation}
  • *
  • {@link Random}
  • *
* * @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 {@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. */ class Alphanumeric 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 = (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()); } } /** * {@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 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 * {@link MethodOrdererContext} based on the {@link Order @Order} * annotation. */ @Override public void orderMethods(MethodOrdererContext context) { context.getMethodDescriptors().sort(comparator); } private static final Comparator 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); } } /** * {@code 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 {@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} * 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 #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 * {@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 {@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 * {@link MethodOrdererContext} pseudo-randomly. */ @Override public void orderMethods(MethodOrdererContext context) { Long seed = null; Optional 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 {@link ExecutionMode} for the test class. * *

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 getDefaultExecutionMode() { return this.usingCustomSeed ? Optional.of(ExecutionMode.SAME_THREAD) : Optional.empty(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy