remove jetCheck source

now it lives separately on https://github.com/JetBrains/jetCheck
This commit is contained in:
peter
2018-07-10 07:12:44 +02:00
parent 300037705e
commit 1e170960b6
36 changed files with 0 additions and 3272 deletions

2
.idea/modules.xml generated
View File

@@ -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" />

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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);
}
};
}
}

View File

@@ -1,13 +0,0 @@
package org.jetbrains.jetCheck;
/**
* @author peter
*/
class CannotRestoreValue extends RuntimeException {
CannotRestoreValue() {
}
CannotRestoreValue(String message) {
super(message);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 {}
}

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -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?");
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
});
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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());
}
}
}

View File

@@ -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();
}
}

View File

@@ -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 +
'}';
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
};
}
}

View File

@@ -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();
}
}

View File

@@ -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
}

View File

@@ -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) {
}
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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("")) + "]";}
}
}

View File

@@ -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());
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}