diff --git a/slowCheck/src/slowCheck/Generator.java b/slowCheck/src/slowCheck/Generator.java index d11922d9d021..492c7dc03d40 100644 --- a/slowCheck/src/slowCheck/Generator.java +++ b/slowCheck/src/slowCheck/Generator.java @@ -34,7 +34,11 @@ public final class Generator { } public Generator map(@NotNull Function fun) { - return from(data -> fun.apply(generateUnstructured(data))); + return from(data -> fun.apply(myFunction.apply(data))); + } + + public Generator flatMap(@NotNull Function> fun) { + return from(data -> fun.apply(generateValue(data)).generateValue(data)); } public Generator noShrink() { diff --git a/slowCheck/src/slowCheck/PropertyFalsified.java b/slowCheck/src/slowCheck/PropertyFalsified.java index b2298def84c7..75334dfcb8a7 100644 --- a/slowCheck/src/slowCheck/PropertyFalsified.java +++ b/slowCheck/src/slowCheck/PropertyFalsified.java @@ -1,5 +1,9 @@ package slowCheck; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; import java.util.function.Supplier; /** @@ -7,19 +11,63 @@ import java.util.function.Supplier; */ @SuppressWarnings("ExceptionClassNameDoesntEndWithException") public class PropertyFalsified extends RuntimeException { + static final String FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION = "!!! FAILURE REASON HAS CHANGED DURING MINIMIZATION !!!"; + private final long seed; private final PropertyFailure failure; private final Supplier data; - public PropertyFalsified(int seed, PropertyFailure failure, Supplier data) { - super("Falsified on " + failure.getMinimalCounterexample().getExampleValue() + "\n" + - (failure.getTotalMinimizationStepCount() > 0 - ? "Minimized in " + failure.getTotalMinimizationStepCount() + " steps\n" - : "") + - "Seed=" + seed, failure.getMinimalCounterexample().getExceptionCause()); + PropertyFalsified(long seed, PropertyFailure failure, Supplier data) { + super(failure.getMinimalCounterexample().getExceptionCause()); + this.seed = seed; this.failure = failure; this.data = data; } + @Override + public String getMessage() { + int minimizationStepCount = failure.getTotalMinimizationStepCount(); + String msg = "Falsified on " + failure.getMinimalCounterexample().getExampleValue() + "\n" + + (minimizationStepCount > 0 ? "Minimized in " + minimizationStepCount + " steps\n" : "") + + "Seed=" + seed; + Throwable first = failure.getFirstCounterExample().getExceptionCause(); + if (exceptionsDiffer(first, failure.getMinimalCounterexample().getExceptionCause())) { + msg += "\n " + FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION; + if (first != null) { + msg += "\n Initial exception: " + printStackTrace(first) + "\n==========================\n"; + } else { + msg += "\n Initially property was falsified without exceptions\n"; + } + } + return msg; + } + + 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; + + return !getUserTrace(e1).equals(getUserTrace(e2)); + } + + private static List getUserTrace(Throwable e) { + List result = new ArrayList<>(); + for (StackTraceElement element : e.getStackTrace()) { + String s = element.toString(); + if (s.startsWith("slowCheck.CounterExampleImpl.checkProperty")) { + break; + } + result.add(s); + } + return result; + } + + private static String printStackTrace(Throwable e) { + StringWriter stringWriter = new StringWriter(); + PrintWriter writer = new PrintWriter(stringWriter); + e.printStackTrace(writer); + return stringWriter.getBuffer().toString(); + } + public PropertyFailure getFailure() { return failure; } diff --git a/slowCheck/test/slowCheck/ExceptionTest.java b/slowCheck/test/slowCheck/ExceptionTest.java new file mode 100644 index 000000000000..6c876fc6cc84 --- /dev/null +++ b/slowCheck/test/slowCheck/ExceptionTest.java @@ -0,0 +1,51 @@ +package slowCheck; + +import static slowCheck.Generator.integers; + +/** + * @author peter + */ +public class ExceptionTest extends PropertyCheckerTestCase { + + public void testFailureReasonUnchanged() { + try { + PropertyChecker.forAll(ourTestSettings, integers(), i -> { + throw new AssertionError("fail"); + }); + fail(); + } + catch (PropertyFalsified e) { + assertFalse(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION)); + } + } + + public void testFailureReasonChangedExceptionClass() { + try { + PropertyChecker.forAll(ourTestSettings, integers(), i -> { + throw (i == 0 ? new RuntimeException("fail") : new IllegalArgumentException("fail")); + }); + fail(); + } + catch (PropertyFalsified e) { + assertTrue(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION)); + } + } + + public void testFailureReasonChangedExceptionTrace() { + try { + PropertyChecker.forAll(ourTestSettings, integers(), i -> { + if (i == 0) { + throw new AssertionError("fail"); + } + else { + throw new AssertionError("fail2"); + } + }); + fail(); + } + catch (PropertyFalsified e) { + assertTrue(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION)); + } + } + +} diff --git a/slowCheck/test/slowCheck/GeneratorTest.java b/slowCheck/test/slowCheck/GeneratorTest.java index beca7deede24..b8be3046acde 100644 --- a/slowCheck/test/slowCheck/GeneratorTest.java +++ b/slowCheck/test/slowCheck/GeneratorTest.java @@ -1,12 +1,9 @@ package slowCheck; -import junit.framework.TestCase; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.function.Predicate; import java.util.stream.Collectors; import static slowCheck.Generator.*; @@ -14,8 +11,7 @@ import static slowCheck.Generator.*; /** * @author peter */ -public class GeneratorTest extends TestCase { - private static final CheckerSettings ourTestSettings = CheckerSettings.DEFAULT_SETTINGS.withSeed(0); +public class GeneratorTest extends PropertyCheckerTestCase { public void testMod() { checkFalsified(integers(), @@ -75,12 +71,6 @@ public class GeneratorTest extends TestCase { return true; } - public void testPropertyThrowsException() { - checkFalsified(integers(), p -> { - throw new AssertionError(p); - }, 1); - } - public void testSuchThat() { PropertyChecker.forAll(integers().suchThat(i -> i < 0), i -> i < 0); } @@ -145,22 +135,4 @@ public class GeneratorTest extends TestCase { assertEquals(1, failure.getMinimalCounterexample().getExampleValue().size()); } - private PropertyFailure checkFalsified(Generator generator, Predicate predicate, int minimizationSteps) { - try { - PropertyChecker.forAll(ourTestSettings, generator, predicate); - throw new AssertionError("Can't falsify " + getName()); - } - catch (PropertyFalsified e) { - //noinspection unchecked - PropertyFailure failure = (PropertyFailure)e.getFailure(); - - System.out.println(" " + getName()); - System.out.println("Value: " + e.getBreakingValue()); - System.out.println("Data: " + e.getData()); - assertEquals(minimizationSteps, failure.getTotalMinimizationStepCount()); - assertEquals(e.getBreakingValue(), generator.generateUnstructured(e.getData())); - - return failure; - } - } } diff --git a/slowCheck/test/slowCheck/PropertyCheckerTestCase.java b/slowCheck/test/slowCheck/PropertyCheckerTestCase.java new file mode 100644 index 000000000000..7f1dc2d5b70f --- /dev/null +++ b/slowCheck/test/slowCheck/PropertyCheckerTestCase.java @@ -0,0 +1,32 @@ +package slowCheck; + +import junit.framework.TestCase; + +import java.util.function.Predicate; + +/** + * @author peter + */ +abstract class PropertyCheckerTestCase extends TestCase { + protected static final CheckerSettings ourTestSettings = CheckerSettings.DEFAULT_SETTINGS.withSeed(0); + + protected PropertyFailure checkFalsified(Generator generator, Predicate predicate, int minimizationSteps) { + try { + PropertyChecker.forAll(ourTestSettings, generator, predicate); + throw new AssertionError("Can't falsify " + getName()); + } + catch (PropertyFalsified e) { + //noinspection unchecked + PropertyFailure failure = (PropertyFailure)e.getFailure(); + + System.out.println(" " + getName()); + System.out.println("Value: " + e.getBreakingValue()); + System.out.println("Data: " + e.getData()); + assertEquals(minimizationSteps, failure.getTotalMinimizationStepCount()); + assertEquals(e.getBreakingValue(), generator.generateUnstructured(e.getData())); + + return failure; + } + } + +}