From 3a951bd55e6eb0f4c3a4ab3a6551260643dd8be1 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 17 Mar 2024 00:57:45 +0100 Subject: [PATCH] Add unit tests for `TaskSeq.forall` and `TaskSeq.forallAsync` --- .../FSharp.Control.TaskSeq.Test.fsproj | 1 + .../TaskSeq.Forall.Tests.fs | 228 ++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 src/FSharp.Control.TaskSeq.Test/TaskSeq.Forall.Tests.fs diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj index ff7ae5e4..d625d93a 100644 --- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj +++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj @@ -24,6 +24,7 @@ + diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Forall.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Forall.Tests.fs new file mode 100644 index 00000000..04a652f5 --- /dev/null +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Forall.Tests.fs @@ -0,0 +1,228 @@ +module TaskSeq.Tests.Exists + +open Xunit +open FsUnit.Xunit + +open FSharp.Control + +// +// TaskSeq.exists +// TaskSeq.existsAsyncc +// + +module EmptySeq = + [] + let ``Null source is invalid`` () = + assertNullArg + <| fun () -> TaskSeq.exists (fun _ -> false) null + + assertNullArg + <| fun () -> TaskSeq.existsAsync (fun _ -> Task.fromResult false) null + + [)>] + let ``TaskSeq-exists returns false`` variant = + Gen.getEmptyVariant variant + |> TaskSeq.exists ((=) 12) + |> Task.map (should be False) + + [)>] + let ``TaskSeq-existsAsync returns false`` variant = + Gen.getEmptyVariant variant + |> TaskSeq.existsAsync (fun x -> task { return x = 12 }) + |> Task.map (should be False) + +module Immutable = + [)>] + let ``TaskSeq-exists sad path returns false`` variant = + Gen.getSeqImmutable variant + |> TaskSeq.exists ((=) 0) + |> Task.map (should be False) + + [)>] + let ``TaskSeq-existsAsync sad path return false`` variant = + Gen.getSeqImmutable variant + |> TaskSeq.existsAsync (fun x -> task { return x = 0 }) + |> Task.map (should be False) + + [)>] + let ``TaskSeq-exists happy path middle of seq`` variant = + Gen.getSeqImmutable variant + |> TaskSeq.exists (fun x -> x < 6 && x > 4) + |> Task.map (should be True) + + [)>] + let ``TaskSeq-existsAsync happy path middle of seq`` variant = + Gen.getSeqImmutable variant + |> TaskSeq.existsAsync (fun x -> task { return x < 6 && x > 4 }) + |> Task.map (should be True) + + [)>] + let ``TaskSeq-exists happy path first item of seq`` variant = + Gen.getSeqImmutable variant + |> TaskSeq.exists ((=) 1) + |> Task.map (should be True) + + [)>] + let ``TaskSeq-existsAsync happy path first item of seq`` variant = + Gen.getSeqImmutable variant + |> TaskSeq.existsAsync (fun x -> task { return x = 1 }) + |> Task.map (should be True) + + [)>] + let ``TaskSeq-exists happy path last item of seq`` variant = + Gen.getSeqImmutable variant + |> TaskSeq.exists ((=) 10) + |> Task.map (should be True) + + [)>] + let ``TaskSeq-existsAsync happy path last item of seq`` variant = + Gen.getSeqImmutable variant + |> TaskSeq.existsAsync (fun x -> task { return x = 10 }) + |> Task.map (should be True) + +module SideEffects = + [)>] + let ``TaskSeq-exists KeyNotFoundException only sometimes for mutated state`` variant = task { + let ts = Gen.getSeqWithSideEffect variant + let finder = (=) 11 + + // first: false + let! found = TaskSeq.exists finder ts + found |> should be False + + // find again: found now, because of side effects + let! found = TaskSeq.exists finder ts + found |> should be True + + // find once more: false + let! found = TaskSeq.exists finder ts + found |> should be False + } + + [)>] + let ``TaskSeq-existsAsync KeyNotFoundException only sometimes for mutated state`` variant = task { + let ts = Gen.getSeqWithSideEffect variant + let finder x = task { return x = 11 } + + // first: false + let! found = TaskSeq.existsAsync finder ts + found |> should be False + + // find again: found now, because of side effects + let! found = TaskSeq.existsAsync finder ts + found |> should be True + + // find once more: false + let! found = TaskSeq.existsAsync finder ts + found |> should be False + } + + [] + let ``TaskSeq-exists _specialcase_ prove we don't read past the found item`` () = task { + let mutable i = 0 + + let ts = taskSeq { + for _ in 0..9 do + i <- i + 1 + yield i + } + + let! found = ts |> TaskSeq.exists ((=) 3) + found |> should be True + i |> should equal 3 // only partial evaluation! + + // find next item. We do get a new iterator, but mutable state is now starting at '3', so first item now returned is '4'. + let! found = ts |> TaskSeq.exists ((=) 4) + found |> should be True + i |> should equal 4 // only partial evaluation! + } + + [] + let ``TaskSeq-existsAsync _specialcase_ prove we don't read past the found item`` () = task { + let mutable i = 0 + + let ts = taskSeq { + for _ in 0..9 do + i <- i + 1 + yield i + } + + let! found = ts |> TaskSeq.existsAsync (fun x -> task { return x = 3 }) + found |> should be True + i |> should equal 3 // only partial evaluation! + + // find next item. We do get a new iterator, but mutable state is now starting at '3', so first item now returned is '4'. + let! found = ts |> TaskSeq.existsAsync (fun x -> task { return x = 4 }) + found |> should be True + i |> should equal 4 + } + + [] + let ``TaskSeq-exists _specialcase_ prove we don't read past the found item v2`` () = task { + let mutable i = 0 + + let ts = taskSeq { + yield 42 + i <- i + 1 + i <- i + 1 + } + + let! found = ts |> TaskSeq.exists ((=) 42) + found |> should be True + i |> should equal 0 // because no MoveNext after found item, the last statements are not executed + } + + [] + let ``TaskSeq-existsAsync _specialcase_ prove we don't read past the found item v2`` () = task { + let mutable i = 0 + + let ts = taskSeq { + yield 42 + i <- i + 1 + i <- i + 1 + } + + let! found = ts |> TaskSeq.existsAsync (fun x -> task { return x = 42 }) + found |> should be True + i |> should equal 0 // because no MoveNext after found item, the last statements are not executed + } + + [] + let ``TaskSeq-exists _specialcase_ prove statement after yield is not evaluated`` () = task { + let mutable i = 0 + + let ts = taskSeq { + for _ in 0..9 do + yield i + i <- i + 1 + } + + let! found = ts |> TaskSeq.exists ((=) 0) + found |> should be True + i |> should equal 0 // notice that it should be one higher if the statement after 'yield' is evaluated + + // find some next item. We do get a new iterator, but mutable state is now starting at '1' + let! found = ts |> TaskSeq.exists ((=) 4) + found |> should be True + i |> should equal 4 // only partial evaluation! + } + + [] + let ``TaskSeq-existsAsync _specialcase_ prove statement after yield is not evaluated`` () = task { + let mutable i = 0 + + let ts = taskSeq { + for _ in 0..9 do + yield i + i <- i + 1 + } + + let! found = ts |> TaskSeq.existsAsync (fun x -> task { return x = 0 }) + found |> should be True + i |> should equal 0 // notice that it should be one higher if the statement after 'yield' is evaluated + + // find some next item. We do get a new iterator, but mutable state is now starting at '1' + let! found = ts |> TaskSeq.existsAsync (fun x -> task { return x = 4 }) + found |> should be True + i |> should equal 4 // only partial evaluation! + }