Add filtering to combine(...) (combineNotNull?)
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
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)
}
}
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.
Adding
filterto the combinators might be viable, however, it would require redeclaration of the variables which looks sad.
That's true for all filter methods, right?
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.
Added to TODO/BACKLOG.
Done with https://github.com/jlink/jqwik/commit/aea950e1ead866e66ebafbbc2b75479cdaf9f80b
Released in 1.7.1-SNAPSHOT
Snapshot documentation is here: https://jqwik.net/docs/snapshot/user-guide.html#filtering-combinations