Skip to content

Commit 9604f9d

Browse files
test: add SideEffects module to TaskSeq.FirstLastDefault.Tests.fs
Add SideEffects tests for firstOrDefault and lastOrDefault, covering: - firstOrDefault stops consuming after the first element (matches head behaviour) - firstOrDefault executes side effects that occur before the first yield - lastOrDefault fully drains the sequence (matches last/tryLast behaviour) - TestSideEffectTaskSeq theory variants for both functions 58 tests now pass for the FirstLastDefault suite (19 new). Co-authored-by: Copilot <[email protected]>
1 parent fc687a3 commit 9604f9d

1 file changed

Lines changed: 78 additions & 0 deletions

File tree

src/FSharp.Control.TaskSeq.Test/TaskSeq.FirstLastDefault.Tests.fs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,81 @@ module Immutable =
9191
let! result = TaskSeq.singleton 42 |> TaskSeq.lastOrDefault 0
9292
result |> should equal 42
9393
}
94+
95+
96+
module SideEffects =
97+
[<Fact>]
98+
let ``TaskSeq-firstOrDefault __special-case__ prove it does not read beyond first yield`` () = task {
99+
let mutable x = 42
100+
101+
let ts = taskSeq {
102+
yield x
103+
x <- x + 1 // we never get here
104+
}
105+
106+
let! fortyTwo = ts |> TaskSeq.firstOrDefault 0
107+
let! stillFortyTwo = ts |> TaskSeq.firstOrDefault 0 // the statement after 'yield' will never be reached
108+
109+
fortyTwo |> should equal 42
110+
stillFortyTwo |> should equal 42
111+
}
112+
113+
[<Fact>]
114+
let ``TaskSeq-firstOrDefault __special-case__ prove early side effect is executed`` () = task {
115+
let mutable x = 42
116+
117+
let ts = taskSeq {
118+
x <- x + 1
119+
x <- x + 1
120+
yield 42
121+
x <- x + 200 // we won't get here!
122+
}
123+
124+
let! result = ts |> TaskSeq.firstOrDefault 0
125+
result |> should equal 42
126+
x |> should equal 44
127+
128+
let! result = ts |> TaskSeq.firstOrDefault 0
129+
result |> should equal 42
130+
x |> should equal 46
131+
}
132+
133+
[<Fact>]
134+
let ``TaskSeq-lastOrDefault __special-case__ prove it reads the entire sequence`` () = task {
135+
let mutable x = 42
136+
137+
let ts = taskSeq {
138+
yield x
139+
x <- x + 1 // will be executed
140+
yield x
141+
x <- x + 1 // will be executed
142+
}
143+
144+
let! result = ts |> TaskSeq.lastOrDefault -1
145+
result |> should equal 43
146+
x |> should equal 44
147+
}
148+
149+
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
150+
let ``TaskSeq-firstOrDefault returns first item in a side-effect sequence`` variant = task {
151+
let ts = Gen.getSeqWithSideEffect variant
152+
153+
let! first = ts |> TaskSeq.firstOrDefault 0
154+
first |> should equal 1
155+
156+
// side effect: re-enumerating changes the first item
157+
let! secondFirst = ts |> TaskSeq.firstOrDefault 0
158+
secondFirst |> should not' (equal 1)
159+
}
160+
161+
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
162+
let ``TaskSeq-lastOrDefault returns last item and exhausts the sequence`` variant = task {
163+
let ts = Gen.getSeqWithSideEffect variant
164+
165+
let! last = ts |> TaskSeq.lastOrDefault 0
166+
last |> should equal 10
167+
168+
// side effect: re-enumerating continues from mutated state
169+
let! secondLast = ts |> TaskSeq.lastOrDefault 0
170+
secondLast |> should equal 20
171+
}

0 commit comments

Comments
 (0)