jqwik icon indicating copy to clipboard operation
jqwik copied to clipboard

Add filtering to combine(...) (combineNotNull?)

Open vlsi opened this issue 3 years ago • 5 comments

Testing Problem

I combine arbitraries (for state-dependent testing), however, certain values are not acceptable, so I want to filter the outcome of the combination.

For instance, for testing swap(i, j) I need two indices. However, I would like to avoid calling swap when i==j.

combine(ints(), ints()) { i, j ->
    if (i == j) {
        ??? // How to tell `combine` i==j is not supported?
    }
    Action {
       ... // call swap(i, j)
    }
}

Suggested Solution

Add combineNotNull, flatMapNotNull, etc methods similar to net.jqwik.api.Arbitrary#filter(java.util.function.Predicate<T>), net.jqwik.api.Arbitrary#filter(java.util.function.Predicate<T>, int maxMisses)

combineNotNull(ints(), ints()) { i, j ->
    if (i == j) {
        return null
    }
    Action {
       ... // call swap(i, j)
    }
}

See https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/filter-not-null.html , https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/map-not-null.html

vlsi avatar May 03 '22 14:05 vlsi

Sounds useful.

The not-null should be easy to simulate:

combine(ints(), ints()) { i, j ->
    if (i == j) {
        return null
    }
    Action {
       ... // call swap(i, j)
    }
}.filter{ a -> a != null }

From a readability standpoint I'd rather have something like:

Combinators.combine(ints(), ints())
   .filter( i, j -> i != j)
   .as(i, j -> new MyAction(...);

And similar for Kotlin.

Introducing CombinatorX.asTuple() together with Kotlin's deconstruction capabilities would get us almost there I think:

combine(ints(), ints())
   .asTuple()
   .filter { 
      val i, j = it
      return i != j
   }
   .map {
     val i, j = it
     Action {
       ... // call swap(i, j)
     }
   }

jlink avatar May 04 '22 09:05 jlink

The issue with asTuple() is that it requires mentioning the same variables multiple times which looks odd.

The not-null should be easy to simulate:

combine(ints(), ints()) { i, j ->
    if (i == j) {
        return null
    }
    Action {
       ... // call swap(i, j)
    }
}.filter{ a -> a != null }

Currently Combinator#as wants a function that returns non-nullable result:

public <R> Arbitrary<R> as(F2<T1, T2, @NotNull R> combinator) {

Adding filter to the combinators might be viable, however, it would require redeclaration of the variables which looks sad.

vlsi avatar May 04 '22 12:05 vlsi

Adding filter to the combinators might be viable, however, it would require redeclaration of the variables which looks sad.

That's true for all filter methods, right?

jlink avatar May 05 '22 11:05 jlink

That's true for all filter methods, right?

Arbitrary#filter has only one parameter, so there's no issue with naming, reordering, etc. combine zips multiple values, so re-declaration is more pronounced.

For instance:

    fun `update num rows statistics`(
        attribute: Arbitrary<out AttributeRef?>,
        objectType: Arbitrary<out ObjectTypeRef?>,
        table: Arbitrary<out TableRef?>,
        numRows: Arbitrary<Int> = frequency(
            1 to 0,
            1 to 100,
        ),
    ) =
        combine(attribute, objectType, table, numRows) { attribute, objectType, table, numRows ->
            if (attribute == null && objectType == null && table == null) return null // require at least one non-null value
        }

vs

        combine(attribute, objectType, table, numRows)
            .filter { attribute, objectType, table, numRows ->
                attribute != null || objectType != null || table != null
            }
           .`as` { attribute, objectType, table, numRows -> // <-- re-declaration of the variables :-/
                attribute != null || objectType != null || table != null
            }

In any case, if there's combine().filter(), then everyone can add combineNotNull function.

vlsi avatar May 05 '22 13:05 vlsi

Added to TODO/BACKLOG.

jlink avatar May 09 '22 11:05 jlink

Done with https://github.com/jlink/jqwik/commit/aea950e1ead866e66ebafbbc2b75479cdaf9f80b

Released in 1.7.1-SNAPSHOT

jlink avatar Oct 10 '22 12:10 jlink

Snapshot documentation is here: https://jqwik.net/docs/snapshot/user-guide.html#filtering-combinations

jlink avatar Oct 10 '22 13:10 jlink