-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Cartesian tests version 1 required users to annotate the test method to define individual arguments. While version 2 of this extension moves the bulk of configuration to parameter-level annotations it should still allow method-level annotations to define the full set of sets, for example to define them in a CSV or JSON file. This is now possible. It's achieved by turning `CartesianArgumentsProvider` into an internal marker interface with two public extensions: * `CartesianParameterArgumentsProvider` (for providing arguments for an individual parameter, i.e. takes on the former meaning of `CartesianArgumentsProvider`) * `CartesianMethodArgumentsProvider` (for providing arguments for all parameters) Related to #415 PR: #540
- Loading branch information
1 parent
de82d95
commit 63d458d
Showing
17 changed files
with
1,298 additions
and
107 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
149 changes: 149 additions & 0 deletions
149
src/main/java/org/junitpioneer/jupiter/cartesian/ArgumentSets.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/* | ||
* 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.stream.Collectors.toList; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.stream.Stream; | ||
|
||
/** | ||
* Class for defining sets to a {@code CartesianTest} execution with arguments for each parameter | ||
* in the order in which they appear in the test method. | ||
* | ||
* <p>Use the static factory method | ||
* {@link ArgumentSets#argumentsForFirstParameter(Object[]) argumentsForFirstParameter} | ||
* to create an instance and call | ||
* {@link ArgumentSets#argumentsForNextParameter(Object[]) argumentsForNextParameter} | ||
* for each parameter after the first. | ||
* Alternatively, call the static factory method | ||
* {@link ArgumentSets#create() create} | ||
* to create an instance call {@code argumentsForNextParameter} | ||
* for each parameter. | ||
* </p> | ||
*/ | ||
public class ArgumentSets { | ||
|
||
private final List<List<?>> argumentSets; | ||
|
||
private ArgumentSets() { | ||
this.argumentSets = new ArrayList<>(); | ||
} | ||
|
||
private ArgumentSets(Collection<?> arguments) { | ||
this(); | ||
add(arguments); | ||
} | ||
|
||
private ArgumentSets add(Collection<?> arguments) { | ||
argumentSets.add(new ArrayList<>(arguments)); | ||
return this; | ||
} | ||
|
||
/** | ||
* Creates a new {@link ArgumentSets} without arguments for any parameters. | ||
*/ | ||
public static ArgumentSets create() { | ||
return new ArgumentSets(); | ||
} | ||
|
||
/** | ||
* Creates a single set of distinct objects (according to their | ||
* {@link Object#equals(Object) equals}) for the first parameter of | ||
* a {@code CartesianTest} from the elements of the passed | ||
* {@link java.util.Collection Collection}. | ||
* <p> | ||
* The passed argument does not have to be an instance of {@link java.util.Set Set}. | ||
* | ||
* @param arguments the objects that should be passed to the parameter | ||
* @return a new {@link ArgumentSets} object | ||
*/ | ||
public static <T> ArgumentSets argumentsForFirstParameter(Collection<T> arguments) { | ||
return new ArgumentSets(arguments); | ||
} | ||
|
||
/** | ||
* Creates a single set of distinct objects (according to their | ||
* {@link Object#equals(Object) equals}) for the first parameter of | ||
* a {@code CartesianTest} from the elements of the passed | ||
* objects. | ||
* | ||
* @param arguments the objects that should be passed to the parameter | ||
* @return a new {@link ArgumentSets} object | ||
*/ | ||
@SafeVarargs | ||
public static <T> ArgumentSets argumentsForFirstParameter(T... arguments) { | ||
return new ArgumentSets(Arrays.asList(arguments)); | ||
} | ||
|
||
/** | ||
* Creates a single set of distinct objects (according to their | ||
* {@link Object#equals(Object) equals}) for the first parameter of | ||
* a {@code CartesianTest} from the elements of the passed | ||
* {@link java.util.stream.Stream Stream}. | ||
* | ||
* @param arguments the objects that should be passed to the parameter | ||
* @return a new {@link ArgumentSets} object | ||
*/ | ||
public static <T> ArgumentSets argumentsForFirstParameter(Stream<T> arguments) { | ||
return new ArgumentSets(arguments.collect(toList())); | ||
} | ||
|
||
/** | ||
* Creates a single set of distinct objects (according to their | ||
* {@link Object#equals(Object) equals}) for the next parameter of | ||
* a {@code CartesianTest} from the elements of the passed | ||
* {@link Collection Collection}. | ||
* <p> | ||
* The passed argument does not have to be an instance of {@link java.util.Set Set}. | ||
* | ||
* @param arguments the objects that should be passed to the parameter | ||
* @return this {@link ArgumentSets} object, for fluent set definitions | ||
*/ | ||
public final <T> ArgumentSets argumentsForNextParameter(Collection<T> arguments) { | ||
return add(arguments); | ||
} | ||
|
||
/** | ||
* Creates a single set of distinct objects (according to their | ||
* {@link Object#equals(Object) equals}) for the next parameter of | ||
* a {@code CartesianTest} from the elements of the passed | ||
* objects. | ||
* | ||
* @param arguments the objects that should be passed to the parameter | ||
* @return this {@link ArgumentSets} object, for fluent set definitions | ||
*/ | ||
@SafeVarargs | ||
public final <T> ArgumentSets argumentsForNextParameter(T... arguments) { | ||
return add(Arrays.asList(arguments)); | ||
} | ||
|
||
/** | ||
* Creates a single set of distinct objects (according to their | ||
* {@link Object#equals(Object) equals}) for the next parameter of | ||
* a {@code CartesianTest} from the elements of the passed | ||
* {@link Stream Stream}. | ||
* | ||
* @param arguments the objects that should be passed to the parameter | ||
* @return this {@link ArgumentSets} object, for fluent set definitions | ||
*/ | ||
public final <T> ArgumentSets argumentsForNextParameter(Stream<T> arguments) { | ||
return add(arguments.collect(toList())); | ||
} | ||
|
||
List<List<?>> getArguments() { | ||
return argumentSets; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
src/main/java/org/junitpioneer/jupiter/cartesian/CartesianFactoryArgumentsProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
/* | ||
* 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.lang.String.format; | ||
import static org.junit.platform.commons.support.ReflectionSupport.invokeMethod; | ||
|
||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Modifier; | ||
|
||
import org.junit.jupiter.api.extension.ExtensionConfigurationException; | ||
import org.junit.jupiter.api.extension.ExtensionContext; | ||
import org.junit.jupiter.api.extension.ParameterResolutionException; | ||
import org.junit.jupiter.params.support.AnnotationConsumer; | ||
import org.junit.platform.commons.function.Try; | ||
import org.junit.platform.commons.support.ReflectionSupport; | ||
import org.junitpioneer.internal.PioneerUtils; | ||
|
||
class CartesianFactoryArgumentsProvider | ||
implements CartesianMethodArgumentsProvider, AnnotationConsumer<CartesianTest.MethodFactory> { | ||
|
||
private String methodFactoryName; | ||
|
||
@Override | ||
public ArgumentSets provideArguments(ExtensionContext context) throws Exception { | ||
Method testMethod = context.getRequiredTestMethod(); | ||
Method factory = findMethodFactory(testMethod, methodFactoryName); | ||
return invokeMethodFactory(testMethod, factory); | ||
} | ||
|
||
private static Method findMethodFactory(Method testMethod, String methodFactoryName) { | ||
String factoryName = extractMethodFactoryName(methodFactoryName); | ||
Class<?> declaringClass = findExplicitOrImplicitClass(testMethod, methodFactoryName); | ||
Method factory = PioneerUtils | ||
.findMethodCurrentOrEnclosing(declaringClass, factoryName) | ||
.orElseThrow(() -> new ExtensionConfigurationException("Method `Stream<? extends Arguments> " | ||
+ factoryName + "()` not found in " + declaringClass + " or any enclosing class.")); | ||
String method = "Method `" + factory + "`"; | ||
if (!Modifier.isStatic(factory.getModifiers())) | ||
throw new ExtensionConfigurationException(method + " must be static."); | ||
if (!ArgumentSets.class.isAssignableFrom(factory.getReturnType())) | ||
throw new ExtensionConfigurationException( | ||
format("%s must return a `%s` object", method, ArgumentSets.class.getName())); | ||
return factory; | ||
} | ||
|
||
private static String extractMethodFactoryName(String methodFactoryName) { | ||
if (methodFactoryName.contains("(")) | ||
methodFactoryName = methodFactoryName.substring(0, methodFactoryName.indexOf('(')); | ||
if (methodFactoryName.contains("#")) | ||
return methodFactoryName.substring(methodFactoryName.indexOf('#') + 1); | ||
return methodFactoryName; | ||
} | ||
|
||
private static Class<?> findExplicitOrImplicitClass(Method testMethod, String methodFactoryName) { | ||
if (!methodFactoryName.contains("#")) | ||
return testMethod.getDeclaringClass(); | ||
|
||
String className = methodFactoryName.substring(0, methodFactoryName.indexOf('#')); | ||
Try<Class<?>> tryToLoadClass = ReflectionSupport.tryToLoadClass(className); | ||
// step (outwards) through all enclosing classes, trying to load the factory class by appending | ||
// its name to the enclosing class' name (if a previous load didn't already succeed | ||
Class<?> methodClass = testMethod.getDeclaringClass(); | ||
while (methodClass != null) { | ||
String enclosingName = methodClass.getName(); | ||
tryToLoadClass = tryToLoadClass | ||
.orElse(() -> ReflectionSupport.tryToLoadClass(enclosingName + "$" + className)); | ||
methodClass = methodClass.getEnclosingClass(); | ||
} | ||
return tryToLoadClass | ||
.getOrThrow(ex -> new ExtensionConfigurationException( | ||
format("Class %s not found, referenced in method %s", className, testMethod.getName()), ex)); | ||
} | ||
|
||
private ArgumentSets invokeMethodFactory(Method testMethod, Method factory) { | ||
ArgumentSets argumentSets = (ArgumentSets) invokeMethod(factory, null); | ||
long count = argumentSets.getArguments().size(); | ||
if (count > testMethod.getParameterCount()) { | ||
// If arguments count == parameters but one of the parameters should be auto-injected by JUnit | ||
// JUnit will throw a ParameterResolutionException for competing resolvers before we could get to this line | ||
throw new ParameterResolutionException(format( | ||
"Method `%s` must register values for each parameter exactly once. Expected [%d] parameter sets, but got [%d].", | ||
factory, testMethod.getParameterCount(), count)); | ||
} | ||
return argumentSets; | ||
} | ||
|
||
@Override | ||
public void accept(CartesianTest.MethodFactory factory) { | ||
this.methodFactoryName = factory.value(); | ||
} | ||
|
||
} |
Oops, something went wrong.