You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In some (relatively rare) circumstances the QCheck.Shrink.list_spine shrinker can produce non-minimal counterexamples.
Notice the last 4-element counterexample in the following:
##require"qcheck-core";;
##require"qcheck-core.runner";;
#openQCheck;;
#let t =Test.make
(small_list small_nat)
(funxs -> not (List.mem 0 xs &&List.mem 1 xs &&List.mem 2 xs));;
#QCheck.Test.check_exn ~rand:(Random.State.make [|16;17;16|]) t;;
Exception:
test `anon_test_1` failed on ≥ 1 cases: [1; 0; 2] (after 13 shrink steps)
#QCheck.Test.check_exn ~rand:(Random.State.make [|16;17;17|]) t;;
Exception:
test `anon_test_1` failed on ≥ 1 cases: [1; 0; 2] (after 2 shrink steps)
#QCheck.Test.check_exn ~rand:(Random.State.make [|16;17;18|]) t;;
Exception:
test `anon_test_1` failed on ≥ 1 cases: [2; 1; 0; 0] (after 4 shrink steps)
This is a consequence of the computationally fast list shrinker from #242.
In slogan form the issue can be phrased as: "speed or exhaustiveness - pick one".
In a few more words:
A naive shrinker will try to remove each element in turn, producing O(n) shrinking candidates
Even that strategy is not exhaustive, as some reductions requires simultaneously removing more than one element (occurrences of add k; remove k that cancel out each other is one such example)
It will be prohibitively slow on longer lists though
I'm not sure what would be the best fix to the issue without breaking the computational complexity.
In https://github.com/jmid/multicoretests where we hit this, I'm experimenting with simply stopping the recursion a bit earlier and emitting hand-chosen shrinker candidates for the new base cases.
This has the advantage of not emitting any more candidates and the disadvantage of pushing the problem of incompleteness to larger lists (recall slogan above).
The following snippet can be used to produce an ASCII-plot of shrinker candidate counts:
At a glance: can we shrink large lists with the fast current algo, and shrink small (len < 16) lists with the more exhaustive algo? Just like sorting uses merge sort for large arrays and insertion sort for small ones?
That's a good suggestion 👍
To avoid breaking the complexity of the current (recursive) algo, we should then have a top-level Shrink.list that just calculates the length and decides between Shrink.list_fast and Shrink.list_more_exhaustive.
(I realize that there's redundant List.length computation going on in the current algo - and so more speed to be gained - but I digress... 😅 )
In some (relatively rare) circumstances the
QCheck.Shrink.list_spine
shrinker can produce non-minimal counterexamples.Notice the last 4-element counterexample in the following:
This is a consequence of the computationally fast list shrinker from #242.
In slogan form the issue can be phrased as: "speed or exhaustiveness - pick one".
In a few more words:
add k; remove k
that cancel out each other is one such example)I'm not sure what would be the best fix to the issue without breaking the computational complexity.
In https://github.com/jmid/multicoretests where we hit this, I'm experimenting with simply stopping the recursion a bit earlier and emitting hand-chosen shrinker candidates for the new base cases.
This has the advantage of not emitting any more candidates and the disadvantage of pushing the problem of incompleteness to larger lists (recall slogan above).
The following snippet can be used to produce an ASCII-plot of shrinker candidate counts:
The shape is identical - and nicely logarithmic for both
Shrink.list_spine
andshrink_list_spine
above:The text was updated successfully, but these errors were encountered: