diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs index 899043f4..f1833c9b 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs @@ -16,8 +16,7 @@ open FSharp.Control [] module With = - /// The only real difference in semantics between the base and the *Inclusive variant lies in whether the final item is returned. - /// NOTE the semantics are very clear on only propagating a single failing item in the inclusive case. + /// The only real difference in semantics between the base and the *Inclusive variant lies in whether the final item is skipped. let getFunction inclusive isAsync = match inclusive, isAsync with | false, false -> TaskSeq.skipWhile @@ -25,19 +24,6 @@ module With = | true, false -> TaskSeq.skipWhileInclusive | true, true -> fun pred -> TaskSeq.skipWhileInclusiveAsync (pred >> Task.fromResult) - /// This is the base condition as one would expect in actual code - let inline cond x = x <> 6 - - /// For each of the tests below, we add a guard that will trigger if the predicate is passed items known to be beyond the - /// first failing item in the known sequence (which is 1..10) - let inline condWithGuard x = - let res = cond x - - if x > 6 then - failwith "Test sequence should not be enumerated beyond the first item failing the predicate" - - res - module EmptySeq = // TaskSeq-skipWhile+A stands for: @@ -76,6 +62,12 @@ module Immutable = [)>] let ``TaskSeq-skipWhile+A filters correctly`` variant = task { + // truth table for f(x) = x < 5 + // 1 2 3 4 5 6 7 8 9 10 + // T T T T F F F F F F (stops at first F) + // x x x x _ _ _ _ _ _ (skips exclusive) + // A B C D E F G H I J + do! Gen.getSeqImmutable variant |> TaskSeq.skipWhile ((>) 5) // skip while less than 5 @@ -102,15 +94,21 @@ module Immutable = [)>] let ``TaskSeq-skipWhileInclusive+A filters correctly`` variant = task { + // truth table for f(x) = x < 5 + // 1 2 3 4 5 6 7 8 9 10 + // T T T T F F F F F F (stops at first F) + // x x x x x _ _ _ _ _ (skips inclusively) + // A B C D E F G H I J + do! Gen.getSeqImmutable variant |> TaskSeq.skipWhileInclusive ((>) 5) - |> verifyDigitsAsString "GHIJ" // last 4 + |> verifyDigitsAsString "FGHIJ" // last 4 do! Gen.getSeqImmutable variant |> TaskSeq.skipWhileInclusiveAsync (fun x -> task { return x < 5 }) - |> verifyDigitsAsString "GHIJ" + |> verifyDigitsAsString "FGHIJ" } @@ -142,16 +140,42 @@ module Immutable = module SideEffects = [)>] - let ``TaskSeq-skipWhile filters correctly`` variant = - Gen.getSeqWithSideEffect variant - |> TaskSeq.skipWhile condWithGuard - |> verifyDigitsAsString "ABCDE" + let ``TaskSeq-skipWhile+A filters correctly`` variant = task { + // truth table for f(x) = x < 6 + // 1 2 3 4 5 6 7 8 9 10 + // T T T T T F F F F F (stops at first F) + // x x x x x _ _ _ _ _ (skips exclusively) + // A B C D E F G H I J + + do! + Gen.getSeqWithSideEffect variant + |> TaskSeq.skipWhile ((>) 6) + |> verifyDigitsAsString "FGHIJ" + + do! + Gen.getSeqWithSideEffect variant + |> TaskSeq.skipWhileAsync (fun x -> task { return x < 6 }) + |> verifyDigitsAsString "FGHIJ" + } [)>] - let ``TaskSeq-skipWhileAsync filters correctly`` variant = - Gen.getSeqWithSideEffect variant - |> TaskSeq.skipWhileAsync (fun x -> task { return condWithGuard x }) - |> verifyDigitsAsString "ABCDE" + let ``TaskSeq-skipWhileInclusive+A filters correctly`` variant = task { + // truth table for f(x) = x < 6 + // 1 2 3 4 5 6 7 8 9 10 + // T T T T T F F F F F (stops at first F) + // x x x x x x _ _ _ _ (skips inclusively) + // A B C D E F G H I J + + do! + Gen.getSeqWithSideEffect variant + |> TaskSeq.skipWhileInclusive ((>) 6) + |> verifyDigitsAsString "GHIJ" + + do! + Gen.getSeqWithSideEffect variant + |> TaskSeq.skipWhileInclusiveAsync (fun x -> task { return x < 6 }) + |> verifyDigitsAsString "GHIJ" + } [] [] @@ -163,19 +187,23 @@ module SideEffects = let functionToTest = getFunction inclusive isAsync ((=) 42) let items = taskSeq { - yield x // Always passes the test; always returned - yield x * 2 // the failing item (which will also be yielded in the result when using *Inclusive) + yield x // Always passes the test; always skipped + yield x * 2 // Fails the test, skipped depending on "inclusive" x <- x + 1 // we are proving we never get here } - let expected = if inclusive then [| 42; 84 |] else [| 42 |] + // we skip one more if "inclusive" + let expected = if inclusive then [||] else [| 84 |] + x |> should equal 42 let! first = items |> functionToTest |> TaskSeq.toArrayAsync + x |> should equal 42 let! repeat = items |> functionToTest |> TaskSeq.toArrayAsync + x |> should equal 42 first |> should equal expected repeat |> should equal expected - x |> should equal 42 + x |> should equal 42 // if the var changed, we got too far } [] @@ -195,9 +223,10 @@ module SideEffects = x <- x + 200 // as previously proven, we should not trigger this } - let expectedFirst = if inclusive then [| 42; 44 * 2 |] else [| 42 |] - let expectedRepeat = if inclusive then [| 45; 47 * 2 |] else [| 45 |] + let expectedFirst = if inclusive then [||] else [| 44 * 2 |] + let expectedRepeat = if inclusive then [||] else [| 47 * 2 |] + x |> should equal 41 let! first = items |> functionToTest |> TaskSeq.toArrayAsync x |> should equal 44 let! repeat = items |> functionToTest |> TaskSeq.toArrayAsync @@ -215,10 +244,11 @@ module SideEffects = TaskSeq.skipWhile (fun x -> x < 5) ts |> TaskSeq.toArrayAsync - let expected = [| 1..4 |] + let expected = [| 5..10 |] first |> should equal expected // side effect, reiterating causes it to resume from where we left it (minus the failing item) + // which means the original sequence has now changed due to the side effect let! repeat = TaskSeq.skipWhile (fun x -> x < 5) ts |> TaskSeq.toArrayAsync @@ -234,10 +264,11 @@ module SideEffects = TaskSeq.skipWhileInclusiveAsync (fun x -> task { return x < 5 }) ts |> TaskSeq.toArrayAsync - let expected = [| 1..5 |] + let expected = [| 6..10 |] first |> should equal expected // side effect, reiterating causes it to resume from where we left it (minus the failing item) + // which means the original sequence has now changed due to the side effect let! repeat = TaskSeq.skipWhileInclusiveAsync (fun x -> task { return x < 5 }) ts |> TaskSeq.toArrayAsync @@ -251,26 +282,71 @@ module Other = [] [] [] - let ``TaskSeq-skipWhileXXX exclude all items after predicate fails`` (inclusive, isAsync) = - let functionToTest = With.getFunction inclusive isAsync + let ``TaskSeq-skipWhileXXX should include all items after predicate fails`` (inclusive, isAsync) = task { + do! + [ 1; 2; 2; 3; 3; 2; 1 ] + |> TaskSeq.ofSeq + |> TaskSeq.skipWhile (fun x -> x <= 2) + |> verifyDigitsAsString "CCBA" + + do! + [ 1; 2; 2; 3; 3; 2; 1 ] + |> TaskSeq.ofSeq + |> TaskSeq.skipWhileInclusive (fun x -> x <= 2) + |> verifyDigitsAsString "CBA" + + do! + [ 1; 2; 2; 3; 3; 2; 1 ] + |> TaskSeq.ofSeq + |> TaskSeq.skipWhileAsync (fun x -> Task.fromResult (x <= 2)) + |> verifyDigitsAsString "CCBA" - [ 1; 2; 2; 3; 3; 2; 1 ] - |> TaskSeq.ofSeq - |> functionToTest (fun x -> x <= 2) - |> verifyDigitsAsString (if inclusive then "ABBC" else "ABB") + do! + [ 1; 2; 2; 3; 3; 2; 1 ] + |> TaskSeq.ofSeq + |> TaskSeq.skipWhileInclusiveAsync (fun x -> Task.fromResult (x <= 2)) + |> verifyDigitsAsString "CBA" + } [] [] [] [] [] - let ``TaskSeq-skipWhileXXX stops consuming after predicate fails`` (inclusive, isAsync) = - let functionToTest = With.getFunction inclusive isAsync + let ``TaskSeq-skipWhileXXX stops consuming after predicate fails`` (inclusive, isAsync) = task { + do! + seq { + yield! [ 1; 2; 2; 3; 3 ] + yield failwith "Too far" + } + |> TaskSeq.ofSeq + |> TaskSeq.skipWhile (fun x -> x <= 2) + |> verifyDigitsAsString "CC" - seq { - yield! [ 1; 2; 2; 3; 3 ] - yield failwith "Too far" - } - |> TaskSeq.ofSeq - |> functionToTest (fun x -> x <= 2) - |> verifyDigitsAsString (if inclusive then "ABBC" else "ABB") + do! + seq { + yield! [ 1; 2; 2; 3; 3 ] + yield failwith "Too far" + } + |> TaskSeq.ofSeq + |> TaskSeq.skipWhileInclusive (fun x -> x <= 2) + |> verifyDigitsAsString "C" + + do! + seq { + yield! [ 1; 2; 2; 3; 3 ] + yield failwith "Too far" + } + |> TaskSeq.ofSeq + |> TaskSeq.skipWhileAsync (fun x -> Task.fromResult (x <= 2)) + |> verifyDigitsAsString "CC" + + do! + seq { + yield! [ 1; 2; 2; 3; 3 ] + yield failwith "Too far" + } + |> TaskSeq.ofSeq + |> TaskSeq.skipWhileInclusiveAsync (fun x -> Task.fromResult (x <= 2)) + |> verifyDigitsAsString "C" + }