fr.vergne.pester.junit.PesterTestComparator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pester-core Show documentation
Show all versions of pester-core Show documentation
Implementation of the Pester library.
The newest version!
package fr.vergne.pester.junit;
import static fr.vergne.pester.junit.TestParameter.*;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.junit.platform.commons.support.AnnotationSupport;
import fr.vergne.pester.junit.annotation.DefinitionSource;
import fr.vergne.pester.util.cache.Cache;
import fr.vergne.pester.util.indexer.IndexedValue;
import fr.vergne.pester.util.indexer.Indexer;
import fr.vergne.pester.util.indexer.impl.IteratorIndexer;
import fr.vergne.pester.util.optional.BiOptional;
public class PesterTestComparator implements Comparator {
private final Comparator comparator = initComparator(Method.class)
.thenComparing(byTestParameters())
.thenComparing(byTestNames());
@Override
public int compare(Method m1, Method m2) {
return comparator.compare(m1, m2);
}
private Comparator byTestParameters() {
Function toTestParameters = method -> AnnotationSupport
.findAnnotation(method, DefinitionSource.class)
.map(DefinitionSource::value)
.orElse(null);
Comparator testParametersComparator = initComparator(TestParameter[].class)
.thenComparing(byTestedParametersPriority())
.thenComparing(byFewerParametersFirst())
.thenComparing(byParametersPriority());
return (method1, method2) -> {
return BiOptional.ofNullables(method1, method2)
.mapEach(toTestParameters)
.mapBoth(
// Compare parameters-based methods
(tp1, tp2) -> testParametersComparator.compare(tp1, tp2),
// Prioritize components-based methods
tp1 -> -1,
tp2 -> 1)
// In other cases, we just don't know
.orElse(0);
};
}
private Comparator byTestedParametersPriority() {
// Parameters are stored in sets to ease retrieval.
Function> parametersAdapter = parameters -> new HashSet<>(Arrays.asList(parameters));
// Use a cache to not rebuild the set each time.
Function> cache = Cache.onFunction(parametersAdapter);
// Identify the tested parameters.
Predicate pojoClass = parameters -> cache.apply(parameters).contains(POJO_CLASS);
Predicate constructor = parameters -> cache.apply(parameters).contains(CONSTRUCTOR) || cache.apply(parameters).contains(DEFAULT_CONSTRUCTOR);
Predicate field = parameters -> cache.apply(parameters).contains(FIELD) || cache.apply(parameters).contains(MUTABLE_FIELD);
Predicate getter = parameters -> cache.apply(parameters).contains(GETTER);
Predicate setter = parameters -> cache.apply(parameters).contains(SETTER);
Predicate noField = field.negate();
Predicate noGetter = getter.negate();
Predicate noSetter = setter.negate();
// Identify the different categories (patterns of parameters).
// They are ordered by priority to establish the comparison.
List> categories = Arrays.asList(
pojoClass,
constructor.and(noField).and(noGetter),
constructor.and(field).and(noGetter),
constructor.and(noField).and(getter),
constructor.and(field).and(getter),
field.and(noGetter).and(noSetter),
noField.and(getter).and(noSetter),
field.and(getter).and(noSetter),
noField.and(noGetter).and(setter),
field.and(noGetter).and(setter),
noField.and(getter).and(setter),
field.and(getter).and(setter));
// Compute a score for the given parameters.
// This score corresponds to the index of the situation observed.
Function scoring = parameters -> {
Indexer indexer = new IteratorIndexer<>();
return categories.stream()
.map(category -> category.test(parameters))
.map(indexer::decorateWithIndex)
.filter(IndexedValue::getValue)
.map(IndexedValue::getIndex)
.findFirst()
.orElseGet(() -> {throw new RuntimeException("Situation not considered: " + Arrays.deepToString(parameters));});
};
// Compare parameters based on their situations
return (parameters1, parameters2) -> scoring.apply(parameters1).compareTo(scoring.apply(parameters2));
}
private Comparator byFewerParametersFirst() {
return (parameters1, parameters2) -> Integer.compare(parameters1.length, parameters2.length);
}
private Comparator byParametersPriority() {
// A single parameter is assigned a score based on its priority.
// The priority is decided by the position of the parameter in its enum.
// A better score corresponds to a higher place, so a lower score (smaller index in the enum).
Function parameterScorer = Arrays.asList(TestParameter.values())::indexOf;
// The score of a list of parameters is based on the score of each of them.
// They are sorted by increasing score to ease the comparison.
Function> parametersScorer = parameters -> Arrays
.asList(parameters).stream()
.map(parameterScorer)
.sorted()
.collect(Collectors.toList());
// The parameters are compared based on which shows the lowest score.
return (parameters1, parameters2) -> {
List scores1 = parametersScorer.apply(parameters1);
List scores2 = parametersScorer.apply(parameters2);
for (int i = 0; i < parameters1.length; i++) {
int comparison = scores1.get(i).compareTo(scores2.get(i));
if (comparison != 0) {
return comparison;
}
}
return 0;
};
}
private Comparator byTestNames() {
return Comparator.comparing(Method::getName);
}
private Comparator initComparator(Class clazz) {
return (Comparator) (a,b) -> 0;
}
}