Shrinking¶
Description¶
Each generated Value
can be shrunk to 2 new values. These are named a
and b
and wrapped in a Dichotomy
.
This allows to take a different route when a shrunk value makes a test pass.
Usually the a
strategy is the more aggressive shrinking strategy to try to find the smallest value as fast as possible. And b
is the more fine grained one, but will take longer.
The difficult part of the shrinking mechanism is that values can be mapped/composed to new ones and must adhere to a predicate. And each low level shrunk value must be mapped/composed back and still adhere to the predicate.
To keep the shrinkers as simple as possible the mapping and predicates are hidden from them.
To do this a Value
will always keep the original value, all the transformations steps (aka mapping) and the predicate it must adhere to. Then when a shrinker applies a shrinking strategy to a Value
, the value can re-apply the transformations to the shrunk value and make sure the new value adheres to the predicate.
Sequence
¶
graph TB
Array -->|Next| RecursiveHalf{Either}
RecursiveHalf -->|a| RemoveHalf
RecursiveHalf -->|b| RemoveTail
RemoveHalf -->|Next| RecursiveHalf
RemoveTail -->|Next| RecursiveTail{Either}
RecursiveTail -->|a| RemoveTail
RecursiveTail -->|b| RemoveHead
RemoveHead -->|Next| RecursiveHead{Either}
RecursiveHead -->|a| RemoveHead
RecursiveHead -->|b| RemoveNth
RemoveNth -->|Next| RecursiveNth{Either}
RecursiveNth -->|"a(n)"| RemoveNth
RecursiveNth -->|"b(n+1)"| RemoveNth
RemoveNth -->|When n overflows| ShrinkANth[Shrink nth element with strategy A]
ShrinkANth -->|Next| RecursiveNthShrink{Either}
RecursiveNthShrink -->|"a(n)"| ShrinkANth
RecursiveNthShrink -->|"b(n+1)"| ShrinkANth
ShrinkANth -->|When n overflows| ShrinkBNth[Shrink nth element with strategy B]
ShrinkBNth -->|Next| RecursiveNthShrink
ShrinkBNth -->|When no longer shrinkable| Identity
This design makes sure all elements are shrunk to their minimum values. It's assured when shrinking elements of the array
with their a
or b
strategy. When we apply a strategy makes the test pass we try to shrink the next available value in the array. This privileges first shrinking values that do not affect the test.
This strategy means the values that affect the failing test are shrunk last. So it will take more shrinking steps to find the minimum values that make a test fail.
Composite
¶
graph TB
Composite -->|Next| RecursiveNthShrink{Either}
RecursiveNthShrink -->|"a(n)"| ShrinkANth
RecursiveNthShrink -->|"b(n+1)"| ShrinkANth
ShrinkANth -->|When n overflows| ShrinkBNth[Shrink nth element with strategy B]
ShrinkBNth -->|Next| RecursiveNthShrink
ShrinkBNth -->|When no longer shrinkable| Identity
This design is identical to the Sequence
one except it doesn't need to remove elements from the composite. It only shrinks elements of the composite.
Seed
ed values¶
The complexity of seeded values lies with the filtering of these values when shrinking.
Let's take a simple example:
The Set
can apply this filter and not yield any empty string. And for any yielded ones if can access the predicate when shrinking the strings in order to not create Dichotomy
s with invalid strings.
The problem with seeded values can be expressed with this example:
use Innmind\BlackBox\Set;
Set::integers()
->flatMap(
static fn(Set\Seed $int) => Set::strings()->map(
static fn(string $string) => $int->map(
static fn(int $int) => $int.$string,
),
),
)
->filter(static fn(string $string) => $string !== '0');
Here when the filter
is applied it abstracts that the string is seeded with a leading int
. But in the implementation it can't know where the seed comes from. So it can't affect the way the int
could be shrunk.
The problem here is that the Dichotomy
attached to any Value
is statically defined when yielding it.
To still correctly filter out seeded values the predicate is attached to the Value
object and is check on both values generated by a Dichotomy
to make sure both are acceptable. If not both are then it prevents shrinking the seed.
This design prevents shrinking to the minimum values in the case only one of the shrunk values is acceptable.
A research should be conducted to improve the shrinking system to fix this shortcoming of seeded values.