Skip to content

Arbitraries containing null don't work correctly when used exhaustively #77

@rkraneis

Description

@rkraneis

We want to exhaustively test parameters that can be either null or zero. This seems to be not possible at the moment. In the cases where jqwik automatically selects exhaustive generation (nullOrZero 1, 2 and 4), only null or zero is ever generated, depending on how the arbitrary is created. Forcing random generation on the other hand leads to the generation of both null and zero.

public class NullOrZeroTest {

  @Property @Report(Reporting.GENERATED)
  boolean nullOrZero1(@ForAll("nullOrZero1") BigDecimal bd) {
    System.out.println("nullOrZero1 " + bd);
    return bd == null || bd.compareTo(BigDecimal.ZERO) == 0;
  }

  @Property @Report(Reporting.GENERATED)
  boolean nullOrZero2(@ForAll("nullOrZero2") BigDecimal bd) {
    System.out.println("nullOrZero2 " + bd);
    return bd == null || bd.compareTo(BigDecimal.ZERO) == 0;
  }

  @Property(generation = GenerationMode.EXHAUSTIVE) @Report(Reporting.GENERATED)
  boolean nullOrZero3(@ForAll("nullOrZero3") BigDecimal bd) {
    System.out.println("nullOrZero3 " + bd);
    return bd == null || bd.compareTo(BigDecimal.ZERO) == 0;
  }

  @Property @Report(Reporting.GENERATED)
  boolean nullOrZero4(@ForAll("nullOrZero4") BigDecimal bd) {
    System.out.println("nullOrZero4 " + bd);
    return bd == null || bd.compareTo(BigDecimal.ZERO) == 0;
  }

  @Provide Arbitrary<BigDecimal> nullOrZero1() {
    return Arbitraries.of(null, BigDecimal.ZERO);
  }

  @Provide Arbitrary<BigDecimal> nullOrZero2() {
    return Arbitraries.of(BigDecimal.ZERO, null);
  }

  @Provide Arbitrary<BigDecimal> nullOrZero3() {
    return Arbitraries.bigDecimals().between(BigDecimal.ZERO, BigDecimal.ZERO)
        .withSamples((BigDecimal) null);
  }

  @Provide Arbitrary<BigDecimal> nullOrZero4() {
    return Arbitraries.of(new BigDecimal("0"), new BigDecimal("0.0"), new BigDecimal("0.00"))
        .withSamples((BigDecimal) null);
  }
}

Test output:

Running NullOrZeroTest
timestamp = 2019-12-12T20:26:00.925608, generated = [null]

nullOrZero1 null
timestamp = 2019-12-12T20:26:00.944175, generated = [0]

nullOrZero2 0
timestamp = 2019-12-12T20:26:00.944756, generated = [null]

nullOrZero2 null
timestamp = 2019-12-12T20:26:00.951872, NullOrZeroTest:nullOrZero2 = 
                              |-------------------jqwik-------------------
tries = 2                     | # of calls to property
checks = 2                    | # of not rejected calls
generation-mode = EXHAUSTIVE  | parameters are exhaustively generated
after-failure = PREVIOUS_SEED | use the previous seed
seed = 4566159385613353648    | random seed to reproduce generated values


timestamp = 2019-12-12T20:26:00.973788, generated = [null]

nullOrZero4 null
Tests run: 4, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.152 s <<< FAILURE! - in NullOrZeroTest
nullOrZero3  Time elapsed: 0.015 s  <<< ERROR!
net.jqwik.api.JqwikException: EXHAUSTIVE generation is not possible. Maybe too many potential examples?

╷
├─ jqwik (JUnit Platform) ✔
│  └─ NullOrZeroTest ✔
│     ├─ nullOrZero1 ✔
│     ├─ nullOrZero2 ✔
│     ├─ nullOrZero3 ✘ EXHAUSTIVE generation is not possible. Maybe too many potential examples?
│     └─ nullOrZero4 ✔
└─ JUnit Jupiter ✔

Results:

Errors: 
  NullOrZeroTest.nullOrZero3 » Jqwik EXHAUSTIVE generation is not possible. Mayb...

Tests run: 4, Failures: 0, Errors: 1, Skipped: 0

Edit: The nullOrZero2 combination actually does work, but the others not. So it seems null must not be the first value created.
Edit2: Even then we do not get the full test matrix when testing two parameters:

Running NullOrZeroTest
timestamp = 2019-12-12T20:49:32.218276, generated = [0, 0]

timestamp = 2019-12-12T20:49:32.220822, generated = [0, null]

timestamp = 2019-12-12T20:49:32.221372, generated = [null, 0]

timestamp = 2019-12-12T20:49:32.231031, NullOrZeroTest:nullOrZero2 = 
                              |-------------------jqwik-------------------
tries = 3                     | # of calls to property
checks = 3                    | # of not rejected calls
generation-mode = EXHAUSTIVE  | parameters are exhaustively generated
after-failure = PREVIOUS_SEED | use the previous seed
seed = 8110854518611637746    | random seed to reproduce generated values

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions