Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
net.jqwik.engine.properties.arbitraries.LazyOfArbitrary Maven / Gradle / Ivy
package net.jqwik.engine.properties.arbitraries;
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.*;
import net.jqwik.engine.execution.lifecycle.*;
import net.jqwik.engine.properties.shrinking.*;
import net.jqwik.engine.support.*;
public class LazyOfArbitrary implements Arbitrary {
// Cached arbitraries only have to survive one property
private static Store>> arbitrariesStore() {
try {
return Store.getOrCreate(Tuple.of(LazyOfShrinkable.class, "arbitraries"), Lifespan.PROPERTY, HashMap::new);
} catch (OutsideJqwikException outsideJqwikException) {
return Store.free(HashMap::new);
}
}
@SuppressWarnings("unchecked")
public static Arbitrary of(int hashIdentifier, List>> suppliers) {
// It's important for good shrinking to work that the same arbitrary usage is handled by the same arbitrary instance
LazyOfArbitrary> arbitrary = arbitrariesStore().get().computeIfAbsent(hashIdentifier, ignore -> new LazyOfArbitrary<>(suppliers));
return (Arbitrary) arbitrary;
}
private final List>> suppliers;
private final Deque>> generatedParts = new ArrayDeque<>();
// Remember generators during the same try. That way generators with state (e.g. unique()) work as expected
private final Store>> generators = createGeneratorsStore();
private Store>> createGeneratorsStore() {
try {
return Store.getOrCreate(Tuple.of(this, "generators"), Lifespan.TRY, HashMap::new);
} catch (OutsideJqwikException outsideJqwikException) {
return Store.free(HashMap::new);
}
}
public LazyOfArbitrary(List>> suppliers) {
this.suppliers = suppliers;
}
@Override
public RandomGenerator generator(int genSize) {
return random -> {
int index = random.nextInt(suppliers.size());
long seed = random.nextLong();
Tuple2, Set>> shrinkableAndParts = generateCurrent(genSize, index, seed);
return createShrinkable(shrinkableAndParts, genSize, seed, Collections.singleton(index));
};
}
private LazyOfShrinkable createShrinkable(
Tuple2, Set>> shrinkableAndParts,
int genSize,
long seed,
Set usedIndices
) {
Shrinkable shrinkable = shrinkableAndParts.get1();
Set> parts = shrinkableAndParts.get2();
LazyOfShrinkable lazyOfShrinkable = new LazyOfShrinkable<>(
shrinkable,
depth(parts),
parts,
(LazyOfShrinkable lazyOf) -> shrink(lazyOf, genSize, seed, usedIndices)
);
addGenerated(lazyOfShrinkable);
return lazyOfShrinkable;
}
private void addGenerated(LazyOfShrinkable lazyOfShrinkable) {
if (peekGenerated() != null) {
peekGenerated().add(lazyOfShrinkable);
}
}
private Set> peekGenerated() {
return generatedParts.peekFirst();
}
private void pushGeneratedLevel() {
generatedParts.addFirst(new HashSet<>());
}
private void popGeneratedLevel() {
generatedParts.removeFirst();
}
private int depth(Set> parts) {
return parts.stream().mapToInt(p -> p.depth).map(depth -> depth + 1).max().orElse(0);
}
private Tuple2, Set>> generateCurrent(int genSize, int index, long seed) {
try {
pushGeneratedLevel();
return Tuple.of(
getGenerator(index, genSize).next(SourceOfRandomness.newRandom(seed)),
peekGenerated()
);
} finally {
// To clean up even if there's an exception during value generation
popGeneratedLevel();
}
}
private RandomGenerator getGenerator(int index, int genSize) {
if (generators.get().get(index) == null) {
RandomGenerator generator = suppliers.get(index).get().generator(genSize);
generators.get().put(index, generator);
}
return generators.get().get(index);
}
private Stream> shrink(
LazyOfShrinkable lazyOf,
int genSize,
long seed,
Set usedIndexes
) {
return JqwikStreamSupport.concat(
shrinkToParts(lazyOf),
shrinkCurrent(lazyOf, genSize, seed, usedIndexes),
shrinkToAlternatives(lazyOf.current, genSize, seed, usedIndexes)
// I don't have an example to show that this adds shrinking quality:
//shrinkToAlternativesAndGrow(lazyOf.current, genSize, seed, usedIndexes)
);
}
private Stream> shrinkCurrent(
LazyOfShrinkable lazyOf,
int genSize,
long seed,
Set usedIndexes
) {
return lazyOf.current.shrink().map(shrinkable -> new LazyOfShrinkable<>(
shrinkable,
lazyOf.depth,
Collections.emptySet(),
(LazyOfShrinkable lazy) -> shrink(lazy, genSize, seed, usedIndexes)
));
}
private Stream> shrinkToParts(LazyOfShrinkable lazyOf) {
return JqwikStreamSupport.concat(
lazyOf.parts.stream().flatMap(this::shrinkToParts),
lazyOf.parts.stream().map(s -> s)
);
}
private Stream> shrinkToAlternatives(Shrinkable current, int genSize, long seed, Set usedIndexes) {
ShrinkingDistance distance = current.distance();
Set newUsedIndexes = new HashSet<>(usedIndexes);
return IntStream
.range(0, suppliers.size())
.filter(index -> !usedIndexes.contains(index))
.peek(newUsedIndexes::add)
.mapToObj(index -> generateCurrent(genSize, index, seed))
.filter(shrinkableAndParts -> shrinkableAndParts.get1().distance().compareTo(distance) < 0)
.map(shrinkableAndParts -> createShrinkable(shrinkableAndParts, genSize, seed, newUsedIndexes));
}
// Currently disabled since I'm not sure if it provides additional value
@SuppressWarnings("unused")
private Stream> shrinkToAlternativesAndGrow(Shrinkable current, int genSize, long seed, Set usedIndexes) {
ShrinkingDistance distance = current.distance();
Set newUsedIndexes = new HashSet<>(usedIndexes);
return IntStream
.range(0, suppliers.size())
.filter(index -> !usedIndexes.contains(index))
.peek(newUsedIndexes::add)
.mapToObj(index -> generateCurrent(genSize, index, seed))
.map(Tuple1::get1)
.filter(tShrinkable -> tShrinkable.distance().compareTo(distance) < 0)
.flatMap(Shrinkable::grow)
.filter(shrinkable -> shrinkable.distance().compareTo(distance) < 0)
.map(grownShrinkable -> createShrinkable(
Tuple.of(grownShrinkable, Collections.emptySet()),
genSize,
seed,
newUsedIndexes
));
}
@Override
public EdgeCases edgeCases(int maxEdgeCases) {
return EdgeCases.none();
}
}