Skip to content

Commit

Permalink
Recreate Cartesian test that annotates parameters (#415 / #487)
Browse files Browse the repository at this point in the history
The former @CartesianProductTest extension closely mirrored Jupiter's
@ParameterizedTest annotation. Since then users came up with the idea
that instead of providing arguments in annotations on the method,
they could be provided on the parameters themselves.

// BEFORE
@CartesianProductTest
@CartesianValueSource(ints = { 1, 2 })
@CartesianValueSource(ints = { 3, 4 })
void myCartesianTestMethod(int x, int y) {
    // passing test code
}

// NOW
@CartesianTest
void myCartesianTestMethod(
		@values(ints = { 1, 2 }) int x,
		@values(ints = { 3, 4 }) int y) {
	// passing test code
}

The old variant is still around, but deprecated and will be removed
in the next major release.

The new variant is different in more ways than just where annotations
are placed:

* new package `org.junitpioneer.jupiter.cartesian`
* shorter name `@CartesianTest`
* to keep parameter annotations short while avoiding collisions,
  they are now inner types of `@CartesianTest`
  (so in the example above, the full name is `@CartesianTest.Values`

Closes: #415
PR: #487
  • Loading branch information
Michael1993 committed Sep 2, 2021
1 parent 5ccaed3 commit 44f5ebe
Show file tree
Hide file tree
Showing 33 changed files with 2,608 additions and 388 deletions.
581 changes: 581 additions & 0 deletions docs/cartesian-product-v1.adoc

Large diffs are not rendered by default.

525 changes: 209 additions & 316 deletions docs/cartesian-product.adoc

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.stream.Stream;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.junit.platform.commons.support.AnnotationSupport;

/**
Expand Down Expand Up @@ -255,4 +256,13 @@ private static <A extends Annotation> Stream<A> findOnOuterClasses(Optional<Clas
return Stream.concat(onClass.stream(), onParentClass);
}

public static List<? extends Annotation> findParameterArgumentsSources(Method testMethod) {
return Arrays
.stream(testMethod.getParameters())
.map(parameter -> PioneerAnnotationUtils.findAnnotatedAnnotations(parameter, ArgumentsSource.class))
.filter(list -> !list.isEmpty())
.map(annotations -> annotations.get(0))
.collect(Collectors.toList());
}

}
23 changes: 23 additions & 0 deletions src/main/java/org/junitpioneer/internal/PioneerUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@

import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collector;
Expand Down Expand Up @@ -121,4 +124,24 @@ public static <T> Class<T> wrap(Class<T> clazz) {
return (Class<T>) MethodType.methodType(clazz).wrap().returnType();
}

public static List<List<?>> cartesianProduct(List<List<?>> lists) {
List<List<?>> resultLists = new ArrayList<>();
if (lists.isEmpty()) {
resultLists.add(Collections.emptyList());
return resultLists;
}
List<?> firstList = lists.get(0);
// Note the recursion here
List<List<?>> remainingLists = cartesianProduct(lists.subList(1, lists.size()));
for (Object item : firstList) {
for (List<?> remainingList : remainingLists) {
ArrayList<Object> resultList = new ArrayList<>();
resultList.add(item);
resultList.addAll(remainingList);
resultLists.add(resultList);
}
}
return resultLists;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
* @param <A> the annotation holding necessary data for providing the arguments
* @see org.junit.jupiter.params.provider.ArgumentsProvider
* @see CartesianProductTestExtension
*
* @deprecated has been superseded by CartesianArgumentsProvider
*/
@Deprecated
public interface CartesianAnnotationConsumer<A extends Annotation> extends Consumer<A> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@

/**
* This is basically an enhanced copy of Jupiter's {@code EnumArgumentsProvider},
* except it does NOT support {@code @ParameterizedTest}.
* except it does NOT support {@code @ParameterizedTest} and consumes a {@code Parameter}
* instead of an annotation.
*
* @deprecated scheduled to be removed in 2.0
*/
class CartesianEnumArgumentsProvider implements CartesianAnnotationConsumer<CartesianEnumSource>, ArgumentsProvider {
@Deprecated
class CartesianEnumArgumentsProvider implements CartesianAnnotationConsumer<CartesianEnumSource>, ArgumentsProvider { //NOSONAR deprecated interface use will be removed in later release

private CartesianEnumSource enumSource;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@
* @see CartesianProductTest
*
* @since 1.3.0
* @deprecated scheduled to be removed in 2.0, use {@link org.junitpioneer.jupiter.cartesian.CartesianTest.Enum} instead.
*/
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(CartesianEnumSources.class)
@ArgumentsSource(CartesianEnumArgumentsProvider.class)
@Deprecated
public @interface CartesianEnumSource {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;

/**
* @deprecated Moved to `org.junitpioneer.jupiter.cartesian`.
*/
@Deprecated
class CartesianProductResolver implements ParameterResolver {

private final List<?> parameters;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,14 @@
* </p>
* @see org.junitpioneer.jupiter.CartesianValueSource
*
* @deprecated has been superseded by CartesianTest, scheduled to be removed in 2.0
* @since 1.0
*/
@TestTemplate
@ExtendWith(CartesianProductTestExtension.class)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Deprecated
public @interface CartesianProductTest {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
import static java.util.stream.Collectors.toList;
import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation;
import static org.junit.platform.commons.support.ReflectionSupport.invokeMethod;
import static org.junitpioneer.internal.PioneerUtils.cartesianProduct;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;

Expand All @@ -38,6 +38,11 @@
import org.junitpioneer.internal.PioneerAnnotationUtils;
import org.junitpioneer.internal.PioneerUtils;

/**
* @deprecated Replaced by `org.junitpioneer.jupiter.cartesian.CartesianTestExtension`.
* Scheduled to be removed in 2.0
*/
@Deprecated
class CartesianProductTestExtension implements TestTemplateInvocationContextProvider {

@Override
Expand Down Expand Up @@ -130,7 +135,7 @@ private ArgumentsProvider initializeArgumentsProvider(Annotation source) {
private List<Object> provideArguments(ExtensionContext context, Annotation source, ArgumentsProvider provider)
throws Exception {
if (provider instanceof CartesianAnnotationConsumer) {
((CartesianAnnotationConsumer<Annotation>) provider).accept(source);
((CartesianAnnotationConsumer) provider).accept(source);
return provider
.provideArguments(context)
.map(Arguments::get)
Expand Down Expand Up @@ -202,24 +207,4 @@ private CartesianProductTest.Sets invokeSetsFactory(Method testMethod, Method fa
return sets;
}

private static List<List<?>> cartesianProduct(List<List<?>> lists) {
List<List<?>> resultLists = new ArrayList<>();
if (lists.isEmpty()) {
resultLists.add(Collections.emptyList());
return resultLists;
}
List<?> firstList = lists.get(0);
// Note the recursion here
List<List<?>> remainingLists = cartesianProduct(lists.subList(1, lists.size()));
for (Object item : firstList) {
for (List<?> remainingList : remainingLists) {
ArrayList<Object> resultList = new ArrayList<>();
resultList.add(item);
resultList.addAll(remainingList);
resultLists.add(resultList);
}
}
return resultLists;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;

/**
* @deprecated scheduled to be removed in 2.0
*/
@Deprecated
class CartesianProductTestInvocationContext implements TestTemplateInvocationContext {

private final List<?> parameters;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
import org.junit.jupiter.api.extension.ExtensionConfigurationException;
import org.junitpioneer.internal.PioneerUtils;

/**
* @deprecated scheduled to be removed in 2.0
*/
@Deprecated
class CartesianProductTestNameFormatter {

private final String pattern;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@
import org.junit.platform.commons.PreconditionViolationException;

/**
* This is basically a copy of Jupiter's {@code ValueSourceArgumentsProvider},
* except it does NOT support {@code @ParameterizedTest}.
* This is a slightly modified copy of Jupiter's {@code ValueSourceArgumentsProvider},
* except it does NOT support {@code @ParameterizedTest} and can consume a {@code Parameter}
* instead of an annotation.
*
* @deprecated scheduled to be removed in 2.0
*/
class CartesianValueArgumentsProvider implements CartesianAnnotationConsumer<CartesianValueSource>, ArgumentsProvider {
@Deprecated
class CartesianValueArgumentsProvider implements CartesianAnnotationConsumer<CartesianValueSource>, ArgumentsProvider { //NOSONAR deprecated interface use will be removed in later release

private Object[] arguments;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
* @see CartesianProductTest
*
* @since 1.0
* @deprecated scheduled to be removed in 2.0, use {@link org.junitpioneer.jupiter.cartesian.CartesianTest.Values} instead.
*/
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2016-2021 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 java.lang.reflect.Parameter;

import org.junit.jupiter.params.provider.ArgumentsProvider;

/**
* If you are implementing an {@link org.junit.jupiter.params.provider.ArgumentsProvider ArgumentsProvider}
* for {@link CartesianTest}, it has to implement this interface <b>instead</b> to know which parameter it provides
* arguments to. For more information, see
* <a href="https://junit-pioneer.org/docs/cartesian-product/" target="_top">the Cartesian product documentation</a>.
*
* @see org.junit.jupiter.params.provider.ArgumentsProvider
* @see CartesianTestExtension
*/
public interface CartesianArgumentsProvider extends ArgumentsProvider {

void accept(Parameter parameter);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2016-2021 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.Arrays.stream;
import static java.util.stream.Collectors.toSet;

import java.lang.reflect.Parameter;
import java.util.EnumSet;
import java.util.Set;
import java.util.stream.Stream;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.platform.commons.PreconditionViolationException;
import org.junit.platform.commons.support.AnnotationSupport;

/**
* This is basically an enhanced copy of Jupiter's {@code EnumArgumentsProvider},
* except it does NOT support {@code @ParameterizedTest} and consumes a {@code Parameter}
* instead of an annotation.
*/
class CartesianEnumArgumentsProvider implements CartesianArgumentsProvider {

private CartesianTest.Enum enumSource;
private Class<?> parameterType;

@Override
public void accept(Parameter parameter) {
this.parameterType = parameter.getType();
if (!Enum.class.isAssignableFrom(this.parameterType))
throw new PreconditionViolationException(String
.format(
"Parameter of type %s must reference an Enum type (alternatively, use the annotation's 'value' attribute to specify the type explicitly)",
this.parameterType));
this.enumSource = AnnotationSupport
.findAnnotation(parameter, CartesianTest.Enum.class)
.orElseThrow(() -> new PreconditionViolationException(
"Parameter has to be annotated with " + CartesianTest.Enum.class.getName()));
}

@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
Set<? extends Enum<?>> constants = getEnumConstants();
CartesianTest.Enum.Mode mode = enumSource.mode();
String[] declaredConstantNames = enumSource.names();
if (declaredConstantNames.length > 0) {
Set<String> uniqueNames = stream(declaredConstantNames).collect(toSet());
if (uniqueNames.size() != declaredConstantNames.length)
throw new PreconditionViolationException("Duplicate enum constant name(s) found in " + enumSource);

mode.validate(enumSource, constants, uniqueNames);
constants.removeIf(constant -> !mode.select(constant, uniqueNames));
}
return constants.stream().map(Arguments::of);
}

private <E extends Enum<E>> Set<? extends E> getEnumConstants() {
Class<E> enumClass = determineEnumClass();
return EnumSet.allOf(enumClass);
}

@SuppressWarnings({ "unchecked", "rawtypes" })
private <E extends Enum<E>> Class<E> determineEnumClass() {
Class enumClass = enumSource.value();
if (enumClass.equals(NullEnum.class)) {
enumClass = this.parameterType;
}
return enumClass;
}

enum NullEnum {

}

}

0 comments on commit 44f5ebe

Please sign in to comment.