net.jqwik.engine.properties.state.ShrinkableChainShrinker Maven / Gradle / Ivy
The newest version!
package net.jqwik.engine.properties.state;
import java.util.*;
import java.util.stream.*;
import net.jqwik.api.*;
import net.jqwik.api.Tuple.*;
import net.jqwik.api.state.*;
import net.jqwik.engine.support.*;
class ShrinkableChainShrinker {
private final ShrinkableChain shrinkable;
private final List> iterations;
private final int maxTransformations;
ShrinkableChainShrinker(ShrinkableChain shrinkableChain, List> iterations, int maxTransformations) {
this.shrinkable = shrinkableChain;
this.iterations = iterations;
this.maxTransformations = maxTransformations;
}
public Stream>> shrink() {
if (iterations.isEmpty()) {
// Do not try to shrink chains that have not run at all
return Stream.empty();
}
return JqwikStreamSupport.concat(
shrinkMaxTransformations(),
shrinkLastStateAccessingTransformer(),
shrinkRanges(),
shrinkTransformersWithoutStateChange()
);
}
private Stream>> shrinkTransformersWithoutStateChange() {
int indexLastStateAccess = indexOfLastIterationWithStateAccess();
if (indexLastStateAccess > 0) {
// Don't try to shrink the last transformation with state access itself,
// because it will be shrunk anyway
List>> shrunkChains = new ArrayList<>();
for (int i = 0; i < indexLastStateAccess; i++) {
ShrinkableChainIteration currentIteration = iterations.get(i);
if (!currentIteration.changeState) {
ArrayList> shrunkIterations = new ArrayList<>(iterations);
shrunkIterations.remove(i);
shrunkChains.add(newShrinkableChain(shrunkIterations, maxTransformations - 1));
}
}
return shrunkChains.stream();
}
return Stream.empty();
}
private int indexOfLastIterationWithStateAccess() {
for (int i = iterations.size() - 1; i >= 0; i--) {
ShrinkableChainIteration iteration = iterations.get(i);
if (iteration.accessState) {
return i;
}
}
return -1;
}
private Stream>> shrinkMaxTransformations() {
if (iterations.size() < maxTransformations) {
return Stream.of(newShrinkableChain(iterations, iterations.size()));
} else {
return Stream.empty();
}
}
private Stream>> shrinkLastStateAccessingTransformer() {
int indexLastIterationWithStateAccess = indexOfLastIterationWithStateAccess();
if (indexLastIterationWithStateAccess >= 0) {
List> shrunkIterations = new ArrayList<>(iterations);
shrunkIterations.remove(indexLastIterationWithStateAccess);
return Stream.of(newShrinkableChain(shrunkIterations, maxTransformations - 1));
}
return Stream.empty();
}
private Stream>> shrinkRanges() {
return splitIntoRanges().stream()
.flatMap(range -> shrinkIterationsRange(range.get1(), range.get2()));
}
private Stream> shrinkIterationsRange(int startIndex, int endIndex) {
List> iterationsRange = extractRange(startIndex, endIndex);
return JqwikStreamSupport.concat(
shrinkAllSubRanges(startIndex, iterationsRange),
shrinkOneAfterTheOther(startIndex, iterationsRange),
shrinkPairs(startIndex, iterationsRange)
);
}
private List> extractRange(int startIndex, int endIndex) {
List> iterationsRange = new ArrayList<>();
for (int i = 0; i < iterations.size(); i++) {
if (i >= startIndex && i <= endIndex) {
iterationsRange.add(iterations.get(i));
}
}
return iterationsRange;
}
private Stream> shrinkPairs(int startIndex, List> iterationsRange) {
Stream>> shrunkRange = shrinkPairsOfIterations(iterationsRange);
int restSize = iterations.size() - iterationsRange.size();
return replaceRangeByShrunkRange(startIndex, shrunkRange, restSize);
}
private Stream>> shrinkPairsOfIterations(List> iterationsRange) {
return Combinatorics
.distinctPairs(iterationsRange.size())
.flatMap(pair -> {
ShrinkableChainIteration first = iterationsRange.get(pair.get1());
ShrinkableChainIteration second = iterationsRange.get(pair.get2());
return JqwikStreamSupport.zip(
first.shrinkable.shrink(),
second.shrinkable.shrink(),
(Shrinkable> s1, Shrinkable> s2) -> {
ArrayList> newElements = new ArrayList<>(iterationsRange);
newElements.set(pair.get1(), first.withShrinkable(s1));
newElements.set(pair.get2(), second.withShrinkable(s2));
return newElements;
}
);
});
}
private Stream> shrinkOneAfterTheOther(int startIndex, List> iterationsRange) {
Stream>> shrunkRange = shrinkOneIterationAfterTheOther(iterationsRange);
int restSize = iterations.size() - iterationsRange.size();
return replaceRangeByShrunkRange(startIndex, shrunkRange, restSize);
}
private Stream> shrinkAllSubRanges(int startIndex, List> iterationsRange) {
Stream>> shrunkRange = shrinkToAllSubLists(iterationsRange);
int restSize = iterations.size() - iterationsRange.size();
return replaceRangeByShrunkRange(startIndex, shrunkRange, restSize);
}
private Stream>> shrinkOneIterationAfterTheOther(List> iterationsRange) {
List>>> shrinkPerElementStreams = new ArrayList<>();
for (int i = 0; i < iterationsRange.size(); i++) {
int index = i;
ShrinkableChainIteration iteration = iterationsRange.get(i);
Shrinkable> element = iteration.shrinkable;
Stream>> shrinkElement = element.shrink().map(shrunkElement -> {
List> iterationsCopy = new ArrayList<>(iterationsRange);
iterationsCopy.set(index, iteration.withShrinkable(shrunkElement));
return iterationsCopy;
});
shrinkPerElementStreams.add(shrinkElement);
}
return JqwikStreamSupport.concat(shrinkPerElementStreams);
}
private Stream> replaceRangeByShrunkRange(
int startIndex,
Stream>> shrunkRange,
int restSize
) {
return shrunkRange.map(shrunkIterationsRange -> {
List> shrunkIterations = new ArrayList<>();
for (int i = 0; i < startIndex; i++) {
shrunkIterations.add(iterations.get(i));
}
shrunkIterations.addAll(shrunkIterationsRange);
int newMaxSize = restSize + shrunkIterationsRange.size();
return newShrinkableChain(shrunkIterations, newMaxSize);
});
}
private Stream>> shrinkToAllSubLists(List> iterations) {
Set>> setOfSequences = new LinkedHashSet<>();
for (int i = 0; i < iterations.size(); i++) {
if (!isUnshrinkableEndOfChain(iterations.get(i))) {
ArrayList> newCandidate = new ArrayList<>(iterations);
newCandidate.remove(i);
setOfSequences.add(newCandidate);
}
}
return setOfSequences.stream();
}
private boolean isUnshrinkableEndOfChain(ShrinkableChainIteration iteration) {
return isInfinite() && iteration.isEndOfChain();
}
private List> splitIntoRanges() {
List> ranges = new ArrayList<>();
// Move backwards to the next iteration with access to state
int end = 0;
for (int i = iterations.size() - 1; i >= 0; i--) {
end = i;
while (i >= 0) {
ShrinkableChainIteration current = iterations.get(i);
if (current.accessState || i == 0) {
ranges.add(Tuple.of(i, end));
break;
}
i--;
}
}
return ranges;
}
private ShrinkableChain newShrinkableChain(List> shrunkIterations, int newMaxTransformations) {
int effectiveNewMax = isInfinite() ? -1 : newMaxTransformations;
if (isInfinite() && !shrunkIterations.get(shrunkIterations.size() - 1).isEndOfChain()) {
shrunkIterations.add(
new ShrinkableChainIteration<>(null, false, Shrinkable.unshrinkable(Transformer.endOfChain()))
);
}
return shrinkable.cloneWith(
shrunkIterations,
effectiveNewMax
);
}
private boolean isInfinite() {
return maxTransformations < 0;
}
}