All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.jqwik.engine.properties.state.ShrinkableChainShrinker Maven / Gradle / Ivy

There is a newer version: 1.9.1
Show 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;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy