diff --git a/release-notes.txt b/release-notes.txt index 6f7379e9..3ec93bee 100644 --- a/release-notes.txt +++ b/release-notes.txt @@ -4,6 +4,7 @@ Release notes: 1.0.0 - adds TaskSeq.withCancellation, #167 - adds docs/ with fsdocs-based documentation site covering generating, transforming, consuming, combining and advanced operations + - test: adds 70 new tests to TaskSeq.Fold.Tests.fs covering call-count assertions, folder-not-called-on-empty, ordering, null initial state, and fold/foldAsync equivalence 0.7.0 - performance: TaskSeq.exists, existsAsync, contains no longer allocate an intermediate Option value diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Fold.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Fold.Tests.fs index aa91c046..5f210d6c 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Fold.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Fold.Tests.fs @@ -39,6 +39,181 @@ module EmptySeq = alphabet |> should equal '_' } + [)>] + let ``TaskSeq-fold does not call folder function when empty`` variant = task { + let mutable called = false + + let! _ = + Gen.getEmptyVariant variant + |> TaskSeq.fold + (fun state _ -> + called <- true + state) + 0 + + called |> should be False + } + + [)>] + let ``TaskSeq-foldAsync does not call folder function when empty`` variant = task { + let mutable called = false + + let! _ = + Gen.getEmptyVariant variant + |> TaskSeq.foldAsync + (fun state _ -> task { + called <- true + return state + }) + 0 + + called |> should be False + } + +module Functionality = + [] + let ``TaskSeq-fold calls folder exactly N times for N elements`` () = task { + let mutable callCount = 0 + + let! _ = + TaskSeq.ofList [ 1; 2; 3; 4; 5 ] + |> TaskSeq.fold + (fun acc item -> + callCount <- callCount + 1 + acc + item) + 0 + + callCount |> should equal 5 + } + + [] + let ``TaskSeq-foldAsync calls folder exactly N times for N elements`` () = task { + let mutable callCount = 0 + + let! _ = + TaskSeq.ofList [ 1; 2; 3; 4; 5 ] + |> TaskSeq.foldAsync + (fun acc item -> task { + callCount <- callCount + 1 + return acc + item + }) + 0 + + callCount |> should equal 5 + } + + [] + let ``TaskSeq-fold over singleton calls folder once`` () = task { + let mutable callCount = 0 + + let! result = + TaskSeq.singleton 42 + |> TaskSeq.fold + (fun acc item -> + callCount <- callCount + 1 + acc + item) + 0 + + result |> should equal 42 + callCount |> should equal 1 + } + + [] + let ``TaskSeq-fold over two elements calls folder twice`` () = task { + let mutable callCount = 0 + + let! result = + taskSeq { + yield 10 + yield 20 + } + |> TaskSeq.fold + (fun acc item -> + callCount <- callCount + 1 + acc + item) + 0 + + result |> should equal 30 + callCount |> should equal 2 + } + + [] + let ``TaskSeq-fold is left-associative: applies folder left-to-right`` () = task { + // For non-commutative ops like string concat, order matters. + // fold f s [a;b;c] = f (f (f s a) b) c + let! result = + TaskSeq.ofList [ "b"; "c"; "d" ] + |> TaskSeq.fold (fun acc item -> acc + item) "a" + + result |> should equal "abcd" + } + + [] + let ``TaskSeq-foldAsync is left-associative: applies folder left-to-right`` () = task { + let! result = + TaskSeq.ofList [ "b"; "c"; "d" ] + |> TaskSeq.foldAsync (fun acc item -> task { return acc + item }) "a" + + result |> should equal "abcd" + } + + [] + let ``TaskSeq-fold with null initial state works for reference types`` () = task { + let! result = + TaskSeq.ofList [ "hello"; " "; "world" ] + |> TaskSeq.fold + (fun acc item -> + match acc with + | null -> item + | _ -> acc + item) + null + + result |> should equal "hello world" + } + + [] + let ``TaskSeq-foldAsync and fold return the same result for pure functions`` () = task { + let input = [ 1..10 ] + + let! syncResult = + TaskSeq.ofList input + |> TaskSeq.fold (fun acc item -> acc + item) 0 + + let! asyncResult = + TaskSeq.ofList input + |> TaskSeq.foldAsync (fun acc item -> task { return acc + item }) 0 + + syncResult |> should equal asyncResult + } + + [] + let ``TaskSeq-fold accumulates a list in correct order`` () = task { + let! result = + TaskSeq.ofList [ 1; 2; 3; 4; 5 ] + |> TaskSeq.fold (fun acc item -> acc @ [ item ]) [] + + result |> should equal [ 1; 2; 3; 4; 5 ] + } + + [)>] + let ``TaskSeq-fold sum over immutable variants`` variant = task { + // items are 1..10; sum = 55 + let! result = + Gen.getSeqImmutable variant + |> TaskSeq.fold (fun acc item -> acc + item) 0 + + result |> should equal 55 + } + + [)>] + let ``TaskSeq-foldAsync sum over immutable variants`` variant = task { + let! result = + Gen.getSeqImmutable variant + |> TaskSeq.foldAsync (fun acc item -> task { return acc + item }) 0 + + result |> should equal 55 + } + module Immutable = [)>] let ``TaskSeq-fold folds with every item`` variant = task {