Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions release-notes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
175 changes: 175 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Fold.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,181 @@ module EmptySeq =
alphabet |> should equal '_'
}

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
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
}

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
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 =
[<Fact>]
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
}

[<Fact>]
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
}

[<Fact>]
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
}

[<Fact>]
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
}

[<Fact>]
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"
}

[<Fact>]
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"
}

[<Fact>]
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"
}

[<Fact>]
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
}

[<Fact>]
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 ]
}

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
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
}

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
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 =
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-fold folds with every item`` variant = task {
Expand Down
Loading