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

net.jqwik.engine.properties.shrinking.AbstractSampleShrinker Maven / Gradle / Ivy

The newest version!
package net.jqwik.engine.properties.shrinking;

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

import net.jqwik.api.*;
import net.jqwik.api.Tuple.*;
import net.jqwik.api.lifecycle.*;
import net.jqwik.engine.properties.*;

abstract class AbstractSampleShrinker {

	private static ShrinkingDistance calculateDistance(List> shrinkables) {
		return ShrinkingDistance.forCollection(shrinkables);
	}

	private final Map, TryExecutionResult> falsificationCache;

	public AbstractSampleShrinker(Map, TryExecutionResult> falsificationCache) {
		this.falsificationCache = falsificationCache;
	}

	public abstract FalsifiedSample shrink(
		Falsifier> falsifier,
		FalsifiedSample sample,
		Consumer shrinkSampleConsumer,
		Consumer shrinkAttemptConsumer
	);

	protected FalsifiedSample shrink(
		Falsifier> falsifier,
		FalsifiedSample sample,
		Consumer sampleShrunkConsumer,
		Consumer shrinkAttemptConsumer,
		Function>, Stream>>> supplyShrinkCandidates
	) {
		List> currentShrinkBase = sample.shrinkables();
		Optional bestResult = Optional.empty();
		FilteredResults filteredResults = new FilteredResults();

		while (true) {
			ShrinkingDistance currentDistance = calculateDistance(currentShrinkBase);

			FalsifiedSample currentBest = bestResult.orElse(null);

			Optional, List>, TryExecutionResult>> newShrinkingResult =
				supplyShrinkCandidates.apply(currentShrinkBase)
								 .peek(ignore -> shrinkAttemptConsumer.accept(currentBest))
								 .filter(shrinkables -> calculateDistance(shrinkables).compareTo(currentDistance) <= 0)
								 .map(shrinkables -> {
									 List params = createValues(shrinkables).collect(Collectors.toList());
									 TryExecutionResult result = falsify(falsifier, params);
									 return Tuple.of(params, shrinkables, result);
								 })
								 .peek(t -> {
									 // Remember best 10 invalid results in case no  falsified shrink is found
									 if (t.get3().isInvalid() && calculateDistance(t.get2()).compareTo(currentDistance) < 0) {
										 filteredResults.push(t);
									 }
								 })
								 .filter(t -> t.get3().isFalsified())
								 .findAny();

			if (newShrinkingResult.isPresent()) {
				Tuple3, List>, TryExecutionResult> falsifiedTry = newShrinkingResult.get();
				TryExecutionResult tryExecutionResult = falsifiedTry.get3();
				FalsifiedSample falsifiedSample = new FalsifiedSampleImpl(
					falsifiedTry.get1(),
					falsifiedTry.get2(),
					tryExecutionResult.throwable(),
					tryExecutionResult.footnotes()
				);
				sampleShrunkConsumer.accept(falsifiedSample);
				bestResult = Optional.of(falsifiedSample);
				currentShrinkBase = falsifiedTry.get2();
				filteredResults.clear();
			} else if (!filteredResults.isEmpty()) {
				Tuple3, List>, TryExecutionResult> aFilteredResult = filteredResults.pop();
				currentShrinkBase = aFilteredResult.get2();
			} else {
				break;
			}
		}

		return bestResult.orElse(sample);
	}

	private TryExecutionResult falsify(Falsifier> falsifier, List params) {
		// I wonder in which cases this is really an optimization
		return falsificationCache.computeIfAbsent(params, p -> falsifier.execute(params));
	}

	private Stream createValues(List> shrinkables) {
		return shrinkables.stream().map(Shrinkable::value);
	}

	private static class FilteredResults {

		public static final int MAX_SIZE = 100;

		Comparator, List>, TryExecutionResult>> resultComparator =
			Comparator.comparing(left -> calculateDistance(left.get2()));

		PriorityQueue, List>, TryExecutionResult>> prioritizedResults = new PriorityQueue<>(resultComparator);

		Set, List>, TryExecutionResult>> removedResults = new LinkedHashSet<>();

		void push(Tuple3, List>, TryExecutionResult> result) {
			if (removedResults.contains(result)) {
				return;
			}
			prioritizedResults.add(result);
			if (size() > MAX_SIZE) {
				prioritizedResults.poll();
			}
		}

		int size() {
			return prioritizedResults.size();
		}

		boolean isEmpty() {
			return prioritizedResults.isEmpty();
		}

		Tuple3, List>, TryExecutionResult> pop() {
			Tuple3, List>, TryExecutionResult> result = prioritizedResults.peek();
			prioritizedResults.remove(result);
			removedResults.add(result);
			return result;
		}

		public void clear() {
			prioritizedResults.clear();
			removedResults.clear();
		}
	}
}