![JAR search and dependency download from the Maven repository](/logo.png)
org.junitpioneer.jupiter.cartesian.CartesianTest Maven / Gradle / Ivy
/*
* Copyright 2016-2022 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.junitpioneer.jupiter.cartesian;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toSet;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.platform.commons.PreconditionViolationException;
import org.junitpioneer.internal.TestNameFormatter;
import org.junitpioneer.jupiter.cartesian.CartesianEnumArgumentsProvider.NullEnum;
/**
* {@code @CartesianTest} is a JUnit Jupiter extension that marks
* a test to be executed with all possible input combinations.
*
* Methods annotated with this annotation should not be annotated with {@code Test}.
*
*
* This annotation is somewhat similar to {@code @ParameterizedTest}, as in it also takes
* arguments and can run the same test multiple times. With {@code @CartesianTest} you
* don't specify the test cases themselves, though. Instead you specify possible values for
* each test method parameter (for example with @{@link CartesianTest.Values}) by annotating the parameters
* themselves and the extension runs the method with each possible combination.
*
*
* You can specify a custom Display Name for the tests ran by {@code @CartesianTest}.
* By default it's [{index}] {arguments}.
*
*
* For more details and examples, see
* the documentation on @CartesianTest
.
*
*
* @since 1.5.0
*/
@TestTemplate
@ExtendWith(CartesianTestExtension.class)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CartesianTest {
/**
* Placeholder for the display name of a {@code @CartesianTest}:
* {displayName}
*
* @since 1.5
* @see #name
*/
String DISPLAY_NAME_PLACEHOLDER = TestNameFormatter.DISPLAY_NAME_PLACEHOLDER;
/**
* Placeholder for the current invocation index of a {@code @CartesianTest}
* method (1-based): {index}
*
* @since 1.5
* @see #name
*/
String INDEX_PLACEHOLDER = TestNameFormatter.INDEX_PLACEHOLDER;
/**
* Placeholder for the complete, comma-separated arguments list of the
* current invocation of a {@code @CartesianTest} method:
* {arguments}
*
* @since 1.5
* @see #name
*/
String ARGUMENTS_PLACEHOLDER = TestNameFormatter.ARGUMENTS_PLACEHOLDER;
/**
* The display name to be used for individual invocations of the
* parameterized test; never blank or consisting solely of whitespace.
*
*
* Defaults to [{index}] {arguments}.
*
*
* Supported placeholders:
*
* - {@link org.junitpioneer.jupiter.cartesian.CartesianTest#DISPLAY_NAME_PLACEHOLDER}
* - {@link org.junitpioneer.jupiter.cartesian.CartesianTest#INDEX_PLACEHOLDER}
* - {@link org.junitpioneer.jupiter.cartesian.CartesianTest#ARGUMENTS_PLACEHOLDER}
* - {0}
, {1}
, etc.: an individual argument (0-based)
*
*
For the latter, you may use {@link java.text.MessageFormat} patterns
* to customize formatting.
*
*
* @since 1.5
* @see java.text.MessageFormat
* @see org.junit.jupiter.params.ParameterizedTest#name()
*/
String name() default "[{index}] {arguments}";
/**
* Parameter annotation to be used with {@code CartesianTest} for providing simple values.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@CartesianArgumentsSource(CartesianValueArgumentsProvider.class)
@interface Values {
/**
* The {@code short} values to use as sources of arguments; must not be empty.
*/
short[] shorts() default {};
/**
* The {@code byte} values to use as sources of arguments; must not be empty.
*/
byte[] bytes() default {};
/**
* The {@code int} values to use as sources of arguments; must not be empty.
*/
int[] ints() default {};
/**
* The {@code long} values to use as sources of arguments; must not be empty.
*/
long[] longs() default {};
/**
* The {@code float} values to use as sources of arguments; must not be empty.
*/
float[] floats() default {};
/**
* The {@code double} values to use as sources of arguments; must not be empty.
*/
double[] doubles() default {};
/**
* The {@code char} values to use as sources of arguments; must not be empty.
*/
char[] chars() default {};
/**
* The {@code boolean} values to use as sources of arguments; must not be empty.
*/
boolean[] booleans() default {};
/**
* The {@link String} values to use as sources of arguments; must not be empty.
*/
String[] strings() default {};
/**
* The {@link Class} values to use as sources of arguments; must not be empty.
*/
Class>[] classes() default {};
}
/**
* Parameter annotation to be used with {@code CartesianTest} for providing enum values.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@CartesianArgumentsSource(CartesianEnumArgumentsProvider.class)
@interface Enum {
/**
* The enum type that serves as the source of the enum constants.
*
* If this attribute is not set explicitly, the declared type of the
* parameter of the {@code @CartesianProductTest} method, which has the
* same relative index of the annotation, is used.
*
*
For example, in case of the following test:
*
* @CartesianProductTest
* @CartesianTest.Enum
* @CartesianTest.Enum
* void multipleOmittedTypes(FirstEnum e1, SecondEnum e2) {
* ...
* }
*
* the first {@code @CartesianTest.Enum} annotation will provide all the values of {@code FirstEnum},
* while the second annotation will provide all the values of {@code SecondEnum}.
*
* @see #names
* @see #mode
*/
Class extends java.lang.Enum>> value() default NullEnum.class;
/**
* The names of enum constants to provide, or regular expressions to select
* the names of enum constants to provide.
*
* If no names or regular expressions are specified, all enum constants
* declared in the specified {@linkplain #value enum type} will be provided.
*
*
The {@link #mode} determines how the names are interpreted.
*
* @see #value
* @see #mode
*/
String[] names() default {};
/**
* The enum constant selection mode.
*
*
Defaults to {@link CartesianTest.Enum.Mode#INCLUDE INCLUDE}.
*
* @see CartesianTest.Enum.Mode#INCLUDE
* @see CartesianTest.Enum.Mode#EXCLUDE
* @see CartesianTest.Enum.Mode#MATCH_ALL
* @see CartesianTest.Enum.Mode#MATCH_ANY
* @see #names
*/
CartesianTest.Enum.Mode mode() default CartesianTest.Enum.Mode.INCLUDE;
/**
* Enumeration of modes for selecting enum constants by name.
*/
enum Mode {
/**
* Select only those enum constants whose names are supplied via the
* {@link CartesianTest.Enum#names} attribute.
*/
INCLUDE(CartesianTest.Enum.Mode::validateNames, (name, names) -> names.contains(name)),
/**
* Select all declared enum constants except those supplied via the
* {@link CartesianTest.Enum#names} attribute.
*/
EXCLUDE(CartesianTest.Enum.Mode::validateNames, (name, names) -> !names.contains(name)),
/**
* Select only those enum constants whose names match all patterns supplied
* via the {@link CartesianTest.Enum#names} attribute.
*
* @see java.util.stream.Stream#allMatch(java.util.function.Predicate)
*/
MATCH_ALL(CartesianTest.Enum.Mode::validatePatterns,
(name, patterns) -> patterns.stream().allMatch(name::matches)),
/**
* Select only those enum constants whose names match any pattern supplied
* via the {@link CartesianTest.Enum#names} attribute.
*
* @see java.util.stream.Stream#anyMatch(java.util.function.Predicate)
*/
MATCH_ANY(CartesianTest.Enum.Mode::validatePatterns,
(name, patterns) -> patterns.stream().anyMatch(name::matches));
private final CartesianTest.Enum.Mode.Validator validator;
private final BiPredicate> selector;
Mode(CartesianTest.Enum.Mode.Validator validator, BiPredicate> selector) {
this.validator = validator;
this.selector = selector;
}
void validate(CartesianTest.Enum enumSource, Set extends java.lang.Enum>> constants,
Set names) {
validator.validate(requireNonNull(enumSource), constants, requireNonNull(names));
}
boolean select(java.lang.Enum> constant, Set names) {
return selector.test(requireNonNull(constant.name()), requireNonNull(names));
}
private static void validateNames(CartesianTest.Enum enumSource, Set extends java.lang.Enum>> constants,
Set names) {
Set allNames = constants.stream().map(java.lang.Enum::name).collect(toSet());
if (!allNames.containsAll(names)) {
throw new PreconditionViolationException(
"Invalid enum constant name(s) in " + enumSource + ". Valid names include: " + allNames);
}
}
private static void validatePatterns(CartesianTest.Enum enumSource,
Set extends java.lang.Enum>> constants, Set names) {
try {
names.forEach(Pattern::compile);
}
catch (PatternSyntaxException e) {
throw new PreconditionViolationException(
"Pattern compilation failed for a regular expression supplied in " + enumSource, e);
}
}
private interface Validator {
void validate(CartesianTest.Enum enumSource, Set extends java.lang.Enum>> constants,
Set names);
}
}
}
/**
* Points to a method to provide parameter values for a {@link CartesianTest}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@CartesianArgumentsSource(CartesianFactoryArgumentsProvider.class)
@interface MethodFactory {
/**
* The name of the method that returns an {@link ArgumentSets} instance.
*/
String value();
}
}