2
2
3
3
import java .util .*;
4
4
import java .util .function .*;
5
+ import java .util .stream .*;
5
6
6
7
import net .jqwik .api .*;
7
8
import net .jqwik .api .state .*;
8
9
9
- import org .jetbrains .annotations .*;
10
-
11
10
class ShrinkableChainIteration <T > {
11
+ // Larger values might improve shrink quality, however, they increase the shrink space, so it might increase shrink duration
12
+ private final static int NUM_SAMPLES_IN_EAGER_CHAIN_SHRINK = Integer .getInteger ("jqwik.eagerChainShrinkSamples" , 2 );
13
+
14
+ static class ShrinkableWithEagerValue <T > implements Shrinkable <T > {
15
+ protected final Shrinkable <T > base ;
16
+ private final ShrinkingDistance distance ;
17
+ final T value ;
18
+
19
+ ShrinkableWithEagerValue (Shrinkable <T > base ) {
20
+ this .base = base ;
21
+ this .distance = base .distance ();
22
+ this .value = base .value ();
23
+ }
24
+
25
+ @ Override
26
+ public T value () {
27
+ return value ;
28
+ }
29
+
30
+ @ Override
31
+ public Stream <Shrinkable <T >> shrink () {
32
+ return base .shrink ();
33
+ }
34
+
35
+ @ Override
36
+ public ShrinkingDistance distance () {
37
+ return distance ;
38
+ }
39
+ }
40
+
41
+ static class EagerShrinkable <T > extends ShrinkableWithEagerValue <T > {
42
+ private final List <Shrinkable <T >> shrinkResults ;
43
+
44
+ EagerShrinkable (Shrinkable <T > base , int numSamples ) {
45
+ super (base );
46
+ this .shrinkResults =
47
+ base .shrink ()
48
+ .sorted (Comparator .comparing (Shrinkable ::distance ))
49
+ .limit (numSamples )
50
+ .map (ShrinkableWithEagerValue ::new )
51
+ .collect (Collectors .toList ());
52
+ }
53
+
54
+ @ Override
55
+ public Stream <Shrinkable <T >> shrink () {
56
+ return shrinkResults .stream ();
57
+ }
58
+ }
59
+
12
60
final Shrinkable <Transformer <T >> shrinkable ;
13
61
private final Predicate <T > precondition ;
14
- private final @ Nullable Transformer <T > transformer ;
15
62
final boolean accessState ;
16
63
final boolean changeState ;
17
64
@@ -33,10 +80,17 @@ private ShrinkableChainIteration(
33
80
this .precondition = precondition ;
34
81
this .accessState = accessState ;
35
82
this .changeState = changeState ;
36
- this .shrinkable = shrinkable ;
37
- // Transformer method might access state, so we need to cache the value here
38
- // otherwise it might be evaluated with wrong state (e.g. after chain executes)
39
- this .transformer = accessState ? shrinkable .value () : null ;
83
+ // When the shrinkable does not access state, we could just use it as is for ".value()", and ".shrink()"
84
+ // If we get LazyShrinkable here, it means we are in a shrinking phase, so we know ".shrink()" will be called only
85
+ // in case the subsequent execution fails. So we can just keep LazyShrinkable as is
86
+ // Otherwise, we need to eagerly evaluate the shrinkables to since the state might change by appyling subsequent transformers,
87
+ // so we won't be able to access the state anymore.
88
+ // See https://github.com/jlink/jqwik/issues/428
89
+ if (!accessState || shrinkable instanceof ShrinkableChainIteration .ShrinkableWithEagerValue ) {
90
+ this .shrinkable = shrinkable ;
91
+ } else {
92
+ this .shrinkable = new EagerShrinkable <>(shrinkable , NUM_SAMPLES_IN_EAGER_CHAIN_SHRINK );
93
+ }
40
94
}
41
95
42
96
@ Override
@@ -71,6 +125,6 @@ String transformation() {
71
125
}
72
126
73
127
Transformer <T > transformer () {
74
- return transformer == null ? shrinkable .value () : transformer ;
128
+ return shrinkable .value ();
75
129
}
76
130
}
0 commit comments