@@ -5,15 +5,15 @@ open System
55
66[<Struct>]
77type Property < 'a > =
8- | Property of Gen < Journal * Outcome < 'a >>
8+ | Property of Gen < Lazy < Journal * Outcome < 'a > >>
99
1010
1111module Property =
1212
13- let ofGen ( x : Gen < Journal * Outcome < 'a >>) : Property < 'a > =
13+ let ofGen ( x : Gen < Lazy < Journal * Outcome < 'a > >>) : Property < 'a > =
1414 Property x
1515
16- let toGen ( Property x : Property < 'a >) : Gen < Journal * Outcome < 'a >> =
16+ let toGen ( Property x : Property < 'a >) : Gen < Lazy < Journal * Outcome < 'a > >> =
1717 x
1818
1919 let tryFinally ( after : unit -> unit ) ( m : Property < 'a >) : Property < 'a > =
@@ -37,10 +37,10 @@ module Property =
3737 x.Dispose ())
3838
3939 let filter ( p : 'a -> bool ) ( m : Property < 'a >) : Property < 'a > =
40- GenTuple .mapSnd ( Outcome.filter p) ( toGen m ) |> ofGen
40+ m |> toGen |> GenLazyTuple .mapSnd ( Outcome.filter p) |> ofGen
4141
4242 let ofOutcome ( x : Outcome < 'a >) : Property < 'a > =
43- ( Journal.empty, x) |> Gen .constant |> ofGen
43+ ( Journal.empty, x) |> GenLazy .constant |> ofGen
4444
4545 let failure : Property < unit > =
4646 Failure |> ofOutcome
@@ -58,43 +58,37 @@ module Property =
5858 failure
5959
6060 let counterexample ( msg : unit -> string ) : Property < unit > =
61- ( Journal.singleton msg, Success ()) |> Gen.constant |> ofGen
62-
63- let private mapGen
64- ( f : Gen < Journal * Outcome < 'a >> -> Gen < Journal * Outcome < 'b >>)
65- ( p : Property < 'a >) : Property < 'b > =
66- p |> toGen |> f |> ofGen
61+ ( Journal.singleton msg, Success ()) |> GenLazy.constant |> ofGen
6762
6863 let map ( f : 'a -> 'b ) ( x : Property < 'a >) : Property < 'b > =
6964 let g ( j , outcome ) =
7065 try
7166 ( j, outcome |> Outcome.map f)
7267 with e ->
7368 ( Journal.append j ( Journal.singletonMessage ( string e)), Failure)
74- let h = g |> Gen.map |> mapGen
75- h x
69+ x |> toGen |> GenLazy.map g |> ofGen
7670
7771 let internal set ( a : 'a ) ( property : Property < 'b >) : Property < 'a > =
7872 property |> map ( fun _ -> a)
7973
8074 let private bindGen
81- ( k : 'a -> Gen < Journal * Outcome < 'b >>)
82- ( m : Gen < Journal * Outcome < 'a >>) : Gen < Journal * Outcome < 'b >> =
83- m |> Gen .bind ( fun ( journal , result ) ->
75+ ( f : 'a -> Gen < Lazy < Journal * Outcome < 'b > >>)
76+ ( m : Gen < Lazy < Journal * Outcome < 'a >>> ) : Gen < Lazy < Journal * Outcome < 'b > >> =
77+ m |> GenLazy .bind ( fun ( journal , result ) ->
8478 match result with
8579 | Failure ->
86- Gen .constant ( journal, Failure)
80+ GenLazy .constant ( journal, Failure)
8781 | Discard ->
88- Gen .constant ( journal, Discard)
89- | Success x ->
90- GenTuple .mapFst ( Journal.append journal) ( k x ))
82+ GenLazy .constant ( journal, Discard)
83+ | Success a ->
84+ GenLazyTuple .mapFst ( Journal.append journal) ( f a ))
9185
9286 let bind ( k : 'a -> Property < 'b >) ( m : Property < 'a >) : Property < 'b > =
9387 let kTry a =
9488 try
9589 k a |> toGen
9690 with e ->
97- ( Journal.singletonMessage ( string e), Failure) |> Gen .constant
91+ ( Journal.singletonMessage ( string e), Failure) |> GenLazy .constant
9892 m
9993 |> toGen
10094 |> bindGen kTry
@@ -140,27 +134,51 @@ module Property =
140134 //
141135
142136 let private shrinkInput
143- ( language : Language option )
144- ( recheckData : RecheckData )
137+ ( language : Language )
138+ ( data : RecheckData )
145139 ( shrinkLimit : int < shrinks > Option ) =
146140 let rec loop
147141 ( nshrinks : int < shrinks >)
148- ( Node (( journal , _ ), xs ) : Tree < Journal * Outcome < 'a >>) =
142+ ( shrinkPath : ShrinkOutcome list )
143+ ( Node ( root , xs ) : Tree < Lazy < Journal * Outcome < 'a >>>) =
144+ let journal = root.Value |> fst
145+ let recheckData = { data with ShrinkPath = shrinkPath }
149146 let failed =
150147 Failed {
151148 Shrinks = nshrinks
152149 Journal = journal
153- RecheckInfo = language |> Option.map ( fun lang -> { Language = lang; Data = recheckData })
154- }
155- match shrinkLimit, Seq.tryFind ( Tree.outcome >> snd >> Outcome.isFailure) xs with
150+ RecheckInfo =
151+ Some { Language = language
152+ Data = recheckData } }
153+ match shrinkLimit, xs |> Seq.indexed |> Seq.tryFind ( snd >> Tree.outcome >> Lazy.value >> snd >> Outcome.isFailure) with
156154 | Some shrinkLimit', _ when nshrinks >= shrinkLimit' -> failed
157155 | _, None -> failed
158- | _, Some tree -> loop ( nshrinks + 1 < shrinks>) tree
159- loop 0 < shrinks>
156+ | _, Some ( idx, tree) ->
157+ let nextShrinkPath = shrinkPath @ List.replicate idx ShrinkOutcome.Pass @ [ ShrinkOutcome.Fail]
158+ loop ( nshrinks + 1 < shrinks>) nextShrinkPath tree
159+ loop 0 < shrinks> []
160+
161+ let rec private followShrinkPath
162+ ( Node ( root , children ) : Tree < Lazy < Journal * Outcome < 'a >>>) =
163+ let rec skipPassedChild children shrinkPath =
164+ match children, shrinkPath with
165+ | _, [] ->
166+ Failed {
167+ Shrinks = 0 < shrinks>
168+ Journal = root.Value |> fst
169+ RecheckInfo = None
170+ }
171+ | [], _ -> failwith " The shrink path lead to a dead end. This should never happen."
172+ | _ :: childrenTail, ShrinkOutcome.Pass :: shrinkPathTail -> skipPassedChild childrenTail shrinkPathTail
173+ | childrenHead :: _, ShrinkOutcome.Fail :: shrinkPathTail -> followShrinkPath childrenHead shrinkPathTail
174+ skipPassedChild ( Seq.toList children)
160175
161- let private reportWith ' ( args : PropertyArgs ) ( config : PropertyConfig ) ( p : Property < unit >) : Report =
162- let random = toGen p |> Gen.toRandom
176+ let private splitAndRun p data =
177+ let seed1 , seed2 = Seed.split data.Seed
178+ let result = p |> toGen |> Gen.toRandom |> Random.run seed1 data.Size
179+ result, seed2
163180
181+ let private reportWith ' ( args : PropertyArgs ) ( config : PropertyConfig ) ( p : Property < unit >) : Report =
164182 let nextSize size =
165183 if size >= 100 then
166184 1
@@ -177,15 +195,14 @@ module Property =
177195 Discards = discards
178196 Status = GaveUp }
179197 else
180- let seed1 , seed2 = Seed.split data.Seed
181- let result = Random.run seed1 data.Size random
198+ let result , seed2 = splitAndRun p data
182199 let nextData = {
183200 data with
184201 Seed = seed2
185202 Size = nextSize data.Size
186203 }
187204
188- match snd ( Tree.outcome result) with
205+ match snd ( Tree.outcome result) .Value with
189206 | Failure ->
190207 { Tests = tests + 1 < tests>
191208 Discards = discards
@@ -222,12 +239,11 @@ module Property =
222239 g |> falseToFailure |> checkWith config
223240
224241 let reportRecheckWith ( recheckData : string ) ( config : PropertyConfig ) ( p : Property < unit >) : Report =
225- let args = {
226- PropertyArgs.init with
227- Language = None
228- RecheckData = recheckData |> RecheckData.deserialize
229- }
230- p |> reportWith' args config
242+ let recheckData = recheckData |> RecheckData.deserialize
243+ let result , _ = splitAndRun p recheckData
244+ { Tests = 1 < tests>
245+ Discards = 0 < discards>
246+ Status = followShrinkPath result recheckData.ShrinkPath }
231247
232248 let reportRecheck ( recheckData : string ) ( p : Property < unit >) : Report =
233249 p |> reportRecheckWith recheckData PropertyConfig.defaultConfig
@@ -310,7 +326,7 @@ module PropertyBuilder =
310326
311327 member __.BindReturn ( m : Gen < 'a >, f : 'a -> 'b ) =
312328 m
313- |> Gen.map ( fun a -> ( Journal.empty, Success a))
329+ |> Gen.map ( fun a -> Lazy.constant ( Journal.empty, Success a))
314330 |> Property.ofGen
315331 |> Property.map f
316332
0 commit comments