-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
…ng too far
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,28 +16,14 @@ open FSharp.Control | |
|
||
[<AutoOpen>] | ||
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 | ||
| false, true -> fun pred -> TaskSeq.skipWhileAsync (pred >> Task.fromResult) | ||
| 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 = | |
|
||
[<Theory; ClassData(typeof<TestImmTaskSeq>)>] | ||
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 = | |
|
||
[<Theory; ClassData(typeof<TestImmTaskSeq>)>] | ||
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 = | ||
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>] | ||
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" | ||
} | ||
|
||
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>] | ||
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" | ||
} | ||
|
||
[<Theory>] | ||
[<InlineData(false, false)>] | ||
|
@@ -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 | ||
Check failure on line 200 in src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs GitHub Actions / Report release testsTaskSeq.Tests.skipWhile+SideEffects ► TaskSeq-skipWhileXXX prove it does not read beyond the failing yield(inclusive: False, isAsync: False)
Raw output
Check failure on line 200 in src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs GitHub Actions / Report release testsTaskSeq.Tests.skipWhile+SideEffects ► TaskSeq-skipWhileXXX prove it does not read beyond the failing yield(inclusive: False, isAsync: True)
Raw output
Check failure on line 200 in src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs GitHub Actions / Report release testsTaskSeq.Tests.skipWhile+SideEffects ► TaskSeq-skipWhileXXX prove it does not read beyond the failing yield(inclusive: True, isAsync: False)
Raw output
Check failure on line 200 in src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs GitHub Actions / Report release testsTaskSeq.Tests.skipWhile+SideEffects ► TaskSeq-skipWhileXXX prove it does not read beyond the failing yield(inclusive: True, isAsync: True)
Raw output
Check failure on line 200 in src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs GitHub Actions / Report debug testsTaskSeq.Tests.skipWhile+SideEffects ► TaskSeq-skipWhileXXX prove it does not read beyond the failing yield(inclusive: False, isAsync: False)
Raw output
Check failure on line 200 in src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs GitHub Actions / Report debug testsTaskSeq.Tests.skipWhile+SideEffects ► TaskSeq-skipWhileXXX prove it does not read beyond the failing yield(inclusive: False, isAsync: True)
Raw output
Check failure on line 200 in src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs GitHub Actions / Report debug testsTaskSeq.Tests.skipWhile+SideEffects ► TaskSeq-skipWhileXXX prove it does not read beyond the failing yield(inclusive: True, isAsync: False)
Raw output
Check failure on line 200 in src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs GitHub Actions / Report debug testsTaskSeq.Tests.skipWhile+SideEffects ► TaskSeq-skipWhileXXX prove it does not read beyond the failing yield(inclusive: True, isAsync: True)
Raw output
|
||
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 | ||
} | ||
|
||
[<Theory>] | ||
|
@@ -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 | ||
Check failure on line 231 in src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs GitHub Actions / Report release testsTaskSeq.Tests.skipWhile+SideEffects ► TaskSeq-skipWhileXXX prove side effects are executed(inclusive: False, isAsync: False)
Raw output
Check failure on line 231 in src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs GitHub Actions / Report release testsTaskSeq.Tests.skipWhile+SideEffects ► TaskSeq-skipWhileXXX prove side effects are executed(inclusive: False, isAsync: True)
Raw output
Check failure on line 231 in src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs GitHub Actions / Report release testsTaskSeq.Tests.skipWhile+SideEffects ► TaskSeq-skipWhileXXX prove side effects are executed(inclusive: True, isAsync: False)
Raw output
Check failure on line 231 in src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs GitHub Actions / Report debug testsTaskSeq.Tests.skipWhile+SideEffects ► TaskSeq-skipWhileXXX prove side effects are executed(inclusive: False, isAsync: False)
Raw output
Check failure on line 231 in src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs GitHub Actions / Report debug testsTaskSeq.Tests.skipWhile+SideEffects ► TaskSeq-skipWhileXXX prove side effects are executed(inclusive: False, isAsync: True)
Raw output
Check failure on line 231 in src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs GitHub Actions / Report debug testsTaskSeq.Tests.skipWhile+SideEffects ► TaskSeq-skipWhileXXX prove side effects are executed(inclusive: True, isAsync: False)
Raw output
|
||
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 = | |
[<InlineData(false, true)>] | ||
[<InlineData(true, false)>] | ||
[<InlineData(true, true)>] | ||
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" | ||
} | ||
|
||
[<Theory>] | ||
[<InlineData(false, false)>] | ||
[<InlineData(false, true)>] | ||
[<InlineData(true, false)>] | ||
[<InlineData(true, true)>] | ||
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" | ||
} |