quicktheories / QuickTheories

Property based testing for Java 8
Apache License 2.0
505 stars 51 forks source link

Assumptions that check for non-empty lists fail because empty lists are generated too often #76

Open pvigliatore opened 4 years ago

pvigliatore commented 4 years ago

The following simplified examples are written in Kotlin and were tested with version 0.25 and 0.26 of QuickTheories

The test case below fails reliably. Even though the theory is asking for lists ranging from size 0 to 10,000 the assumption will claim to find 0 matching values.

    @Test
    fun whenUsingAssumptionsTheLowerBoundOfListSizeIsTooCommon() {
        qt().forAll(lists().of(integers().all()).ofSizeBetween(0, 10000))
            .assuming { it.isNotEmpty() }
            .check { true }
    }

The failure message for the above test is:

java.lang.IllegalStateException: Gave up after finding only 0 example(s) matching the assumptions

However, the upper bound and a non-zero lower bound do not have the same behavior as above. The following test cases always pass.

   @Test
    fun whenUsingAssumptionsTheUpperBoundOfListSizeINotTooCommon() {
        qt().forAll(lists().of(integers().all()).ofSizeBetween(0, 10000))
            .assuming { it.size < 10000 }
            .check { true }
    }

    @Test
    fun whenUsingAssumptionsTheNonZeroLowerBoundOfListsSizeIsNotTooCommon() {
        qt().forAll(lists().of(integers().all()).ofSizeBetween(1, 10000))
            .assuming { it.size > 1 }
            .check { true }
    }

Lastly, when capturing the input to the assumption, the supplied values are very different.

    @Test
    fun whenUsingAssumptionsTheLowerBoundOfListSizeIsTooCommon() {
        qt().forAll(lists().of(integers().all()).ofSizeBetween(0, 10000))
            .assuming { println(it.size); it.size > 0}
            .check { true }
    }

The output of the above test is:

9143
0
0
0
0
0
0
0
0
0
0

java.lang.IllegalStateException: Gave up after finding only 0 example(s) matching the assumptions

    at org.quicktheories.core.ExceptionReporter.valuesExhausted(ExceptionReporter.java:26)
    at org.quicktheories.impl.TheoryRunner.check(TheoryRunner.java:39)
    at org.quicktheories.dsl.TheoryBuilder.check(TheoryBuilder.java:132)

By changing the assumption to ALWAYS pass, the output is completely different:

    @Test
    fun whenUsingAssumptionsTheLowerBoundOfListSizeIsTooCommon() {
        qt().forAll(lists().of(integers().all()).ofSizeBetween(0, 10000))
            .assuming { println(it.size); true}
            .check { true }
    }

1277
0
0
10000
3202
2346
6106
823
1207
9146
5662
3457