mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
remove jetCheck source
now it lives separately on https://github.com/JetBrains/jetCheck
This commit is contained in:
2
.idea/modules.xml
generated
2
.idea/modules.xml
generated
@@ -164,7 +164,6 @@
|
||||
<module old-name="javac-ref-scanner-8" new-name="intellij.java.jps.javacRefScanner8" />
|
||||
<module old-name="javac2" new-name="intellij.java.compiler.antTasks" />
|
||||
<module old-name="javacvs-src" new-name="intellij.vcs.cvs.javacvs" />
|
||||
<module old-name="jetCheck" new-name="intellij.tools.jetCheck" />
|
||||
<module old-name="jetgroovy" new-name="intellij.groovy" />
|
||||
<module old-name="jira" new-name="intellij.tasks.jira" />
|
||||
<module old-name="jps-builders" new-name="intellij.platform.jps.build" />
|
||||
@@ -692,7 +691,6 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/build/dependencies/intellij.tools.dependencies.iml" filepath="$PROJECT_DIR$/build/dependencies/intellij.tools.dependencies.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/build/dependencies/gradle-api-deps/intellij.tools.dependencies.gradle.iml" filepath="$PROJECT_DIR$/build/dependencies/gradle-api-deps/intellij.tools.dependencies.gradle.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/tools/index-tools/intellij.tools.index.iml" filepath="$PROJECT_DIR$/tools/index-tools/intellij.tools.index.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/jetCheck/intellij.tools.jetCheck.iml" filepath="$PROJECT_DIR$/jetCheck/intellij.tools.jetCheck.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/jps/antLayout/intellij.tools.jps.antTasks.iml" filepath="$PROJECT_DIR$/jps/antLayout/intellij.tools.jps.antTasks.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/jps/standalone-builder/intellij.tools.jps.build.standalone.iml" filepath="$PROJECT_DIR$/jps/standalone-builder/intellij.tools.jps.build.standalone.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/platform/testFramework/bootstrap/intellij.tools.testsBootstrap.iml" filepath="$PROJECT_DIR$/platform/testFramework/bootstrap/intellij.tools.testsBootstrap.iml" />
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
|
||||
<orderEntry type="library" name="jetbrains-annotations-java5" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util" scope="RUNTIME" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,39 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
abstract class AbstractDataStructure implements DataStructure {
|
||||
protected final StructureNode node;
|
||||
protected final int sizeHint;
|
||||
|
||||
AbstractDataStructure(StructureNode node, int sizeHint) {
|
||||
this.node = node;
|
||||
this.sizeHint = sizeHint;
|
||||
}
|
||||
|
||||
int childSizeHint() {
|
||||
return Math.max(1, sizeHint - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSizeHint() {
|
||||
return sizeHint;
|
||||
}
|
||||
|
||||
abstract int drawInt(@NotNull IntDistribution distribution);
|
||||
|
||||
int suggestCollectionSize() {
|
||||
return drawInt(IntDistribution.uniform(0, getSizeHint()));
|
||||
}
|
||||
|
||||
abstract <T> T generateNonShrinkable(@NotNull Generator<T> generator);
|
||||
|
||||
abstract <T> T generateConditional(@NotNull Generator<T> generator, @NotNull Predicate<? super T> condition);
|
||||
|
||||
abstract void changeKind(StructureKind kind);
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
public class BoundedIntDistribution implements IntDistribution {
|
||||
public static final IntDistribution ALL_INTS = IntDistribution.uniform(Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||
private final int min;
|
||||
private final int max;
|
||||
private final ToIntFunction<Random> producer;
|
||||
|
||||
BoundedIntDistribution(int min, int max, ToIntFunction<Random> producer) {
|
||||
if (min > max) throw new IllegalArgumentException(min + ">" + max);
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.producer = producer;
|
||||
}
|
||||
|
||||
int getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
int getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int generateInt(Random random) {
|
||||
int i = producer.applyAsInt(random);
|
||||
if (i < min || i > max) {
|
||||
throw new IllegalStateException("Int out of bounds produced by " + producer + ": " + i + " not in [" + min + ", " + max + "]");
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidValue(int i) {
|
||||
return i >= min && i <= max;
|
||||
}
|
||||
|
||||
public static BoundedIntDistribution bound(int min, int max, IntDistribution distribution) {
|
||||
return new BoundedIntDistribution(min, max, random -> Math.min(Math.max(distribution.generateInt(random), min), max)) {
|
||||
@Override
|
||||
public boolean isValidValue(int i) {
|
||||
return super.isValidValue(i) && distribution.isValidValue(i);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
class CannotRestoreValue extends RuntimeException {
|
||||
CannotRestoreValue() {
|
||||
}
|
||||
|
||||
CannotRestoreValue(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
@SuppressWarnings("ExceptionClassNameDoesntEndWithException")
|
||||
public class CannotSatisfyCondition extends RuntimeException {
|
||||
private final Predicate<?> condition;
|
||||
|
||||
CannotSatisfyCondition(@NotNull Predicate<?> condition) {
|
||||
super("Cannot satisfy condition " + condition);
|
||||
this.condition = condition;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Predicate<?> getCondition() {
|
||||
return condition;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class CounterExampleImpl<T> implements PropertyFailure.CounterExample<T> {
|
||||
final StructureNode data;
|
||||
private final T value;
|
||||
@Nullable private final Throwable exception;
|
||||
private final Iteration<T> iteration;
|
||||
|
||||
private CounterExampleImpl(StructureNode data, T value, @Nullable Throwable exception, Iteration<T> iteration) {
|
||||
this.data = data;
|
||||
this.value = value;
|
||||
this.exception = exception;
|
||||
this.iteration = iteration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getExampleValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Throwable getExceptionCause() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CounterExampleImpl<T> replay() {
|
||||
T value = iteration.generateValue(createReplayData());
|
||||
CounterExampleImpl<T> example = checkProperty(iteration, value, data);
|
||||
return example != null ? example :
|
||||
new CounterExampleImpl<>(data, value, new IllegalStateException("Replaying failure is unexpectedly successful!"), iteration);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getSerializedData() {
|
||||
return DataSerializer.serialize(iteration, data);
|
||||
}
|
||||
|
||||
ReplayDataStructure createReplayData() {
|
||||
return new ReplayDataStructure(data, iteration.sizeHint, IntCustomizer::checkValidInt);
|
||||
}
|
||||
|
||||
static <T> CounterExampleImpl<T> checkProperty(Iteration<T> iteration, T value, StructureNode node) {
|
||||
try {
|
||||
if (!iteration.session.property.test(value)) {
|
||||
return new CounterExampleImpl<>(node, value, null, iteration);
|
||||
}
|
||||
}
|
||||
catch (Throwable e) {
|
||||
return new CounterExampleImpl<>(node, value, e, iteration);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
class DataSerializer {
|
||||
|
||||
private static int readINT(ByteArrayInputStream record) {
|
||||
int val = readWithEof(record);
|
||||
if (val < 192) {
|
||||
return val;
|
||||
}
|
||||
|
||||
int res = val - 192;
|
||||
for (int sh = 6; ; sh += 7) {
|
||||
int next = readWithEof(record);
|
||||
res |= (next & 0x7F) << sh;
|
||||
if ((next & 0x80) == 0) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int readWithEof(ByteArrayInputStream record) {
|
||||
if (record.available() <= 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return record.read();
|
||||
}
|
||||
|
||||
static void writeINT(DataOutput record, int val) throws IOException {
|
||||
if (0 > val || val >= 192) {
|
||||
record.writeByte(192 + (val & 0x3F));
|
||||
val >>>= 6;
|
||||
while (val >= 128) {
|
||||
record.writeByte((val & 0x7F) | 0x80);
|
||||
val >>>= 7;
|
||||
}
|
||||
}
|
||||
record.writeByte(val);
|
||||
}
|
||||
|
||||
static String serialize(Iteration iteration, StructureElement node) {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
try (DataOutputStream data = new DataOutputStream(stream)) {
|
||||
writeINT(data, (int)(iteration.iterationSeed >> 32));
|
||||
writeINT(data, (int)iteration.iterationSeed);
|
||||
writeINT(data, iteration.sizeHint);
|
||||
node.serialize(data);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return Base64.getEncoder().encodeToString(stream.toByteArray());
|
||||
}
|
||||
|
||||
static void deserializeInto(String data, PropertyChecker.Parameters parameters) {
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream(Base64.getDecoder().decode(data));
|
||||
|
||||
int seedHigh = readINT(stream);
|
||||
int seedLow = readINT(stream);
|
||||
parameters.globalSeed = (long)seedHigh << 32 | seedLow & 0xFFFFFFFFL;
|
||||
|
||||
int hint = readINT(stream);
|
||||
parameters.sizeHintFun = __ -> hint;
|
||||
|
||||
parameters.serializedData = (IntDistribution dist) -> {
|
||||
int i = readINT(stream);
|
||||
if (!dist.isValidValue(i)) {
|
||||
throw new CannotRestoreValue("Error restoring from serialized \"rechecking\" data. Possible cause: either the test or the environment it depends on has changed.");
|
||||
}
|
||||
return i;
|
||||
};
|
||||
}
|
||||
|
||||
static class EOFException extends RuntimeException {}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A context for {@link Generator}s. Primitive generators (e.g. {@link Generator#integers} know how to obtain
|
||||
* random data from it, other generators build more complex values on top of that, by running {@link #generate(Generator)} recursively.
|
||||
*/
|
||||
public interface DataStructure {
|
||||
|
||||
/**
|
||||
* @return a non-negative number used by various generators to guide the sizes of structures (e.g. collections) they create.
|
||||
* The sizes need not be exactly equal to this hint, but in average bigger hints should in average correspond to bigger structures. When generators invoke other generators, the size hint of the structure used by called generators is
|
||||
* generally less than the original one's.
|
||||
*/
|
||||
int getSizeHint();
|
||||
|
||||
/** Runs the given generator on a data sub-structure of this structure and returns the result */
|
||||
<T> T generate(@NotNull Generator<T> generator);
|
||||
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
public class FrequencyGenerator<T> extends Generator<T> {
|
||||
private final List<WeightedGenerator<T>> alternatives;
|
||||
|
||||
FrequencyGenerator(int weight1, Generator<? extends T> alternative1,
|
||||
int weight2, Generator<? extends T> alternative2) {
|
||||
this(weightedGenerators(weight1, alternative1, weight2, alternative2));
|
||||
}
|
||||
|
||||
private FrequencyGenerator(List<WeightedGenerator<T>> alternatives) {
|
||||
super(frequencyFunction(alternatives));
|
||||
this.alternatives = alternatives;
|
||||
}
|
||||
|
||||
FrequencyGenerator<T> with(int weight, Generator<? extends T> alternative) {
|
||||
List<WeightedGenerator<T>> alternatives = new ArrayList<>(this.alternatives);
|
||||
alternatives.add(new WeightedGenerator<>(weight, alternative));
|
||||
return new FrequencyGenerator<>(alternatives);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static <T> Function<DataStructure, T> frequencyFunction(List<WeightedGenerator<T>> alternatives) {
|
||||
List<Integer> weights = alternatives.stream().map(w -> w.weight).collect(Collectors.toList());
|
||||
IntDistribution distribution = IntDistribution.frequencyDistribution(weights);
|
||||
return data -> {
|
||||
((AbstractDataStructure) data).changeKind(StructureKind.CHOICE);
|
||||
return data.generate(alternatives.get(((AbstractDataStructure)data).drawInt(distribution)).generator);
|
||||
};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static <T> List<WeightedGenerator<T>> weightedGenerators(int weight1, Generator<? extends T> alternative1,
|
||||
int weight2, Generator<? extends T> alternative2) {
|
||||
List<WeightedGenerator<T>> alternatives = new ArrayList<>();
|
||||
alternatives.add(new WeightedGenerator<>(weight1, alternative1));
|
||||
alternatives.add(new WeightedGenerator<>(weight2, alternative2));
|
||||
return alternatives;
|
||||
}
|
||||
|
||||
private static class WeightedGenerator<T> {
|
||||
final int weight;
|
||||
final Generator<? extends T> generator;
|
||||
|
||||
WeightedGenerator(int weight, Generator<? extends T> generator) {
|
||||
this.weight = weight;
|
||||
this.generator = generator;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
class GenerativeDataStructure extends AbstractDataStructure {
|
||||
private final CurrentData dataTracker;
|
||||
private final IntSource random;
|
||||
|
||||
GenerativeDataStructure(IntSource random, StructureNode node, int sizeHint) {
|
||||
this(null, random, node, sizeHint);
|
||||
}
|
||||
|
||||
private GenerativeDataStructure(@Nullable CurrentData dataTracker, IntSource random, StructureNode node, int sizeHint) {
|
||||
super(node, sizeHint);
|
||||
this.random = random;
|
||||
this.dataTracker = dataTracker != null ? dataTracker : new CurrentData();
|
||||
}
|
||||
|
||||
@Override
|
||||
int drawInt(@NotNull IntDistribution distribution) {
|
||||
dataTracker.checkContext(this);
|
||||
int i = random.drawInt(distribution);
|
||||
node.addChild(new IntData(node.id.childId(null), i, distribution));
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T generate(@NotNull Generator<T> generator) {
|
||||
return dataTracker.generateOn(generator, subStructure(generator, childSizeHint()), this);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private GenerativeDataStructure subStructure(@NotNull Generator<?> generator, int childSizeHint) {
|
||||
return new GenerativeDataStructure(dataTracker, random, node.subStructure(generator), childSizeHint);
|
||||
}
|
||||
|
||||
@Override
|
||||
<T> T generateNonShrinkable(@NotNull Generator<T> generator) {
|
||||
GenerativeDataStructure data = subStructure(generator, sizeHint);
|
||||
data.node.shrinkProhibited = true;
|
||||
return dataTracker.generateOn(generator, data, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
<T> T generateConditional(@NotNull Generator<T> generator, @NotNull Predicate<? super T> condition) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
GenerativeDataStructure structure = subStructure(generator, childSizeHint());
|
||||
T value = dataTracker.generateOn(generator, structure, this);
|
||||
if (condition.test(value)) return value;
|
||||
|
||||
node.removeLastChild(structure.node);
|
||||
}
|
||||
throw new CannotSatisfyCondition(condition);
|
||||
}
|
||||
|
||||
@Override
|
||||
void changeKind(StructureKind kind) {
|
||||
node.kind = kind;
|
||||
}
|
||||
|
||||
private class CurrentData {
|
||||
DataStructure current = GenerativeDataStructure.this;
|
||||
|
||||
<T> T generateOn(Generator<T> gen, GenerativeDataStructure data, GenerativeDataStructure parent) {
|
||||
checkContext(parent);
|
||||
current = data;
|
||||
try {
|
||||
return gen.getGeneratorFunction().apply(data);
|
||||
}
|
||||
finally {
|
||||
current = parent;
|
||||
}
|
||||
}
|
||||
|
||||
void checkContext(GenerativeDataStructure data) {
|
||||
if (current != data) throw new WrongDataStructure();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WrongDataStructure extends IllegalStateException {
|
||||
WrongDataStructure() {
|
||||
super("You're calling methods on wrong DataStructure, confusing nested lambda arguments?");
|
||||
}
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* A generator for objects based on random data from {@link DataStructure}.<p/>
|
||||
*
|
||||
* A generator is a function that takes a source of random data (which is DataStructure) and, using that random data
|
||||
* (e.g. by calling more primitive generators and reinterpreting their results), constructs a value of type {@code T}.
|
||||
* For data structures returning the same data, generators should produce equivalent values.<p/>
|
||||
*
|
||||
* Generators for standard types can be obtained using static methods of this class (e.g. {@link #integers}, {@link #listsOf} etc).
|
||||
* Generators for custom types can be created by deriving them from standard types (e.g. via {@link #map}, {@link #flatMap}, {@link #zipWith})
|
||||
* or by writing your own from scratch ({@link #from(Function)}).
|
||||
*/
|
||||
public class Generator<T> {
|
||||
private final Function<DataStructure, T> myFunction;
|
||||
|
||||
Generator(Function<DataStructure, T> function) {
|
||||
myFunction = function;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a generator from a custom function, that creates objects of the given type based on the data from {@link DataStructure}.
|
||||
* The generator may invoke other, more primitive, generators using {@link DataStructure#generate(Generator)}.<p/>
|
||||
*
|
||||
* When a property is falsified, the DataStructure is attempted to be minimized, and the generator will be run on
|
||||
* ever "smaller" versions of it, this enables automatic minimization on all kinds of generated types.<p/>
|
||||
*
|
||||
* To ensure test reproducibility during re-run or minimization phase,
|
||||
* the result of the generators should only depend on the DataStructure. Generators should not have any side effects
|
||||
* or depend on the outside world. Generators may have internal mutable state accessible to other (nested) generators,
|
||||
* but that's error-prone, difficult and computationally expensive to minimize. If you still think you need that,
|
||||
* please see {@link ImperativeCommand} for potentially more convenient way of testing stateful systems.
|
||||
*/
|
||||
@NotNull
|
||||
public static <T> Generator<T> from(@NotNull Function<DataStructure, T> function) {
|
||||
return new Generator<>(function);
|
||||
}
|
||||
|
||||
Function<DataStructure, T> getGeneratorFunction() {
|
||||
return myFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes "this" generator, and then applies the given function to transform the generated value in any way.
|
||||
* The function should not depend on anything besides its argument.
|
||||
*/
|
||||
public <V> Generator<V> map(@NotNull Function<? super T, ? extends V> fun) {
|
||||
return from(data -> fun.apply(myFunction.apply(data)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes "this" generator, and then applies the given function to provide a new generator that additionally
|
||||
* depends on the generated value.
|
||||
* The function should not depend on anything besides its argument.
|
||||
*/
|
||||
public <V> Generator<V> flatMap(@NotNull Function<? super T, ? extends Generator<V>> fun) {
|
||||
return from(data -> {
|
||||
T value = data.generate(this);
|
||||
Generator<V> result = fun.apply(value);
|
||||
if (result == null) throw new NullPointerException(fun + " returned null on " + value);
|
||||
return data.generate(result);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off automatic minimization for the data produced by this generator (and its components, if any).
|
||||
* This can be useful to speed up minimization by not wasting time on shrinking objects where it makes no sense.
|
||||
* It's especially useful when using stateful generators (e.g. {@link ImperativeCommand}), because
|
||||
* shrinkable values there can lead to doubling of the shrinking time.
|
||||
*/
|
||||
public Generator<T> noShrink() {
|
||||
return from(data -> ((AbstractDataStructure)data).generateNonShrinkable(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to generate the value several times, until one comes out that satisfies the given condition. That value is returned as generator result.
|
||||
* Results of all previous attempts are discarded. During shrinking, the underlying data structures from those attempts
|
||||
* won't be used for re-running generation, so be careful that those attempts don't leave any traces of themselves
|
||||
* (e.g. side effects, even ones internal to an outer generator).<p/>
|
||||
*
|
||||
* If the condition still fails after a large number of attempts, data generation is stopped prematurely and {@link CannotSatisfyCondition} exception is thrown.<p/>
|
||||
*
|
||||
* This method is useful to avoid infrequent corner cases (e.g. {@code integers().suchThat(i -> i != 0)}).
|
||||
* To eliminate large portions of search space, this strategy might prove ineffective
|
||||
* and result in generator failure due to inability to come up with satisfying examples
|
||||
* (e.g. {@code integers().suchThat(i -> i > 0 && i <= 10)}
|
||||
* where the condition would be {@code true} in just 10 of about 4 billion times). In such cases, please consider changing the generator instead of using {@code suchThat}.
|
||||
*/
|
||||
public Generator<T> suchThat(@NotNull Predicate<? super T> condition) {
|
||||
return from(data -> ((AbstractDataStructure)data).generateConditional(this, condition));
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// common generators
|
||||
// ------------------------------------
|
||||
|
||||
/** A generator that always returns the same value */
|
||||
public static <T> Generator<T> constant(T value) {
|
||||
return from(data -> value);
|
||||
}
|
||||
|
||||
/** A generator that returns one of the given values with equal probability */
|
||||
@SafeVarargs
|
||||
public static <T> Generator<T> sampledFrom(T... values) {
|
||||
return sampledFrom(Arrays.asList(values));
|
||||
}
|
||||
|
||||
/** A generator that returns one of the given values with equal probability */
|
||||
public static <T> Generator<T> sampledFrom(List<T> values) {
|
||||
return anyOf(values.stream().map(Generator::constant).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
/** Delegates to one of the given generators with equal probability */
|
||||
@SafeVarargs
|
||||
public static <T> Generator<T> anyOf(Generator<? extends T>... alternatives) {
|
||||
return anyOf(Arrays.asList(alternatives));
|
||||
}
|
||||
|
||||
/** Delegates to one of the given generators with equal probability */
|
||||
public static <T> Generator<T> anyOf(List<? extends Generator<? extends T>> alternatives) {
|
||||
if (alternatives.isEmpty()) throw new IllegalArgumentException("No alternatives to choose from");
|
||||
return from(data -> {
|
||||
((AbstractDataStructure) data).changeKind(StructureKind.CHOICE);
|
||||
int index = ((AbstractDataStructure)data).drawInt(IntDistribution.uniform(0, alternatives.size() - 1));
|
||||
return data.generate(alternatives.get(index));
|
||||
});
|
||||
}
|
||||
|
||||
/** Delegates one of the two generators with probability corresponding to their weights */
|
||||
public static <T> FrequencyGenerator<T> frequency(int weight1, Generator<? extends T> alternative1,
|
||||
int weight2, Generator<? extends T> alternative2) {
|
||||
return new FrequencyGenerator<>(weight1, alternative1, weight2, alternative2);
|
||||
}
|
||||
|
||||
/** Delegates one of the three generators with probability corresponding to their weights */
|
||||
public static <T> Generator<T> frequency(int weight1, Generator<? extends T> alternative1,
|
||||
int weight2, Generator<? extends T> alternative2,
|
||||
int weight3, Generator<? extends T> alternative3) {
|
||||
return frequency(weight1, alternative1, weight2, alternative2).with(weight3, alternative3);
|
||||
}
|
||||
|
||||
/** Gets the data from two generators and invokes the given function to produce a result based on the two generated values. */
|
||||
public static <A,B,C> Generator<C> zipWith(Generator<A> gen1, Generator<B> gen2, BiFunction<? super A, ? super B, ? extends C> zip) {
|
||||
return from(data -> zip.apply(data.generate(gen1), data.generate(gen2)));
|
||||
}
|
||||
|
||||
/**
|
||||
* A fixed-point combinator to easily create recursive generators by giving a name to the whole generator and allowing to reuse it inside itself. For example, a recursive tree generator could be defined as follows by binding itself to the name {@code nodes}:
|
||||
* <pre>
|
||||
* Generator<Node> gen = Generator.recursive(<b>nodes</b> -> Generator.anyOf(
|
||||
* Generator.constant(new Leaf()),
|
||||
* Generator.listsOf(<b>nodes</b>).map(children -> new Composite(children))))
|
||||
* </pre>
|
||||
* @return the generator returned from the passed function
|
||||
*/
|
||||
@NotNull
|
||||
public static <T> Generator<T> recursive(@NotNull Function<? super Generator<T>, ? extends Generator<T>> createGenerator) {
|
||||
AtomicReference<Generator<T>> ref = new AtomicReference<>();
|
||||
Generator<T> result = from(data -> ref.get().getGeneratorFunction().apply(data));
|
||||
ref.set(createGenerator.apply(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/** A generator that returns 'true' or 'false' */
|
||||
public static Generator<Boolean> booleans() {
|
||||
return integers(0, 1).map(i -> i == 1);
|
||||
}
|
||||
|
||||
// char generators
|
||||
|
||||
/** Generates characters in the given range (both ends inclusive) */
|
||||
public static Generator<Character> charsInRange(char min, char max) {
|
||||
return integers(min, max).map(i -> (char)i.intValue());
|
||||
}
|
||||
|
||||
/** Generates characters that occur in the given string */
|
||||
public static Generator<Character> charsFrom(String possibleChars) {
|
||||
return sampledFrom(IntStream.range(0, possibleChars.length()).mapToObj(possibleChars::charAt).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
/** Generates ASCII characters excluding the system ones (lower than 32) */
|
||||
public static Generator<Character> asciiPrintableChars() {
|
||||
return charsInRange((char)32, (char)126);
|
||||
}
|
||||
|
||||
/** Generates uppercase latin letters */
|
||||
public static Generator<Character> asciiUppercaseChars() {
|
||||
return charsInRange('A', 'Z');
|
||||
}
|
||||
|
||||
/** Generates lowercase latin letters */
|
||||
public static Generator<Character> asciiLowercaseChars() {
|
||||
return charsInRange('a', 'z');
|
||||
}
|
||||
|
||||
/** Generates latin letters, with a preference for lowercase ones */
|
||||
public static Generator<Character> asciiLetters() {
|
||||
return frequency(9, asciiLowercaseChars(), 1, asciiUppercaseChars()).noShrink();
|
||||
}
|
||||
|
||||
/** Generates decimal digits */
|
||||
public static Generator<Character> digits() {
|
||||
return charsInRange('0', '9');
|
||||
}
|
||||
|
||||
// strings
|
||||
|
||||
/** Generates (possibly empty) random strings consisting of the given characters (provided as a string) */
|
||||
public static Generator<String> stringsOf(@NotNull String possibleChars) {
|
||||
return stringsOf(charsFrom(possibleChars));
|
||||
}
|
||||
|
||||
/** Generates (possibly empty) random strings consisting of characters provided by the given generator */
|
||||
public static Generator<String> stringsOf(@NotNull Generator<Character> charGen) {
|
||||
return listsOf(charGen).map(Generator::charsToString);
|
||||
}
|
||||
|
||||
/** Generates (possibly empty) random strings of length in the given distribution, consisting of characters provided by the given generator */
|
||||
public static Generator<String> stringsOf(@NotNull IntDistribution length, @NotNull Generator<Character> charGen) {
|
||||
return listsOf(length, charGen).map(Generator::charsToString);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String charsToString(List<Character> chars) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
chars.forEach(sb::append);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Generates random strings consisting ASCII letters and digit and starting with a letter */
|
||||
public static Generator<String> asciiIdentifiers() {
|
||||
return stringsOf(frequency(50, asciiLetters(),
|
||||
5, digits(),
|
||||
1, constant('_')))
|
||||
.suchThat(s -> s.length() > 0 && !Character.isDigit(s.charAt(0)));
|
||||
}
|
||||
|
||||
|
||||
// numbers
|
||||
|
||||
/** Generates any integers */
|
||||
public static Generator<Integer> integers() {
|
||||
return integers(BoundedIntDistribution.ALL_INTS);
|
||||
}
|
||||
|
||||
public static Generator<Integer> naturals() {
|
||||
return integers(0, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/** Generates integers uniformly distributed in the given range (both ends inclusive) */
|
||||
public static Generator<Integer> integers(int min, int max) {
|
||||
return integers(IntDistribution.uniform(min, max));
|
||||
}
|
||||
|
||||
/** Generates integers with the given distribution */
|
||||
public static Generator<Integer> integers(@NotNull IntDistribution distribution) {
|
||||
return from(data -> ((AbstractDataStructure)data).drawInt(distribution));
|
||||
}
|
||||
|
||||
/** Generates any doubles, including infinities and NaN */
|
||||
public static Generator<Double> doubles() {
|
||||
return from(data -> {
|
||||
long i1 = data.generate(integers(BoundedIntDistribution.ALL_INTS));
|
||||
long i2 = data.generate(integers(BoundedIntDistribution.ALL_INTS));
|
||||
return Double.longBitsToDouble((i1 << 32) + (i2 & 0xffffffffL));
|
||||
});
|
||||
}
|
||||
|
||||
// lists
|
||||
|
||||
/** Generates (possibly empty) lists of values produced by the given generator */
|
||||
public static <T> Generator<List<T>> listsOf(Generator<T> itemGenerator) {
|
||||
return from(data -> generateList(itemGenerator, data, ((AbstractDataStructure)data).suggestCollectionSize()));
|
||||
}
|
||||
|
||||
/** Generates non-empty lists of values produced by the given generator */
|
||||
public static <T> Generator<List<T>> nonEmptyLists(Generator<T> itemGenerator) {
|
||||
return listsOf(itemGenerator).suchThat(l -> !l.isEmpty());
|
||||
}
|
||||
|
||||
/** Generates lists of values produced by the given generator. The list length is determined by the given distribution. */
|
||||
public static <T> Generator<List<T>> listsOf(IntDistribution length, Generator<T> itemGenerator) {
|
||||
return from(data -> generateList(itemGenerator, data, ((AbstractDataStructure)data).drawInt(length)));
|
||||
}
|
||||
|
||||
private static <T> List<T> generateList(Generator<T> itemGenerator, DataStructure data, int size) {
|
||||
((AbstractDataStructure) data).changeKind(StructureKind.LIST);
|
||||
List<T> list = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
list.add(data.generate(itemGenerator));
|
||||
}
|
||||
return Collections.unmodifiableList(list);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
public class GeneratorException extends RuntimeException {
|
||||
|
||||
GeneratorException(Iteration<?> iteration, Throwable cause) {
|
||||
super("Exception while generating data, " + iteration.printSeeds(), cause);
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
*/
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents an action with potential side effects, for single-threaded property-based testing of stateful systems.
|
||||
* The engine executes a top-level command, which should prepare the system-under-test and run the sequence of nested (actual) commands
|
||||
* that change the system, check the needed invariants, and log all activity to allow to reproduce failing scenarios.<p/>
|
||||
*
|
||||
* A typical way to test with imperative commands looks like this:
|
||||
* <pre>
|
||||
* PropertyChecker.checkScenarios(() -> env -> {
|
||||
* System sys = setUpSystem();
|
||||
* try {
|
||||
* // run a random sequence of commands
|
||||
* env.performCommands(Generator.sampledFrom(new Command1(sys), new Command2(sys), ...))
|
||||
* assertPropertyHolds(sys); // optional; fail with an exception if some invariant is broken
|
||||
* } finally {
|
||||
* tearDownSystem(); // if any resources should be freed
|
||||
* }
|
||||
* })
|
||||
* ...
|
||||
* class Command1 implements ImperativeCommand {
|
||||
* System sys;
|
||||
* Command1(System sys) {
|
||||
* this.sys = sys;
|
||||
* }
|
||||
*
|
||||
* public void performCommand(Environment env) {
|
||||
* env.logMessage("Performing command1");
|
||||
* // do some actions on 'sys' using environment to generate random data (and log it) on the way, e.g.:
|
||||
* Item item = env.generateData(Generator.sampledFrom(sys.currentItems), "working on %s item");
|
||||
* modifyItemInTheSystem(sys, item); // may change the system and the items within it
|
||||
* }
|
||||
* }
|
||||
* ...
|
||||
* </pre>
|
||||
*
|
||||
* If any command fails with an exception, the property being checked is considered to be falsified and the test fails.
|
||||
* In the error message, the causing exception is printed together with command log.
|
||||
* Commands can and should log what they're doing using
|
||||
* {@link Environment#logMessage(String)} and {@link Environment#generateValue(Generator, String)} so that you
|
||||
* can restore the order of events that leads to the failure.<p/>
|
||||
*
|
||||
* Top-level command should not have any side effects on the outside world,
|
||||
* otherwise proper test case separation and minimization will be impossible.
|
||||
* The supplier that creates the top-level command should
|
||||
* return a "fresh" object each time, as it's invoked on each testing and minimization iterations.
|
||||
* Nested commands may have side effects, provided that those effects are fully contained within the system-under-test,
|
||||
* which is set up and disposed inside the top-level command on each iteration.<p/>
|
||||
*
|
||||
* Test case minimization is complicated, when commands make random choices based on the current state of the system
|
||||
* (which can change even after removing irrelevant commands during minimization).
|
||||
* The engine tries to account for that heuristically. <b>Rule of thumb</b>: whenever your command generates an index into some list,
|
||||
* or uses {@link Generator#sampledFrom} on system elements, try to ensure that these elements have some predictable ordering.
|
||||
* It's ideal if each command effect on these elements is either adding or removing a contiguous range in that list.
|
||||
*/
|
||||
public interface ImperativeCommand {
|
||||
/** Perform the actual change on the system-under-test, using {@code env} to generate random data and log the progress */
|
||||
void performCommand(@NotNull Environment env);
|
||||
|
||||
/** A helper object passed into {@link #performCommand} to allow for logging and ad hoc random data generation */
|
||||
interface Environment {
|
||||
|
||||
/** Add a log message. The whole execution log would be printed if the command fails */
|
||||
void logMessage(@NotNull String message);
|
||||
|
||||
/** Generate a pseudo-random value using the given generator.
|
||||
* Optionally log a message, so that when a test fails, you'd know the value was generated.
|
||||
* The message is a Java format string, so you can use it to include the generated value, e.g.
|
||||
* {@code String s = generateValue(stringsOf(asciiLetters(), "Generated %s")}.<p/>
|
||||
* If you don't want to generate message, or would like to show the generated value in a custom way, pass {@code null}.
|
||||
* You can use {@link #logMessage} later to still leave a trace of this value generation in the log.<p/>
|
||||
*
|
||||
* Consider making generators non-shrinkable (by invoking {@link Generator#noShrink()}) where possible
|
||||
* because it can speed up overall failing scenario minimization significantly.
|
||||
*/
|
||||
<T> T generateValue(@NotNull Generator<T> generator, @Nullable String logMessage);
|
||||
|
||||
/** Executes a sequence (of length with the given distribution) of random nested commands (produced by the given generator) */
|
||||
void executeCommands(IntDistribution count, Generator<? extends ImperativeCommand> cmdGen);
|
||||
|
||||
/** Executes a non-empty sequence of random nested commands (produced by the given generator) */
|
||||
void executeCommands(Generator<? extends ImperativeCommand> cmdGen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
|
||||
interface IntCustomizer {
|
||||
int suggestInt(IntData data, IntDistribution currentDistribution);
|
||||
|
||||
static int checkValidInt(IntData data, IntDistribution currentDistribution) {
|
||||
int value = data.value;
|
||||
if (!currentDistribution.isValidValue(value)) throw new CannotRestoreValue();
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class CombinatorialIntCustomizer implements IntCustomizer {
|
||||
private final LinkedHashMap<NodeId, Set<Integer>> valuesToTry;
|
||||
private final Map<NodeId, Integer> currentCombination;
|
||||
private final Map<NodeId, IntDistribution> changedDistributions = new HashMap<>();
|
||||
|
||||
CombinatorialIntCustomizer() {
|
||||
this(new LinkedHashMap<>(), new HashMap<>());
|
||||
}
|
||||
|
||||
private CombinatorialIntCustomizer(LinkedHashMap<NodeId, Set<Integer>> valuesToTry, Map<NodeId, Integer> currentCombination) {
|
||||
this.valuesToTry = valuesToTry;
|
||||
this.currentCombination = currentCombination;
|
||||
}
|
||||
|
||||
public int suggestInt(IntData data, IntDistribution currentDistribution) {
|
||||
if (data.distribution instanceof BoundedIntDistribution &&
|
||||
currentDistribution instanceof BoundedIntDistribution &&
|
||||
registerDifferentRange(data, (BoundedIntDistribution)currentDistribution, (BoundedIntDistribution)data.distribution)) {
|
||||
return suggestCombinatorialVariant(data, currentDistribution);
|
||||
}
|
||||
return IntCustomizer.checkValidInt(data, currentDistribution);
|
||||
}
|
||||
|
||||
private int suggestCombinatorialVariant(IntData data, IntDistribution currentDistribution) {
|
||||
int value = currentCombination.computeIfAbsent(data.id, __ -> valuesToTry.get(data.id).iterator().next());
|
||||
if (currentDistribution.isValidValue(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new CannotRestoreValue();
|
||||
}
|
||||
|
||||
private boolean registerDifferentRange(IntData data, BoundedIntDistribution current, BoundedIntDistribution original) {
|
||||
if (currentCombination.containsKey(data.id)) {
|
||||
changedDistributions.put(data.id, current);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (original.getMax() != current.getMax() || original.getMin() != current.getMin()) {
|
||||
LinkedHashSet<Integer> possibleValues = getPossibleValues(data, current, original);
|
||||
if (!possibleValues.isEmpty()) {
|
||||
assert !valuesToTry.containsKey(data.id);
|
||||
valuesToTry.put(data.id, possibleValues);
|
||||
changedDistributions.put(data.id, current);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private LinkedHashSet<Integer> getPossibleValues(IntData data, BoundedIntDistribution current, BoundedIntDistribution original) {
|
||||
List<Integer> possibleValues = new ArrayList<>();
|
||||
int fromStart = data.value - original.getMin();
|
||||
int fromEnd = original.getMax() - data.value;
|
||||
|
||||
int sameDistanceFromStart = current.getMin() + fromStart;
|
||||
int sameDistanceFromEnd = current.getMax() - fromEnd;
|
||||
|
||||
if (!tooManyCombinations()) {
|
||||
if (fromStart < fromEnd) {
|
||||
possibleValues.add(sameDistanceFromStart);
|
||||
possibleValues.add(sameDistanceFromEnd);
|
||||
} else {
|
||||
possibleValues.add(sameDistanceFromEnd);
|
||||
possibleValues.add(sameDistanceFromStart);
|
||||
}
|
||||
}
|
||||
possibleValues.add(data.value);
|
||||
|
||||
return possibleValues.stream()
|
||||
.map(value -> Math.min(Math.max(value, current.getMin()), current.getMax()))
|
||||
.filter(current::isValidValue)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
}
|
||||
|
||||
private boolean tooManyCombinations() {
|
||||
return valuesToTry.values().stream().filter(s -> s.size() > 1).count() > 3;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
CombinatorialIntCustomizer nextAttempt() {
|
||||
Map<NodeId, Integer> nextCombination = new HashMap<>(currentCombination);
|
||||
for (Map.Entry<NodeId, Set<Integer>> entry : valuesToTry.entrySet()) {
|
||||
List<Integer> possibleValues = new ArrayList<>(entry.getValue());
|
||||
Integer usedValue = currentCombination.get(entry.getKey());
|
||||
int index = possibleValues.indexOf(usedValue);
|
||||
if (index < possibleValues.size() - 1) {
|
||||
// found a position which can be incremented by 1
|
||||
nextCombination.put(entry.getKey(), possibleValues.get(index + 1));
|
||||
return new CombinatorialIntCustomizer(valuesToTry, nextCombination);
|
||||
}
|
||||
// digit overflow in this position, so zero it and try incrementing the next one
|
||||
nextCombination.put(entry.getKey(), possibleValues.get(0));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
StructureNode writeChanges(StructureNode node) {
|
||||
StructureNode result = node;
|
||||
for (Map.Entry<NodeId, IntDistribution> entry : changedDistributions.entrySet()) {
|
||||
NodeId id = entry.getKey();
|
||||
result = result.replace(id, new IntData(id, currentCombination.get(id), entry.getValue()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int countVariants() {
|
||||
return valuesToTry.values().stream().mapToInt(Set::size).reduce(1, (a, b) -> a*b);
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Used for generating random int values with custom distribution, and ensuring that minimized integer values don't violate that distribution.
|
||||
*/
|
||||
public interface IntDistribution {
|
||||
/** Returns an int value distributed in the needed fashion using the given Random */
|
||||
int generateInt(Random random);
|
||||
|
||||
/** @return true if the given value is valid for this distribution */
|
||||
boolean isValidValue(int i);
|
||||
|
||||
/**
|
||||
* This distribution returns an integer uniformly distributed between {@code min} and {@code max} (both ends inclusive).
|
||||
*/
|
||||
static IntDistribution uniform(int min, int max) {
|
||||
return new BoundedIntDistribution(min, max, r -> {
|
||||
if (min == max) return min;
|
||||
|
||||
int i = r.nextInt();
|
||||
return i >= min && i <= max ? i : Math.abs(i) % (max - min + 1) + min;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Geometric distribution ("number of failures until first success") with a given mean
|
||||
*/
|
||||
static IntDistribution geometric(int mean) {
|
||||
double p = 1.0 / (mean + 1);
|
||||
return new IntDistribution() {
|
||||
@Override
|
||||
public int generateInt(Random random) {
|
||||
double u = random.nextDouble();
|
||||
return (int) (Math.log(u) / Math.log(1 - p));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidValue(int i) {
|
||||
return i >= 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The distribution for numbers in {@code [0, ..., weights.size()-1]} range, where the probability of {@code i}
|
||||
* is equal to {@code weights.get(i)/sum(weights)}.
|
||||
*/
|
||||
static IntDistribution frequencyDistribution(List<Integer> weights) {
|
||||
if (weights.isEmpty()) throw new IllegalArgumentException("No alternatives to choose from");
|
||||
|
||||
int sum = weights.stream().reduce(0, (a, b) -> a + b);
|
||||
return new BoundedIntDistribution(0, weights.size() - 1, r -> {
|
||||
int value = r.nextInt(sum);
|
||||
for (int i = 0; i < weights.size(); i++) {
|
||||
value -= weights.get(i);
|
||||
if (value < 0) return i;
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
interface IntSource {
|
||||
int drawInt(IntDistribution distribution);
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
class Iteration<T> {
|
||||
|
||||
private static final Predicate<Object> DATA_IS_DIFFERENT = new Predicate<Object>() {
|
||||
@Override
|
||||
public boolean test(Object o) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ": cannot generate enough sufficiently different values";
|
||||
}
|
||||
};
|
||||
|
||||
final CheckSession<T> session;
|
||||
long iterationSeed;
|
||||
final int sizeHint;
|
||||
final int iterationNumber;
|
||||
private Random random;
|
||||
|
||||
Iteration(CheckSession<T> session, long iterationSeed, int iterationNumber) {
|
||||
this.session = session;
|
||||
this.sizeHint = session.parameters.sizeHintFun.applyAsInt(iterationNumber);
|
||||
this.iterationNumber = iterationNumber;
|
||||
if (sizeHint < 0) {
|
||||
throw new IllegalArgumentException("Size hint should be non-negative, found " + sizeHint);
|
||||
}
|
||||
initSeed(iterationSeed);
|
||||
}
|
||||
|
||||
private void initSeed(long seed) {
|
||||
iterationSeed = seed;
|
||||
random = new Random(seed);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private CounterExampleImpl<T> findCounterExample() {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
if (i > 0) {
|
||||
initSeed(random.nextLong());
|
||||
}
|
||||
StructureNode node = new StructureNode(new NodeId(session.generator));
|
||||
T value;
|
||||
try {
|
||||
IntSource source = session.parameters.serializedData != null ? session.parameters.serializedData : d -> d.generateInt(random);
|
||||
value = session.generator.getGeneratorFunction().apply(new GenerativeDataStructure(source, node, sizeHint));
|
||||
}
|
||||
catch (CannotSatisfyCondition e) {
|
||||
continue;
|
||||
}
|
||||
catch (DataSerializer.EOFException e) {
|
||||
session.notifier.eofException();
|
||||
return null;
|
||||
}
|
||||
catch (WrongDataStructure e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
//noinspection InstanceofCatchParameter
|
||||
if (e instanceof CannotRestoreValue && session.parameters.serializedData != null) {
|
||||
throw e;
|
||||
}
|
||||
throw new GeneratorException(this, e);
|
||||
}
|
||||
if (!session.addGeneratedNode(node)) continue;
|
||||
|
||||
return CounterExampleImpl.checkProperty(this, value, node);
|
||||
}
|
||||
throw new GeneratorException(this, new CannotSatisfyCondition(DATA_IS_DIFFERENT));
|
||||
}
|
||||
|
||||
String printToReproduce(@Nullable Throwable failureReason, CounterExampleImpl<?> minimalCounterExample) {
|
||||
String data = minimalCounterExample.getSerializedData();
|
||||
boolean scenarios =
|
||||
failureReason != null && StatusNotifier.printStackTrace(failureReason).contains("PropertyChecker$Parameters.checkScenarios");
|
||||
String rechecking = "PropertyChecker.customized().rechecking(\"" + data + "\")\n ." +
|
||||
(scenarios ? "checkScenarios" : "forAll") + "(...)\n";
|
||||
return "To reproduce the minimal failing case, run\n " + rechecking +
|
||||
"To re-run the test with all intermediate minimization steps, use `recheckingIteration(" + iterationSeed + "L, " + sizeHint + ")` instead for last iteration, or `withSeed(" + session.parameters.globalSeed + "L)` for all iterations";
|
||||
}
|
||||
|
||||
String printSeeds() {
|
||||
return "iteration seed=" + iterationSeed + "L, " +
|
||||
"size hint=" + sizeHint + ", " +
|
||||
"global seed=" + session.parameters.globalSeed + "L";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Iteration<T> performIteration() {
|
||||
session.notifier.iterationStarted(iterationNumber);
|
||||
|
||||
CounterExampleImpl<T> example = findCounterExample();
|
||||
if (example != null) {
|
||||
session.notifier.counterExampleFound(this);
|
||||
throw new PropertyFalsified(new PropertyFailureImpl<>(example, this));
|
||||
}
|
||||
|
||||
if (iterationNumber >= session.parameters.iterationCount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Iteration<>(session, random.nextLong(), iterationNumber + 1);
|
||||
}
|
||||
|
||||
T generateValue(ReplayDataStructure data) {
|
||||
return session.generator.getGeneratorFunction().apply(data);
|
||||
}
|
||||
}
|
||||
|
||||
class CheckSession<T> {
|
||||
final Generator<T> generator;
|
||||
final Predicate<T> property;
|
||||
final PropertyChecker.Parameters parameters;
|
||||
final StatusNotifier notifier;
|
||||
private final Set<StructureNode> generatedNodes = Collections.newSetFromMap(new LinkedHashMap<StructureNode, Boolean>() {
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<StructureNode, Boolean> eldest) {
|
||||
return size() > 1_000;
|
||||
}
|
||||
});
|
||||
|
||||
CheckSession(Generator<T> generator, Predicate<T> property, PropertyChecker.Parameters parameters) {
|
||||
this.generator = generator;
|
||||
this.property = property;
|
||||
this.parameters = parameters;
|
||||
notifier = parameters.silent ? StatusNotifier.SILENT : new StatusNotifier(parameters.iterationCount);
|
||||
}
|
||||
|
||||
boolean addGeneratedNode(StructureNode node) {
|
||||
return generatedNodes.add(node);
|
||||
}
|
||||
|
||||
Iteration<T> firstIteration() {
|
||||
return new Iteration<>(this, parameters.globalSeed, 1);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
class NodeId {
|
||||
private final AtomicInteger counter;
|
||||
final int number;
|
||||
@Nullable final Integer generatorHash;
|
||||
|
||||
NodeId(@NotNull Generator<?> generator) {
|
||||
this(new AtomicInteger(), generator);
|
||||
}
|
||||
|
||||
private NodeId(AtomicInteger counter, @Nullable Generator<?> generator) {
|
||||
this.counter = counter;
|
||||
this.generatorHash = generator == null ? null : generator.getGeneratorFunction().hashCode();
|
||||
number = counter.getAndIncrement();
|
||||
}
|
||||
|
||||
NodeId childId(@Nullable Generator<?> generator) {
|
||||
return new NodeId(counter, generator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(number);
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.function.IntUnaryOperator;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* An entry point to property-based testing. The main usage pattern: {@code PropertyChecker.forAll(generator, property)}.
|
||||
*/
|
||||
public class PropertyChecker {
|
||||
static final int DEFAULT_MAX_SIZE_HINT = 100;
|
||||
|
||||
/**
|
||||
* Checks that the given property returns {@code true} and doesn't throw exceptions by running the generator and the property
|
||||
* on random data repeatedly for some number of times. To customize the settings, invoke {@link #customized()} first.
|
||||
*/
|
||||
public static <T> void forAll(Generator<T> generator, @NotNull Predicate<T> property) {
|
||||
customized().forAll(generator, property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a check that the scenarios generated by the given command are successful. Default {@link PropertyChecker} settings are used. To customize the settings, invoke {@link #customized()} first.
|
||||
* @param command a supplier for a top-level command. This supplier should not have any side effects.
|
||||
*/
|
||||
public static void checkScenarios(@NotNull Supplier<? extends ImperativeCommand> command) {
|
||||
customized().checkScenarios(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a "parameters" object that where some checker settings can be changed
|
||||
*/
|
||||
public static Parameters customized() {
|
||||
return new Parameters();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UseOfSystemOutOrSystemErr")
|
||||
public static class Parameters {
|
||||
long globalSeed = new Random().nextLong();
|
||||
@Nullable IntSource serializedData;
|
||||
IntUnaryOperator sizeHintFun = iteration -> (iteration - 1) % DEFAULT_MAX_SIZE_HINT + 1;
|
||||
int iterationCount = 100;
|
||||
boolean silent;
|
||||
|
||||
private Parameters() {
|
||||
}
|
||||
|
||||
/**
|
||||
* This function allows to start the test with a fixed random seed. It's useful to reproduce some previous test run and debug it.
|
||||
* @param seed A random seed to use for the first iteration.
|
||||
* The following iterations will use other, pseudo-random seeds, but still derived from this one.
|
||||
* @return this PropertyChecker
|
||||
* @deprecated To catch your attention. It's fine to call this method during test debugging, but it should not be committed to version control
|
||||
* and used in regression tests, because any changes in the test itself or the framework can render the passed argument obsolete.
|
||||
* For regression testing, it's recommended to code the failing scenario explicitly.
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("DeprecatedIsStillUsed")
|
||||
public Parameters withSeed(long seed) {
|
||||
if (serializedData != null) {
|
||||
System.err.println("withSeed ignored, because 'rechecking' is used");
|
||||
return this;
|
||||
}
|
||||
|
||||
globalSeed = seed;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterationCount the number of iterations to try. By default it's 100.
|
||||
* @return this PropertyChecker
|
||||
*/
|
||||
public Parameters withIterationCount(int iterationCount) {
|
||||
if (serializedData != null) {
|
||||
System.err.println("withIterationCount ignored, because 'rechecking' is used");
|
||||
return this;
|
||||
}
|
||||
this.iterationCount = iterationCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sizeHintFun a function determining how size hint should be distributed depending on the iteration number.
|
||||
* By default the size hint will be 1 in the first iteration, 2 in the second one, and so on until 100,
|
||||
* then again 1,...,100,1,...,100, etc.
|
||||
* @return this PropertyChecker
|
||||
* @see DataStructure#getSizeHint()
|
||||
*/
|
||||
public Parameters withSizeHint(@NotNull IntUnaryOperator sizeHintFun) {
|
||||
if (serializedData != null) {
|
||||
System.err.println("withSizeHint ignored, because 'rechecking' is used");
|
||||
return this;
|
||||
}
|
||||
|
||||
this.sizeHintFun = sizeHintFun;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppresses all output from the testing infrastructure during property check and shrinking
|
||||
* @return this PropertyChecker
|
||||
*/
|
||||
public Parameters silent() {
|
||||
this.silent = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the property within a single iteration by using specified seed and size hint. Useful to debug the test after it's failed, if {@link #rechecking} isn't enough (e.g. due to unforeseen side effects).
|
||||
* @deprecated To catch your attention. It's fine to call this method during test debugging, but it should not be committed to version control
|
||||
* and used in regression tests, because any changes in the test itself or the framework can render the passed arguments obsolete.
|
||||
* For regression testing, it's recommended to code the failing scenario explicitly.
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("DeprecatedIsStillUsed")
|
||||
public Parameters recheckingIteration(long seed, int sizeHint) {
|
||||
return withSeed(seed).withSizeHint(whatever -> sizeHint).withIterationCount(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the property within a single iteration by using specified underlying data. Useful to debug the test after it's failed.
|
||||
* @param serializedData the data used for running generators in serialized form, as printed by {@link PropertyFailure} exception.
|
||||
* @deprecated To catch your attention. It's fine to call this method during test debugging, but it should not be committed to version control
|
||||
* and used in regression tests, because any changes in the test itself or the framework can render the passed argument obsolete.
|
||||
* For regression testing, it's recommended to code the failing scenario explicitly.
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("DeprecatedIsStillUsed")
|
||||
public Parameters rechecking(@NotNull String serializedData) {
|
||||
this.iterationCount = 1;
|
||||
DataSerializer.deserializeInto(serializedData, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the given property returns {@code true} and doesn't throw exceptions by running the given generator and the property
|
||||
* on random data repeatedly for some number of times (see {@link #withIterationCount(int)}).
|
||||
*/
|
||||
public <T> void forAll(Generator<T> generator, Predicate<T> property) {
|
||||
Iteration<T> iteration = new CheckSession<>(serializedData == null ? generator : generator.noShrink(),
|
||||
property, this).firstIteration();
|
||||
while (iteration != null) {
|
||||
iteration = iteration.performIteration();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a check that the scenarios generated by the given command are successful. Default {@link PropertyChecker} settings are used.
|
||||
* @param command a supplier for a top-level command. This supplier should not have any side effects.
|
||||
*/
|
||||
public void checkScenarios(@NotNull Supplier<? extends ImperativeCommand> command) {
|
||||
forAll(Scenario.scenarios(command), Scenario::ensureSuccessful);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
public interface PropertyFailure<T> {
|
||||
@NotNull
|
||||
CounterExample<T> getFirstCounterExample();
|
||||
|
||||
@NotNull
|
||||
CounterExample<T> getMinimalCounterexample();
|
||||
|
||||
@Nullable
|
||||
Throwable getStoppingReason();
|
||||
|
||||
int getTotalMinimizationExampleCount();
|
||||
|
||||
int getMinimizationStageCount();
|
||||
|
||||
int getIterationNumber();
|
||||
|
||||
long getIterationSeed();
|
||||
|
||||
long getGlobalSeed();
|
||||
|
||||
int getSizeHint();
|
||||
|
||||
interface CounterExample<T> {
|
||||
/**
|
||||
* @return the value produced by the generator, on which the property check has failed
|
||||
*/
|
||||
T getExampleValue();
|
||||
|
||||
/**
|
||||
* @return the exception, if property check has failed with one
|
||||
*/
|
||||
@Nullable
|
||||
Throwable getExceptionCause();
|
||||
|
||||
/**
|
||||
* Re-run the generator and the property on the {@link DataStructure} corresponding to this counter-example.<p/>
|
||||
*
|
||||
* This can be useful for debugging, when this example fails after some previous runs and shrinking, but doesn't fail
|
||||
* by itself. This can indicate unnoticed side effects in the generators and properties. Catching {@link PropertyFalsified}
|
||||
* exception, calling {@code replay} and putting some breakpoints might shed some light on the reasons involved.
|
||||
* @return a CounterExample with the results from the re-run. If re-run is successful (which also indicates some unaccounted side effects), a CounterExample is returned with an exception cause indicating that fact.
|
||||
*/
|
||||
@NotNull
|
||||
CounterExample<T> replay();
|
||||
|
||||
/**
|
||||
* @return the data used for generator to produce this counterexample, serialized into Base64.
|
||||
* To be used with {@link PropertyChecker#rechecking} or {@link ImperativeCommand#checkScenario}
|
||||
*/
|
||||
@NotNull
|
||||
String getSerializedData();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
class PropertyFailureImpl<T> implements PropertyFailure<T> {
|
||||
private final CounterExampleImpl<T> initial;
|
||||
private CounterExampleImpl<T> minimized;
|
||||
private int totalSteps;
|
||||
private int successfulSteps;
|
||||
final Iteration<T> iteration;
|
||||
private Throwable stoppingReason;
|
||||
|
||||
PropertyFailureImpl(@NotNull CounterExampleImpl<T> initial, Iteration<T> iteration) {
|
||||
this.initial = initial;
|
||||
this.minimized = initial;
|
||||
this.iteration = iteration;
|
||||
try {
|
||||
shrink();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
stoppingReason = e;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CounterExampleImpl<T> getFirstCounterExample() {
|
||||
return initial;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CounterExampleImpl<T> getMinimalCounterexample() {
|
||||
return minimized;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Throwable getStoppingReason() {
|
||||
return stoppingReason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalMinimizationExampleCount() {
|
||||
return totalSteps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinimizationStageCount() {
|
||||
return successfulSteps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIterationNumber() {
|
||||
return iteration.iterationNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getIterationSeed() {
|
||||
return iteration.iterationSeed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getGlobalSeed() {
|
||||
return iteration.session.parameters.globalSeed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSizeHint() {
|
||||
return iteration.sizeHint;
|
||||
}
|
||||
|
||||
private void shrink() {
|
||||
ShrinkStep lastSuccessfulShrink = null;
|
||||
do {
|
||||
lastSuccessfulShrink = shrinkIteration(lastSuccessfulShrink);
|
||||
}
|
||||
while (lastSuccessfulShrink != null);
|
||||
}
|
||||
|
||||
private ShrinkStep shrinkIteration(ShrinkStep limit) {
|
||||
ShrinkStep lastSuccessfulShrink = null;
|
||||
ShrinkStep step = minimized.data.shrink();
|
||||
while (step != null) {
|
||||
step = findSuccessfulShrink(step, limit);
|
||||
if (step != null) {
|
||||
lastSuccessfulShrink = step;
|
||||
step = step.onSuccess(minimized.data);
|
||||
}
|
||||
}
|
||||
return lastSuccessfulShrink;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ShrinkStep findSuccessfulShrink(ShrinkStep step, @Nullable ShrinkStep limit) {
|
||||
List<CustomizedNode> combinatorial = new ArrayList<>();
|
||||
|
||||
while (step != null && !step.equals(limit)) {
|
||||
StructureNode node = step.apply(minimized.data);
|
||||
if (node != null && iteration.session.addGeneratedNode(node)) {
|
||||
CombinatorialIntCustomizer customizer = new CombinatorialIntCustomizer();
|
||||
if (tryStep(node, customizer)) {
|
||||
return step;
|
||||
}
|
||||
CombinatorialIntCustomizer next = customizer.nextAttempt();
|
||||
if (next != null) {
|
||||
combinatorial.add(new CustomizedNode(next, step));
|
||||
}
|
||||
}
|
||||
|
||||
step = step.onFailure();
|
||||
}
|
||||
return processDelayedCombinations(combinatorial);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ShrinkStep processDelayedCombinations(List<CustomizedNode> delayed) {
|
||||
Collections.sort(delayed);
|
||||
|
||||
for (CustomizedNode customizedNode : delayed) {
|
||||
CombinatorialIntCustomizer customizer = customizedNode.customizer;
|
||||
while (customizer != null) {
|
||||
if (tryStep(customizedNode.step.apply(minimized.data), customizer)) {
|
||||
return customizedNode.step;
|
||||
}
|
||||
customizer = customizer.nextAttempt();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean tryStep(StructureNode node, CombinatorialIntCustomizer customizer) {
|
||||
try {
|
||||
iteration.session.notifier.shrinkAttempt(this, iteration);
|
||||
|
||||
totalSteps++;
|
||||
T value = iteration.generateValue(new ReplayDataStructure(node, iteration.sizeHint, customizer));
|
||||
CounterExampleImpl<T> example = CounterExampleImpl.checkProperty(iteration, value, customizer.writeChanges(node));
|
||||
if (example != null) {
|
||||
minimized = example;
|
||||
successfulSteps++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (CannotRestoreValue ignored) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class CustomizedNode implements Comparable<CustomizedNode> {
|
||||
final CombinatorialIntCustomizer customizer;
|
||||
final ShrinkStep step;
|
||||
|
||||
CustomizedNode(CombinatorialIntCustomizer customizer, ShrinkStep step) {
|
||||
this.customizer = customizer;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull CustomizedNode o) {
|
||||
return Integer.compare(customizer.countVariants(), o.customizer.countVariants());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
@SuppressWarnings("ExceptionClassNameDoesntEndWithException")
|
||||
public class PropertyFalsified extends RuntimeException {
|
||||
static final String FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION = "Failure reason has changed during minimization, see initial failing example below";
|
||||
private static final String SEPARATOR = "\n==========================\n";
|
||||
private final PropertyFailureImpl<?> failure;
|
||||
private final String message;
|
||||
|
||||
PropertyFalsified(PropertyFailureImpl<?> failure) {
|
||||
super(failure.getMinimalCounterexample().getExceptionCause());
|
||||
this.failure = failure;
|
||||
this.message = calcMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
private String calcMessage() {
|
||||
StringBuilder traceBuilder = new StringBuilder();
|
||||
|
||||
String exampleString = valueToString(failure.getMinimalCounterexample(), traceBuilder);
|
||||
|
||||
Throwable failureReason = failure.getMinimalCounterexample().getExceptionCause();
|
||||
Throwable rootCause = failureReason == null ? null : getRootCause(failureReason);
|
||||
String msg = rootCause != null && !rootCause.toString().contains("ComparisonFailure") // otherwise IDEA replaces the whole message (including example and rechecking information) with a diff
|
||||
? "Failed with " + rootCause + "\nOn " + exampleString
|
||||
: "Falsified on " + exampleString;
|
||||
|
||||
msg += "\n" +
|
||||
getMinimizationStats() +
|
||||
"\n" + failure.iteration.printToReproduce(failureReason, failure.getMinimalCounterexample()) + "\n";
|
||||
|
||||
if (failureReason != null) {
|
||||
appendTrace(traceBuilder,
|
||||
rootCause == failureReason ? "Property failure reason: " : "Property failure reason, innermost exception (see full trace below): ",
|
||||
rootCause);
|
||||
}
|
||||
|
||||
if (failure.getStoppingReason() != null) {
|
||||
msg += "\n Minimization stopped prematurely, see the reason below.";
|
||||
appendTrace(traceBuilder, "An unexpected exception happened during minimization: ", failure.getStoppingReason());
|
||||
}
|
||||
|
||||
Throwable first = failure.getFirstCounterExample().getExceptionCause();
|
||||
if (exceptionsDiffer(first, failure.getMinimalCounterexample().getExceptionCause())) {
|
||||
msg += "\n " + FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION;
|
||||
StringBuilder secondaryTrace = new StringBuilder();
|
||||
traceBuilder.append("\n Initial value: ").append(valueToString(failure.getFirstCounterExample(), secondaryTrace));
|
||||
if (first == null) {
|
||||
traceBuilder.append("\n Initially property was falsified without exceptions");
|
||||
traceBuilder.append(secondaryTrace);
|
||||
} else {
|
||||
traceBuilder.append(secondaryTrace);
|
||||
appendTrace(traceBuilder, "Initially failed because of ", first);
|
||||
}
|
||||
}
|
||||
return msg + traceBuilder;
|
||||
}
|
||||
|
||||
private static Throwable getRootCause(Throwable t) {
|
||||
while (t.getCause() != null) {
|
||||
t = t.getCause();
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
private static void appendTrace(StringBuilder traceBuilder, String prefix, Throwable e) {
|
||||
traceBuilder.append("\n ").append(prefix).append(StatusNotifier.printStackTrace(e)).append(SEPARATOR);
|
||||
}
|
||||
|
||||
private static String valueToString(CounterExampleImpl<?> example, StringBuilder traceBuilder) {
|
||||
try {
|
||||
return String.valueOf(example.getExampleValue());
|
||||
}
|
||||
catch (Throwable e) {
|
||||
appendTrace(traceBuilder, "Exception during toString evaluation: ", e);
|
||||
return "<can't evaluate toString(), see exception below>";
|
||||
}
|
||||
}
|
||||
|
||||
private String getMinimizationStats() {
|
||||
int exampleCount = failure.getTotalMinimizationExampleCount();
|
||||
if (exampleCount == 0) return "";
|
||||
String examples = exampleCount == 1 ? "example" : "examples";
|
||||
|
||||
int stageCount = failure.getMinimizationStageCount();
|
||||
if (stageCount == 0) return "Couldn't minimize, tried " + exampleCount + " " + examples + "\n";
|
||||
|
||||
String stages = stageCount == 1 ? "stage" : "stages";
|
||||
return "Minimized in " + stageCount + " " + stages + ", by trying " + exampleCount + " " + examples + "\n";
|
||||
}
|
||||
|
||||
private static boolean exceptionsDiffer(Throwable e1, Throwable e2) {
|
||||
if (e1 == null && e2 == null) return false;
|
||||
if ((e1 == null) != (e2 == null)) return true;
|
||||
if (!e1.getClass().equals(e2.getClass())) return true;
|
||||
if (e1 instanceof StackOverflowError) return false;
|
||||
|
||||
return !getUserTrace(e1).equals(getUserTrace(e2));
|
||||
}
|
||||
|
||||
private static List<String> getUserTrace(Throwable e) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (StackTraceElement element : e.getStackTrace()) {
|
||||
String s = element.toString();
|
||||
if (s.startsWith("org.jetbrains.jetCheck.") && !s.contains("Test.")) {
|
||||
break;
|
||||
}
|
||||
result.add(s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public PropertyFailure<?> getFailure() {
|
||||
return failure;
|
||||
}
|
||||
|
||||
public Object getBreakingValue() {
|
||||
return failure.getMinimalCounterexample().getExampleValue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
*/
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
class RemoveListRange extends ShrinkStep {
|
||||
private final StructureNode node;
|
||||
private final int lastSuccessfulRemove;
|
||||
private final int start;
|
||||
private final int length;
|
||||
|
||||
static RemoveListRange fromEnd(StructureNode node) {
|
||||
int likelyFailingSuffix = node.isIncompleteList() && node.children.size() > 2 ? 1 : 0;
|
||||
return new RemoveListRange(node,
|
||||
node.children.size() - likelyFailingSuffix,
|
||||
node.children.size() - likelyFailingSuffix - 1, 1);
|
||||
}
|
||||
|
||||
private RemoveListRange(StructureNode node, int lastSuccessfulRemove, int start, int length) {
|
||||
this.node = node;
|
||||
this.lastSuccessfulRemove = lastSuccessfulRemove;
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
assert start > 0;
|
||||
assert start + length <= node.children.size();
|
||||
assert lastSuccessfulRemove > 0;
|
||||
assert lastSuccessfulRemove <= node.children.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
List<?> getEqualityObjects() {
|
||||
return Arrays.asList(node.id, start, length);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
StructureNode apply(StructureNode root) {
|
||||
int newSize = node.children.size() - length - 1;
|
||||
IntDistribution lengthDistribution = ((IntData)node.children.get(0)).distribution;
|
||||
if (!lengthDistribution.isValidValue(newSize)) return null;
|
||||
|
||||
List<StructureElement> lessItems = new ArrayList<>(newSize + 1);
|
||||
lessItems.add(node.isIncompleteList() ? node.children.get(0) : new IntData(node.children.get(0).id, newSize, lengthDistribution));
|
||||
lessItems.addAll(node.children.subList(1, start));
|
||||
lessItems.addAll(node.children.subList(start + length, node.children.size()));
|
||||
StructureNode replacement = new StructureNode(node.id, lessItems);
|
||||
replacement.kind = StructureKind.LIST;
|
||||
return root.replace(node.id, replacement);
|
||||
}
|
||||
|
||||
@Override
|
||||
ShrinkStep onFailure() {
|
||||
if (length > 1) {
|
||||
int end = start + length;
|
||||
return new RemoveListRange(node, lastSuccessfulRemove, end - (length / 2), length / 2);
|
||||
}
|
||||
|
||||
int newEnd = start == 1 ? node.children.size() : start;
|
||||
if (newEnd == lastSuccessfulRemove) return node.shrinkChild(node.children.size() - 1);
|
||||
return new RemoveListRange(node, lastSuccessfulRemove, newEnd - 1, 1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
ShrinkStep onSuccess(StructureNode smallerRoot) {
|
||||
if (length == node.children.size() - 1) return null;
|
||||
|
||||
StructureNode inheritor = (StructureNode)Objects.requireNonNull(smallerRoot.findChildById(node.id));
|
||||
if (start == 1) return fromEnd(inheritor);
|
||||
|
||||
int newLength = Math.min(length * 2, start - 1);
|
||||
return new RemoveListRange(inheritor, start, start - newLength, newLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RemoveListRange{" +
|
||||
"last=" + lastSuccessfulRemove +
|
||||
", start=" + start +
|
||||
", length=" + length +
|
||||
", node=" + node.id + ": " + node +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
class ReplayDataStructure extends AbstractDataStructure {
|
||||
private final Iterator<StructureElement> iterator;
|
||||
private final IntCustomizer customizer;
|
||||
|
||||
ReplayDataStructure(StructureNode node, int sizeHint, IntCustomizer customizer) {
|
||||
super(node, sizeHint);
|
||||
this.iterator = node.childrenIterator();
|
||||
this.customizer = customizer;
|
||||
}
|
||||
|
||||
@Override
|
||||
int drawInt(@NotNull IntDistribution distribution) {
|
||||
return customizer.suggestInt(nextChild(IntData.class), distribution);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private <E extends StructureElement> E nextChild(Class<E> required) {
|
||||
if (!iterator.hasNext()) throw new CannotRestoreValue();
|
||||
Object next = iterator.next();
|
||||
if (!required.isInstance(next)) throw new CannotRestoreValue();
|
||||
//noinspection unchecked
|
||||
return (E)next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T generate(@NotNull Generator<T> generator) {
|
||||
return generator.getGeneratorFunction().apply(new ReplayDataStructure(nextChild(StructureNode.class), childSizeHint(), customizer));
|
||||
}
|
||||
|
||||
@Override
|
||||
<T> T generateNonShrinkable(@NotNull Generator<T> generator) {
|
||||
return generate(generator);
|
||||
}
|
||||
|
||||
@Override
|
||||
<T> T generateConditional(@NotNull Generator<T> generator, @NotNull Predicate<? super T> condition) {
|
||||
T value = generate(generator);
|
||||
if (!condition.test(value)) throw new CannotRestoreValue();
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
void changeKind(StructureKind kind) {
|
||||
if (node.kind != kind) {
|
||||
throw new CannotRestoreValue();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return node.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
*/
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
class Scenario {
|
||||
private final List<Object> log = new ArrayList<>();
|
||||
private Throwable failure;
|
||||
|
||||
Scenario(@NotNull ImperativeCommand cmd, @NotNull DataStructure data) {
|
||||
try {
|
||||
performCommand(cmd, data, log);
|
||||
}
|
||||
catch (DataSerializer.EOFException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
addFailure(e);
|
||||
}
|
||||
if (failure instanceof CannotRestoreValue) {
|
||||
throw (CannotRestoreValue)failure;
|
||||
}
|
||||
}
|
||||
|
||||
private void addFailure(Throwable e) {
|
||||
if (failure == null) {
|
||||
failure = e;
|
||||
}
|
||||
}
|
||||
|
||||
private void performCommand(ImperativeCommand command, DataStructure data, List<Object> log) {
|
||||
command.performCommand(new ImperativeCommand.Environment() {
|
||||
@Override
|
||||
public void logMessage(@NotNull String message) {
|
||||
log.add(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T generateValue(@NotNull Generator<T> generator, @Nullable String logMessage) {
|
||||
T value = safeGenerate(data, generator);
|
||||
if (logMessage != null) {
|
||||
logMessage(String.format(logMessage, value));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeCommands(IntDistribution count, Generator<? extends ImperativeCommand> cmdGen) {
|
||||
innerCommandLists(Generator.listsOf(count, innerCommands(cmdGen)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeCommands(Generator<? extends ImperativeCommand> cmdGen) {
|
||||
innerCommandLists(Generator.nonEmptyLists(innerCommands(cmdGen)));
|
||||
}
|
||||
|
||||
private void innerCommandLists(final Generator<List<Object>> listGen) {
|
||||
data.generate(Generator.from(new EquivalentGenerator<List<Object>>() {
|
||||
@Override
|
||||
public List<Object> apply(DataStructure data) {
|
||||
return listGen.getGeneratorFunction().apply(data);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Generator<Object> innerCommands(Generator<? extends ImperativeCommand> cmdGen) {
|
||||
return Generator.from(new EquivalentGenerator<Object>() {
|
||||
@Override
|
||||
public Object apply(DataStructure cmdData) {
|
||||
List<Object> localLog = new ArrayList<>();
|
||||
log.add(localLog);
|
||||
performCommand(safeGenerate(cmdData, cmdGen), cmdData, localLog);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T> T safeGenerate(DataStructure data, Generator<T> generator) {
|
||||
try {
|
||||
return data.generate(generator);
|
||||
}
|
||||
catch (CannotRestoreValue e) { //todo test for evil intermediate code hiding this exception, also CannotSatisfyCondition
|
||||
addFailure(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o instanceof Scenario && log.equals(((Scenario)o).log);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return log.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
printLog(sb, "", log);
|
||||
return "commands:" + (sb.length() == 0 ? "<none>" : sb.toString());
|
||||
}
|
||||
|
||||
private static void printLog(StringBuilder sb, String indent, List<Object> log) {
|
||||
for (Object o : log) {
|
||||
if (o instanceof String) {
|
||||
sb.append("\n").append(indent).append(o);
|
||||
} else {
|
||||
//noinspection unchecked
|
||||
printLog(sb, indent + " ", (List)o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean ensureSuccessful() {
|
||||
if (failure instanceof Error) throw (Error)failure;
|
||||
if (failure instanceof RuntimeException) throw (RuntimeException)failure;
|
||||
if (failure != null) throw new RuntimeException(failure);
|
||||
return true;
|
||||
}
|
||||
|
||||
static Generator<Scenario> scenarios(@NotNull Supplier<? extends ImperativeCommand> command) {
|
||||
return Generator.from(data -> new Scenario(command.get(), data));
|
||||
}
|
||||
|
||||
private static abstract class EquivalentGenerator<T> implements Function<DataStructure, T> {
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return getClass() == obj.getClass(); // for recursive shrinking to work
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getClass().hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
*/
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
abstract class ShrinkStep {
|
||||
|
||||
@Nullable
|
||||
abstract StructureNode apply(StructureNode root);
|
||||
|
||||
@Nullable
|
||||
abstract ShrinkStep onSuccess(StructureNode smallerRoot);
|
||||
|
||||
@Nullable
|
||||
abstract ShrinkStep onFailure();
|
||||
|
||||
abstract List<?> getEqualityObjects();
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj != null && obj.getClass().equals(getClass()) && getEqualityObjects().equals(((ShrinkStep)obj).getEqualityObjects());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getEqualityObjects().hashCode();
|
||||
}
|
||||
|
||||
static ShrinkStep create(@NotNull NodeId replaced,
|
||||
@NotNull StructureElement replacement,
|
||||
@Nullable Function<StructureNode, ShrinkStep> onSuccess,
|
||||
@Nullable Supplier<ShrinkStep> onFailure) {
|
||||
return new ShrinkStep() {
|
||||
|
||||
@Override
|
||||
StructureNode apply(StructureNode root) {
|
||||
return root.replace(replaced, replacement);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
ShrinkStep onSuccess(StructureNode smallerRoot) {
|
||||
return onSuccess == null ? null : onSuccess.apply(smallerRoot);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
ShrinkStep onFailure() {
|
||||
return onFailure == null ? null : onFailure.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "replace " + replaced + " with " + replacement;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<?> getEqualityObjects() {
|
||||
return Arrays.asList(replaced, replacement);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
@SuppressWarnings("UseOfSystemOutOrSystemErr")
|
||||
class StatusNotifier {
|
||||
static final StatusNotifier SILENT = new StatusNotifier(0) {
|
||||
@Override
|
||||
boolean shouldPrint() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void counterExampleFound(Iteration<?> iteration) {}
|
||||
|
||||
@Override
|
||||
void eofException() {}
|
||||
};
|
||||
|
||||
private final int iterationCount;
|
||||
private int currentIteration;
|
||||
private long lastPrinted = System.currentTimeMillis();
|
||||
|
||||
StatusNotifier(int iterationCount) {
|
||||
this.iterationCount = iterationCount;
|
||||
}
|
||||
|
||||
void iterationStarted(int iteration) {
|
||||
currentIteration = iteration;
|
||||
if (shouldPrint()) {
|
||||
System.out.println(formatCurrentTime() + ": iteration " + currentIteration + " of " + iterationCount + "...");
|
||||
}
|
||||
}
|
||||
|
||||
void counterExampleFound(Iteration<?> iteration) {
|
||||
lastPrinted = System.currentTimeMillis();
|
||||
System.err.println(formatCurrentTime() + ": failed on iteration " + currentIteration + " (" + iteration.printSeeds() + "), shrinking...");
|
||||
}
|
||||
|
||||
boolean shouldPrint() {
|
||||
if (System.currentTimeMillis() - lastPrinted > 5_000) {
|
||||
lastPrinted = System.currentTimeMillis();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private int lastReportedStage = -1;
|
||||
private String lastReportedTrace = null;
|
||||
<T> void shrinkAttempt(PropertyFailure<T> failure, Iteration<T> iteration) {
|
||||
if (shouldPrint()) {
|
||||
int stage = failure.getMinimizationStageCount();
|
||||
System.out.println(formatCurrentTime() + ": still shrinking (" + iteration.printSeeds() + "). " +
|
||||
"Examples tried: " + failure.getTotalMinimizationExampleCount() +
|
||||
", successful minimizations: " + stage);
|
||||
if (lastReportedStage != stage) {
|
||||
lastReportedStage = stage;
|
||||
|
||||
System.err.println(" Current minimal example: " + failure.getMinimalCounterexample().getExampleValue());
|
||||
|
||||
Throwable exceptionCause = failure.getMinimalCounterexample().getExceptionCause();
|
||||
if (exceptionCause != null) {
|
||||
String trace = shortenStackTrace(exceptionCause);
|
||||
if (!trace.equals(lastReportedTrace)) {
|
||||
lastReportedTrace = trace;
|
||||
System.err.println(" Reason: " + trace);
|
||||
}
|
||||
}
|
||||
System.err.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void eofException() {
|
||||
System.out.println("Generator tried to read past the end of serialized data, so it seems the failure isn't reproducible anymore");
|
||||
}
|
||||
|
||||
private static String shortenStackTrace(Throwable e) {
|
||||
String trace = printStackTrace(e);
|
||||
return trace.length() > 1000 ? trace.substring(0, 1000) + "..." : trace;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String formatCurrentTime() {
|
||||
return LocalTime.now().format(DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault()));
|
||||
}
|
||||
|
||||
static String printStackTrace(Throwable e) {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
PrintWriter writer = new PrintWriter(stringWriter);
|
||||
e.printStackTrace(writer);
|
||||
return stringWriter.getBuffer().toString();
|
||||
}
|
||||
}
|
||||
@@ -1,306 +0,0 @@
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
abstract class StructureElement {
|
||||
final NodeId id;
|
||||
|
||||
StructureElement(@NotNull NodeId id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
abstract ShrinkStep shrink();
|
||||
|
||||
@NotNull
|
||||
abstract StructureElement replace(NodeId id, StructureElement replacement);
|
||||
|
||||
@Nullable
|
||||
abstract StructureElement findChildById(NodeId id);
|
||||
|
||||
abstract void serialize(DataOutputStream out) throws IOException;
|
||||
}
|
||||
|
||||
class StructureNode extends StructureElement {
|
||||
final List<StructureElement> children;
|
||||
@NotNull StructureKind kind = StructureKind.GENERIC;
|
||||
boolean shrinkProhibited;
|
||||
|
||||
StructureNode(NodeId id) {
|
||||
this(id, new ArrayList<>());
|
||||
}
|
||||
|
||||
StructureNode(NodeId id, List<StructureElement> children) {
|
||||
super(id);
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
Iterator<StructureElement> childrenIterator() {
|
||||
return children.iterator();
|
||||
}
|
||||
|
||||
void addChild(StructureElement child) {
|
||||
children.add(child);
|
||||
}
|
||||
|
||||
StructureNode subStructure(@NotNull Generator<?> generator) {
|
||||
StructureNode e = new StructureNode(id.childId(generator));
|
||||
addChild(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
void removeLastChild(StructureNode node) {
|
||||
if (children.isEmpty() || children.get(children.size() - 1) != node) {
|
||||
throw new IllegalStateException("Last sub-structure changed");
|
||||
}
|
||||
children.remove(children.size() - 1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
ShrinkStep shrink() {
|
||||
if (shrinkProhibited) return null;
|
||||
|
||||
return kind == StructureKind.LIST && children.size() > 1 ? RemoveListRange.fromEnd(this) : shrinkChild(children.size() - 1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
ShrinkStep shrinkChild(int index) {
|
||||
int minIndex = kind == StructureKind.GENERIC ? 0 : 1;
|
||||
for (; index >= minIndex; index--) {
|
||||
ShrinkStep childShrink = children.get(index).shrink();
|
||||
if (childShrink != null) return wrapChildShrink(index, childShrink);
|
||||
}
|
||||
|
||||
return shrinkRecursion();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ShrinkStep wrapChildShrink(int index, @Nullable ShrinkStep step) {
|
||||
if (step == null) return shrinkChild(index - 1);
|
||||
|
||||
NodeId oldChild = children.get(index).id;
|
||||
|
||||
return new ShrinkStep() {
|
||||
|
||||
@Override
|
||||
List<?> getEqualityObjects() {
|
||||
return Collections.singletonList(step);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
StructureNode apply(StructureNode root) {
|
||||
return step.apply(root);
|
||||
}
|
||||
|
||||
@Override
|
||||
ShrinkStep onSuccess(StructureNode smallerRoot) {
|
||||
StructureNode inheritor = (StructureNode)Objects.requireNonNull(smallerRoot.findChildById(id));
|
||||
assert inheritor.children.size() == children.size();
|
||||
if (inheritor.children.get(index).id != oldChild) {
|
||||
return inheritor.shrink();
|
||||
}
|
||||
|
||||
return inheritor.wrapChildShrink(index, step.onSuccess(smallerRoot));
|
||||
}
|
||||
|
||||
@Override
|
||||
ShrinkStep onFailure() {
|
||||
return wrapChildShrink(index, step.onFailure());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "-" + step.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
boolean isIncompleteList() {
|
||||
return ((IntData)children.get(0)).value > children.size() - 1;
|
||||
}
|
||||
|
||||
private void findChildrenWithGenerator(int generatorHash, List<StructureNode> result) {
|
||||
for (StructureElement child : children) {
|
||||
if (child instanceof StructureNode) {
|
||||
Integer childGen = child.id.generatorHash;
|
||||
if (childGen != null && generatorHash == childGen.intValue()) {
|
||||
result.add((StructureNode)child);
|
||||
} else {
|
||||
((StructureNode)child).findChildrenWithGenerator(generatorHash, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ShrinkStep shrinkRecursion() {
|
||||
if (id.generatorHash != null) {
|
||||
List<StructureNode> sameGeneratorChildren = new ArrayList<>();
|
||||
findChildrenWithGenerator(id.generatorHash, sameGeneratorChildren);
|
||||
return tryReplacing(sameGeneratorChildren, 0);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ShrinkStep tryReplacing(List<StructureNode> candidates, int index) {
|
||||
if (index < candidates.size()) {
|
||||
StructureNode replacement = candidates.get(index);
|
||||
return ShrinkStep.create(id, replacement, __ -> replacement.shrink(), () -> tryReplacing(candidates, index + 1));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
StructureNode replace(NodeId id, StructureElement replacement) {
|
||||
if (id == this.id) {
|
||||
return (StructureNode)replacement;
|
||||
}
|
||||
|
||||
if (children.isEmpty()) return this;
|
||||
|
||||
int index = indexOfChildContaining(id);
|
||||
StructureElement oldChild = children.get(index);
|
||||
StructureElement newChild = oldChild.replace(id, replacement);
|
||||
if (oldChild == newChild) return this;
|
||||
|
||||
List<StructureElement> newChildren = new ArrayList<>(this.children);
|
||||
newChildren.set(index, newChild);
|
||||
StructureNode copy = new StructureNode(this.id, newChildren);
|
||||
copy.shrinkProhibited = this.shrinkProhibited;
|
||||
copy.kind = this.kind;
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
StructureElement findChildById(NodeId id) {
|
||||
if (id == this.id) return this;
|
||||
int index = indexOfChildContaining(id);
|
||||
return index < 0 ? null : children.get(index).findChildById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
void serialize(DataOutputStream out) throws IOException {
|
||||
for (StructureElement child : children) {
|
||||
child.serialize(out);
|
||||
}
|
||||
}
|
||||
|
||||
private int indexOfChildContaining(NodeId id) {
|
||||
int i = 0;
|
||||
while (i < children.size() && children.get(i).id.number <= id.number) i++;
|
||||
return i - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof StructureNode && children.equals(((StructureNode)obj).children);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return children.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String inner = children.stream().map(Object::toString).collect(Collectors.joining(", "));
|
||||
switch (kind) {
|
||||
case LIST: return "[" + inner + "]";
|
||||
case CHOICE: return "?(" + inner + ")";
|
||||
default: return "(" + inner + ")";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class IntData extends StructureElement {
|
||||
final int value;
|
||||
final IntDistribution distribution;
|
||||
|
||||
IntData(NodeId id, int value, IntDistribution distribution) {
|
||||
super(id);
|
||||
this.value = value;
|
||||
this.distribution = distribution;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
ShrinkStep shrink() {
|
||||
if (value == 0) return null;
|
||||
|
||||
int minValue = 0;
|
||||
if (distribution instanceof BoundedIntDistribution) {
|
||||
minValue = Math.max(minValue, ((BoundedIntDistribution)distribution).getMin());
|
||||
}
|
||||
return tryInt(minValue, () -> null, this::tryNegation);
|
||||
}
|
||||
|
||||
private ShrinkStep tryNegation() {
|
||||
if (value < 0) {
|
||||
return tryInt(-value, () -> divisionLoop(-value), () -> divisionLoop(value));
|
||||
}
|
||||
return divisionLoop(value);
|
||||
}
|
||||
|
||||
private ShrinkStep divisionLoop(int value) {
|
||||
if (value == 0) return null;
|
||||
int divided = value / 2;
|
||||
return tryInt(divided, () -> divisionLoop(divided), null);
|
||||
}
|
||||
|
||||
private ShrinkStep tryInt(int value, @NotNull Supplier<ShrinkStep> success, @Nullable Supplier<ShrinkStep> fail) {
|
||||
return distribution.isValidValue(value) ? ShrinkStep.create(id, new IntData(id, value, distribution), __ -> success.get(), fail) : null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
IntData replace(NodeId id, StructureElement replacement) {
|
||||
return this.id == id ? (IntData)replacement : this;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
StructureElement findChildById(NodeId id) {
|
||||
return id == this.id ? this : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
void serialize(DataOutputStream out) throws IOException {
|
||||
DataSerializer.writeINT(out, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof IntData && value == ((IntData)obj).value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
enum StructureKind {
|
||||
GENERIC, LIST, CHOICE
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
*/
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import static org.jetbrains.jetCheck.Generator.*;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
public class ExceptionTest extends PropertyCheckerTestCase {
|
||||
|
||||
public void testFailureReasonUnchanged() {
|
||||
PropertyFalsified e = checkFails(STABLE, integers(), i -> {
|
||||
throw new AssertionError("fail");
|
||||
});
|
||||
|
||||
assertFalse(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION));
|
||||
}
|
||||
|
||||
public void testFailureReasonChangedExceptionClass() {
|
||||
PropertyFalsified e = checkFails(STABLE, integers(), i -> {
|
||||
throw (i == 0 ? new RuntimeException("fail") : new IllegalArgumentException("fail"));
|
||||
});
|
||||
assertTrue(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION));
|
||||
}
|
||||
|
||||
public void testFailureReasonChangedExceptionTrace() {
|
||||
PropertyFalsified e = checkFails(STABLE, integers(), i -> {
|
||||
if (i == 0) {
|
||||
throw new AssertionError("fail");
|
||||
}
|
||||
else {
|
||||
throw new AssertionError("fail2");
|
||||
}
|
||||
});
|
||||
assertTrue(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION));
|
||||
}
|
||||
|
||||
public void testExceptionWhileGeneratingValue() {
|
||||
try {
|
||||
STABLE.forAll(from(data -> {
|
||||
throw new AssertionError("fail");
|
||||
}), i -> true);
|
||||
fail();
|
||||
}
|
||||
catch (GeneratorException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
public void testExceptionWhileShrinkingValue() {
|
||||
PropertyFalsified e = checkFails(PropertyChecker.customized(), listsOf(integers()).suchThat(l -> {
|
||||
if (l.size() == 1 && l.get(0) == 0) throw new RuntimeException("my exception");
|
||||
return true;
|
||||
}), l -> l.stream().allMatch(i -> i > 0));
|
||||
|
||||
assertEquals("my exception", e.getFailure().getStoppingReason().getMessage());
|
||||
assertTrue(StatusNotifier.printStackTrace(e).contains("my exception"));
|
||||
}
|
||||
|
||||
public void testUnsatisfiableSuchThat() {
|
||||
try {
|
||||
PropertyChecker.forAll(integers(-1, 1).suchThat(i -> i > 2), i -> i == 0);
|
||||
fail();
|
||||
}
|
||||
catch (GeneratorException e) {
|
||||
assertTrue(e.getCause() instanceof CannotSatisfyCondition);
|
||||
}
|
||||
}
|
||||
|
||||
public void testUsingWrongDataStructure() {
|
||||
Generator<Integer> gen = from(data1 -> {
|
||||
int i1 = data1.generate(naturals());
|
||||
int i2 = data1.generate(from(data2 -> data1.generate(integers())));
|
||||
return i1 + i2;
|
||||
});
|
||||
try {
|
||||
PropertyChecker.forAll(gen, i -> true);
|
||||
fail();
|
||||
}
|
||||
catch (WrongDataStructure expected) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
*/
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jetbrains.jetCheck.Generator.*;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
public class GeneratorTest extends PropertyCheckerTestCase {
|
||||
|
||||
public void testMod() {
|
||||
checkFalsified(integers(),
|
||||
i -> i % 12 != 0,
|
||||
1);
|
||||
}
|
||||
|
||||
public void testListSumMod() {
|
||||
checkFalsified(nonEmptyLists(integers()),
|
||||
l -> l.stream().mapToInt(Integer::intValue).sum() % 10 != 0,
|
||||
301);
|
||||
}
|
||||
|
||||
public void testListContainsDivisible() {
|
||||
checkGeneratesExample(nonEmptyLists(integers()),
|
||||
l -> l.stream().anyMatch(i -> i % 10 == 0),
|
||||
4);
|
||||
}
|
||||
|
||||
public void testStringContains() {
|
||||
assertEquals("a", checkGeneratesExample(stringsOf(asciiPrintableChars()),
|
||||
s -> s.contains("a"),
|
||||
10));
|
||||
|
||||
String aWithB = checkGeneratesExample(stringsOf(IntDistribution.uniform(2, 100), asciiPrintableChars()),
|
||||
s -> s.contains("a") && s.contains("b"),
|
||||
27);
|
||||
assertTrue(aWithB, "ab".equals(aWithB) || "ba".equals(aWithB));
|
||||
}
|
||||
|
||||
public void testLetterStringContains() {
|
||||
checkFalsified(stringsOf(asciiLetters()),
|
||||
s -> !s.contains("a"),
|
||||
1);
|
||||
}
|
||||
|
||||
public void testIsSorted() {
|
||||
PropertyFailure<List<Integer>> failure = checkFalsified(nonEmptyLists(integers()),
|
||||
l -> l.stream().sorted().collect(Collectors.toList()).equals(l),
|
||||
36);
|
||||
List<Integer> value = failure.getMinimalCounterexample().getExampleValue();
|
||||
assertEquals(2, value.size());
|
||||
assertTrue(value.toString(), value.stream().allMatch(i -> Math.abs(i) < 2));
|
||||
}
|
||||
|
||||
public void testSuccess() {
|
||||
PropertyChecker.forAll(listsOf(integers(-1, 1)), l -> l.stream().allMatch(i -> Math.abs(i) <= 1));
|
||||
}
|
||||
|
||||
public void testSortedDoublesNonDescending() {
|
||||
PropertyFailure<List<Double>> failure = checkFalsified(listsOf(doubles()),
|
||||
l -> isSorted(l.stream().sorted().collect(Collectors.toList())),
|
||||
23);
|
||||
assertEquals(2, failure.getMinimalCounterexample().getExampleValue().size());
|
||||
}
|
||||
|
||||
private static boolean isSorted(List<Double> list) {
|
||||
for (int i = 0; i < list.size() - 1; i++) {
|
||||
double d1 = list.get(i);
|
||||
double d2 = list.get(i + 1);
|
||||
if (!(d1 <= d2)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void testSuchThat() {
|
||||
PropertyChecker.forAll(integers().suchThat(i -> i < 0), i -> i < 0);
|
||||
}
|
||||
|
||||
public void testNestedSometimesVeryRareSuchThat() {
|
||||
STABLE.forAll(frequency(50, constant(0), 1, integers(1, 1000)).suchThat(i -> i > 0), i -> i > 0);
|
||||
}
|
||||
|
||||
public void testStringOfStringChecksAllChars() {
|
||||
checkFalsified(stringsOf("abc "),
|
||||
s -> !s.contains(" "),
|
||||
0);
|
||||
}
|
||||
|
||||
public void testListNotLongerThanMaxDefaultSize() {
|
||||
PropertyChecker.customized().withIterationCount(100_000).forAll(listsOf(integers()), l -> l.size() <= PropertyChecker.DEFAULT_MAX_SIZE_HINT);
|
||||
}
|
||||
|
||||
public void testNonEmptyList() {
|
||||
PropertyChecker.forAll(nonEmptyLists(integers()), l -> !l.isEmpty());
|
||||
}
|
||||
|
||||
public void testNoDuplicateData() {
|
||||
Set<List<Integer>> visited = new HashSet<>();
|
||||
PropertyChecker.forAll(listsOf(integers()), l -> visited.add(l));
|
||||
}
|
||||
|
||||
public void testOneOf() {
|
||||
List<Integer> values = new ArrayList<>();
|
||||
PropertyChecker.forAll(anyOf(integers(0, 1), integers(10, 1100)), i -> values.add(i));
|
||||
assertTrue(values.stream().anyMatch(i -> i < 2));
|
||||
assertTrue(values.stream().anyMatch(i -> i > 5));
|
||||
}
|
||||
|
||||
public void testAsciiIdentifier() {
|
||||
PropertyChecker.forAll(asciiIdentifiers(),
|
||||
s -> Character.isJavaIdentifierStart(s.charAt(0)) && s.chars().allMatch(Character::isJavaIdentifierPart));
|
||||
checkGeneratesExample(asciiIdentifiers(),
|
||||
s -> s.contains("_"),
|
||||
9);
|
||||
}
|
||||
|
||||
public void testBoolean() {
|
||||
List<Boolean> list = checkGeneratesExample(listsOf(booleans()),
|
||||
l -> l.contains(true) && l.contains(false),
|
||||
4);
|
||||
assertEquals(2, list.size());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testRecheckWithGivenSeeds() {
|
||||
Generator<List<Integer>> gen = nonEmptyLists(integers(0, 100));
|
||||
Predicate<List<Integer>> property = l -> !l.contains(42);
|
||||
|
||||
PropertyFailure<?> failure = checkFails(PropertyChecker.customized().withSeed(1), gen, property).getFailure();
|
||||
assertTrue(failure.getIterationNumber() > 1);
|
||||
|
||||
PropertyFalsified e;
|
||||
|
||||
e = checkFails(PropertyChecker.customized().recheckingIteration(failure.getIterationSeed(), failure.getSizeHint()), gen, property);
|
||||
assertEquals(1, e.getFailure().getIterationNumber());
|
||||
|
||||
e = checkFails(PropertyChecker.customized().withSeed(failure.getGlobalSeed()), gen, property);
|
||||
assertEquals(failure.getIterationNumber(), e.getFailure().getIterationNumber());
|
||||
}
|
||||
|
||||
public void testSameFrequency() {
|
||||
checkFalsified(listsOf(frequency(1, constant(1), 1, constant(2))),
|
||||
l -> !l.contains(1) || !l.contains(2),
|
||||
2);
|
||||
|
||||
checkFalsified(listsOf(frequency(1, constant(1), 1, constant(2)).with(1, constant(3))),
|
||||
l -> !l.contains(1) || !l.contains(2) || !l.contains(3),
|
||||
5);
|
||||
}
|
||||
|
||||
public void testReplay() {
|
||||
List<List> log = new ArrayList<>();
|
||||
PropertyFailure<List<Integer>> failure = checkFalsified(listsOf(integers(0, 100)), l -> {
|
||||
log.add(l);
|
||||
return !l.contains(42);
|
||||
}, 9);
|
||||
List<Integer> goldMin = Collections.singletonList(42);
|
||||
|
||||
PropertyFailure.CounterExample<List<Integer>> first = failure.getFirstCounterExample();
|
||||
PropertyFailure.CounterExample<List<Integer>> min = failure.getMinimalCounterexample();
|
||||
assertEquals(goldMin, min.getExampleValue());
|
||||
assertTrue(log.contains(first.getExampleValue()));
|
||||
assertTrue(log.contains(min.getExampleValue()));
|
||||
|
||||
log.clear();
|
||||
PropertyFailure.CounterExample<List<Integer>> first2 = first.replay();
|
||||
assertEquals(first.getExampleValue(), first2.getExampleValue());
|
||||
assertEquals(log, Collections.singletonList(first2.getExampleValue()));
|
||||
|
||||
log.clear();
|
||||
PropertyFailure.CounterExample<List<Integer>> min2 = min.replay();
|
||||
assertEquals(goldMin, min2.getExampleValue());
|
||||
assertEquals(log, Collections.singletonList(goldMin));
|
||||
}
|
||||
|
||||
public void testShrinkToRangeStart() {
|
||||
PropertyFailure<String> failure = checkFalsified(stringsOf(asciiUppercaseChars()), s -> s.length() < 5, 11);
|
||||
assertEquals("AAAAA", failure.getMinimalCounterexample().getExampleValue());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
*/
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
abstract class PropertyCheckerTestCase extends TestCase {
|
||||
@SuppressWarnings("deprecation")
|
||||
static final PropertyChecker.Parameters STABLE = PropertyChecker.customized().withSeed(0);
|
||||
|
||||
protected <T> PropertyFalsified checkFails(PropertyChecker.Parameters parameters, Generator<T> checker, Predicate<T> predicate) {
|
||||
try {
|
||||
parameters.silent().forAll(checker, predicate);
|
||||
throw new AssertionError("Can't falsify " + getName());
|
||||
}
|
||||
catch (PropertyFalsified e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> T checkGeneratesExample(Generator<T> generator, Predicate<T> predicate, int minimizationSteps) {
|
||||
return checkFalsified(generator, predicate.negate(), minimizationSteps).getMinimalCounterexample().getExampleValue();
|
||||
}
|
||||
|
||||
protected <T> PropertyFailure<T> checkFalsified(Generator<T> generator, Predicate<T> predicate, int minimizationSteps) {
|
||||
PropertyFalsified e = checkFails(STABLE, generator, predicate);
|
||||
//noinspection unchecked
|
||||
PropertyFailure<T> failure = (PropertyFailure<T>)e.getFailure();
|
||||
|
||||
if (failure.getStoppingReason() != null) {
|
||||
throw new RuntimeException(failure.getStoppingReason());
|
||||
}
|
||||
|
||||
/*
|
||||
System.out.println(" " + getName());
|
||||
System.out.println("Value: " + e.getBreakingValue());
|
||||
System.out.println("Data: " + e.getData());
|
||||
*/
|
||||
assertEquals(minimizationSteps, failure.getTotalMinimizationExampleCount()); // to track that framework changes don't increase shrinking time significantly on average
|
||||
assertEquals(e.getBreakingValue(), generator.getGeneratorFunction().apply(((CounterExampleImpl)failure.getMinimalCounterexample()).createReplayData()));
|
||||
|
||||
String strData = failure.getMinimalCounterexample().getSerializedData();
|
||||
//noinspection deprecation
|
||||
assertNotNull(checkFails(PropertyChecker.customized().rechecking(strData), generator, predicate));
|
||||
|
||||
return failure;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
*/
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
public class RecursiveGeneratorTest extends PropertyCheckerTestCase {
|
||||
private static final Generator<Leaf> leaves = Generator.asciiLetters().map(Leaf::new);
|
||||
|
||||
private void checkShrinksToLeaf(Generator<Node> nodes) {
|
||||
PropertyFailure<?> failure = checkFails(STABLE, nodes, tree -> !tree.toString().contains("a")).getFailure();
|
||||
assertTrue(failure.getMinimalCounterexample().getExampleValue() instanceof Leaf);
|
||||
}
|
||||
|
||||
public void testShrinksToLeafWithFrequency() {
|
||||
checkShrinksToLeaf(Generator.recursive(nodes -> Generator.frequency(2, leaves,
|
||||
1, Generator.listsOf(nodes).map(Composite::new))));
|
||||
}
|
||||
|
||||
public void testShrinksToLeafWithAnyOf() {
|
||||
checkShrinksToLeaf(Generator.recursive(nodes -> Generator.anyOf(leaves,
|
||||
Generator.listsOf(nodes).map(Composite::new))));
|
||||
}
|
||||
|
||||
public void testShrinksToLeafDespiteWrapping() {
|
||||
checkShrinksToLeaf(Generator.recursive(nodes -> Generator.frequency(2, leaves,
|
||||
1, Generator.from(data -> data.generate(Generator.listsOf(nodes).map(Composite::new))))));
|
||||
}
|
||||
|
||||
private interface Node {}
|
||||
|
||||
private static class Leaf implements Node {
|
||||
final char c;
|
||||
Leaf(char c) { this.c = c;}
|
||||
public String toString() { return String.valueOf(c);}
|
||||
}
|
||||
|
||||
private static class Composite implements Node {
|
||||
final List<Node> children;
|
||||
Composite(List<Node> children) { this.children = children;}
|
||||
public String toString() { return "[" + children.stream().map(Object::toString).collect(Collectors.joining("")) + "]";}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
*/
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static org.jetbrains.jetCheck.Generator.*;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
public class ShrinkTest extends PropertyCheckerTestCase {
|
||||
public void testShrinkingComplexString() {
|
||||
checkFalsified(listsOf(stringsOf(asciiPrintableChars())),
|
||||
l -> {
|
||||
String s = l.toString();
|
||||
return !"abcdefghijklmnopqrstuvwxyz()[]#!".chars().allMatch(c -> s.indexOf((char)c) >= 0);
|
||||
},
|
||||
371);
|
||||
}
|
||||
|
||||
public void testShrinkingNonEmptyList() {
|
||||
List<Integer> list = checkGeneratesExample(nonEmptyLists(integers(0, 100)),
|
||||
l -> l.contains(42),
|
||||
12);
|
||||
assertEquals(1, list.size());
|
||||
}
|
||||
|
||||
public void testWhenEarlyObjectsCannotBeShrunkBeforeLater() {
|
||||
Generator<String> gen = listsOf(IntDistribution.uniform(0, 2), listsOf(IntDistribution.uniform(0, 2), sampledFrom('a', 'b'))).map(List::toString);
|
||||
Set<String> failing = new HashSet<>(Arrays.asList("[[a, b], [a, b]]", "[[a, b], [a]]", "[[a], [a]]", "[[a]]", "[]"));
|
||||
Predicate<String> property = s -> !failing.contains(s);
|
||||
checkFalsified(gen, property, 0); // prove that it sometimes fails
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
try {
|
||||
PropertyChecker.customized().silent().forAll(gen, property);
|
||||
}
|
||||
catch (PropertyFalsified e) {
|
||||
assertEquals("[]", e.getBreakingValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
*/
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.jetbrains.jetCheck.Generator.*;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
public class StatefulGeneratorTest extends PropertyCheckerTestCase {
|
||||
static final String DELETING = "deleting";
|
||||
|
||||
public void testShrinkingIntsWithDistributionsDependingOnListSize() {
|
||||
Generator<List<InsertChar>> gen = from(data -> {
|
||||
AtomicInteger modelLength = new AtomicInteger(0);
|
||||
Generator<List<InsertChar>> cmds = listsOf(from(cmdData -> {
|
||||
int index = cmdData.generate(integers(0, modelLength.getAndIncrement()));
|
||||
char c = cmdData.generate(asciiLetters());
|
||||
return new InsertChar(c, index);
|
||||
}));
|
||||
return data.generate(cmds);
|
||||
});
|
||||
List<InsertChar> minCmds = checkGeneratesExample(gen,
|
||||
cmds -> InsertChar.performOperations(cmds).contains("ab"),
|
||||
17);
|
||||
assertEquals(minCmds.toString(), 2, minCmds.size());
|
||||
}
|
||||
|
||||
public void testImperativeInsertDeleteCheckCommands() {
|
||||
Scenario minHistory = checkFalsified(Scenario.scenarios(() -> env -> {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
env.executeCommands(withRecursion(insertStringCmd(sb), deleteStringCmd(sb), checkDoesNotContain(sb, "A")));
|
||||
}), Scenario::ensureSuccessful, 29).getMinimalCounterexample().getExampleValue();
|
||||
|
||||
assertEquals("commands:\n" +
|
||||
" insert A at 0\n" +
|
||||
" check",
|
||||
minHistory.toString());
|
||||
}
|
||||
|
||||
public void testImperativeInsertReplaceDeleteCommands() {
|
||||
Scenario minHistory = checkFalsified(Scenario.scenarios(() -> env -> {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
ImperativeCommand replace = env1 -> {
|
||||
if (sb.length() == 0) return;
|
||||
int index = env1.generateValue(integers(0, sb.length() - 1), null);
|
||||
char toReplace = env1.generateValue(asciiLetters().suchThat(c -> c != 'A'), "replace " + sb.charAt(index) + " with %s at " + index);
|
||||
sb.setCharAt(index, toReplace);
|
||||
};
|
||||
|
||||
env.executeCommands(withRecursion(insertStringCmd(sb), replace, deleteStringCmd(sb), checkDoesNotContain(sb, "A")));
|
||||
}), Scenario::ensureSuccessful, 52).getMinimalCounterexample().getExampleValue();
|
||||
|
||||
assertEquals("commands:\n" +
|
||||
" insert A at 0\n" +
|
||||
" check",
|
||||
minHistory.toString());
|
||||
}
|
||||
|
||||
public void testImperativeCommandRechecking() {
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
Supplier<ImperativeCommand> command = () -> env -> {
|
||||
List<Integer> list = env.generateValue(listsOf(integers()), "%s");
|
||||
if (list.size() > 5 || counter.incrementAndGet() > 50) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
};
|
||||
try {
|
||||
PropertyChecker.customized().silent().checkScenarios(command);
|
||||
fail();
|
||||
}
|
||||
catch (PropertyFalsified e) {
|
||||
assertFalse(e.getMessage(), e.getMessage().contains("forAll(..."));
|
||||
assertTrue(e.getMessage(), e.getMessage().contains("rechecking("));
|
||||
assertTrue(e.getMessage(), e.getMessage().contains("checkScenarios(..."));
|
||||
|
||||
PropertyFailure<?> failure = e.getFailure();
|
||||
try {
|
||||
//noinspection deprecation
|
||||
PropertyChecker.customized().silent().rechecking(failure.getMinimalCounterexample().getSerializedData()).checkScenarios(command);
|
||||
fail();
|
||||
}
|
||||
catch (PropertyFalsified fromRecheck) {
|
||||
assertEquals(e.getBreakingValue(), fromRecheck.getBreakingValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we shouldn't fail on incomplete data
|
||||
// because the test might have failed in the middle of some command execution,
|
||||
// and after we fixed the reason of test failure, the command might just want to continue working,
|
||||
// but there's no saved data for that
|
||||
public void testRecheckingOnIncompleteData() {
|
||||
AtomicBoolean shouldFail = new AtomicBoolean(true);
|
||||
Supplier<ImperativeCommand> command = () -> env -> {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
env.generateValue(integers(0, 100), null);
|
||||
if (shouldFail.get()) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
PropertyChecker.customized().silent().checkScenarios(command);
|
||||
fail();
|
||||
}
|
||||
catch (PropertyFalsified e) {
|
||||
shouldFail.set(false);
|
||||
|
||||
//noinspection deprecation
|
||||
PropertyChecker.customized().silent().rechecking(e.getFailure().getMinimalCounterexample().getSerializedData()).checkScenarios(command);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static Generator<ImperativeCommand> withRecursion(ImperativeCommand... commands) {
|
||||
return recursive(rec -> {
|
||||
ImperativeCommand group = env -> {
|
||||
env.logMessage("Group");
|
||||
env.executeCommands(rec);
|
||||
};
|
||||
return frequency(2, constant(group), 3, sampledFrom(commands));
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
@NotNull
|
||||
private static ImperativeCommand checkDoesNotContain(StringBuilder sb, String infix) {
|
||||
return env -> {
|
||||
env.logMessage("check");
|
||||
if (sb.indexOf(infix) >= 0) throw new AssertionError();
|
||||
};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static ImperativeCommand insertStringCmd(StringBuilder sb) {
|
||||
return env -> {
|
||||
int index = env.generateValue(integers(0, sb.length()), null);
|
||||
String toInsert = env.generateValue(stringsOf(asciiLetters()), "insert %s at " + index);
|
||||
sb.insert(index, toInsert);
|
||||
};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static ImperativeCommand deleteStringCmd(StringBuilder sb) {
|
||||
return env -> {
|
||||
int start = env.generateValue(integers(0, sb.length()), null);
|
||||
int end = env.generateValue(integers(start, sb.length()), DELETING + " (" + start + ", %s)");
|
||||
sb.delete(start, end);
|
||||
};
|
||||
}
|
||||
|
||||
private ImperativeCommand heavyCommand() {
|
||||
Object[] heavyObject = new Object[100_000];
|
||||
heavyObject[42] = new Object();
|
||||
return new ImperativeCommand() {
|
||||
@Override
|
||||
public void performCommand(@NotNull Environment env) {}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + Arrays.toString(heavyObject);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void testDontFailByOutOfMemoryDueToLeakingObjectsPassedIntoGenerators() {
|
||||
PropertyChecker.customized().checkScenarios(() -> env ->
|
||||
env.executeCommands(from(data -> data.generate(sampledFrom(heavyCommand(), heavyCommand(), heavyCommand())))));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class InsertChar {
|
||||
private final char c;
|
||||
private final int index;
|
||||
|
||||
InsertChar(char c, int index) {
|
||||
this.c = c;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public void performOperation(StringBuilder sb) {
|
||||
sb.insert(index, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "insert " + c + " at " + index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof InsertChar)) return false;
|
||||
InsertChar aChar = (InsertChar)o;
|
||||
return c == aChar.c && index == aChar.index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(c, index);
|
||||
}
|
||||
|
||||
static String performOperations(List<InsertChar> cmds) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
cmds.forEach(cmd -> cmd.performOperation(sb));
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.jetCheck;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.jetbrains.jetCheck.StatefulGeneratorTest.*;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class SubSequenceTest extends PropertyCheckerTestCase{
|
||||
private final String subSequence;
|
||||
private final int expectedMinimizations;
|
||||
|
||||
@SuppressWarnings("JUnitTestCaseWithNonTrivialConstructors")
|
||||
public SubSequenceTest(String subSequence, int expectedMinimizations) {
|
||||
this.subSequence = subSequence;
|
||||
this.expectedMinimizations = expectedMinimizations;
|
||||
}
|
||||
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
public static Collection data() {
|
||||
return Arrays.asList(
|
||||
new Object[]{"abcde", 399},
|
||||
new Object[]{"abcdef", 420},
|
||||
new Object[]{"sadf", 107},
|
||||
new Object[]{"asdf", 118},
|
||||
new Object[]{"xxx", 81},
|
||||
new Object[]{"AA", 47}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkGeneratesSubSequence() {
|
||||
PropertyFailure.CounterExample<Scenario> example = checkFalsified(Scenario.scenarios(() -> env -> {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
env.executeCommands(withRecursion(insertStringCmd(sb), deleteStringCmd(sb), e -> {
|
||||
if (containsSubSequence(sb.toString(), subSequence)) {
|
||||
throw new AssertionError("Found " + sb.toString());
|
||||
}
|
||||
}));
|
||||
}), Scenario::ensureSuccessful, expectedMinimizations).getMinimalCounterexample();
|
||||
String log = example.getExampleValue().toString();
|
||||
assertEquals(log, "Found " + subSequence, example.getExceptionCause().getMessage());
|
||||
assertFalse(log, log.contains(DELETING));
|
||||
}
|
||||
|
||||
private static boolean containsSubSequence(String string, String subSequence) {
|
||||
int pos = -1;
|
||||
for (int i = 0; i < subSequence.length(); i++) {
|
||||
pos = string.indexOf(subSequence.charAt(i), pos + 1);
|
||||
if (pos < 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user