diff --git a/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj b/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj
index 9883983b..d3e6fd2e 100644
--- a/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj
+++ b/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj
@@ -29,6 +29,7 @@
+
diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Realworld.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Realworld.fs
index 963181eb..1226460a 100644
--- a/src/FSharpy.TaskSeq.Test/TaskSeq.Realworld.fs
+++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Realworld.fs
@@ -22,8 +22,14 @@ type AsyncBufferedReader(output: ITestOutputHelper, data, blockSize) =
interface IAsyncEnumerable with
member reader.GetAsyncEnumerator(ct) =
- output.WriteLine $"Cloning!! Current: {current}, lastPos: {lastPos}"
- reader.MemberwiseClone() :?> IAsyncEnumerator<_>
+ { new IAsyncEnumerator<_> with
+ member this.Current = (reader :> IAsyncEnumerator<_>).Current
+ member this.MoveNextAsync() = (reader :> IAsyncEnumerator<_>).MoveNextAsync()
+ interface IAsyncDisposable with
+ member this.DisposeAsync() = ValueTask()
+ }
+ //output.WriteLine $"Cloning!! Current: {current}, lastPos: {lastPos}"
+ //reader.MemberwiseClone() :?> IAsyncEnumerator<_>
interface IAsyncEnumerator with
member _.Current =
@@ -39,6 +45,8 @@ type AsyncBufferedReader(output: ITestOutputHelper, data, blockSize) =
let! bytesRead = buffered.ReadAsync(mem, 0, mem.Length) // offset refers to offset in target buffer, not source
lastPos <- buffered.Position
+ let x: seq = seq { 1 } |> Seq.cast
+
if bytesRead > 0 then
current <- ValueSome mem
return true
@@ -48,7 +56,6 @@ type AsyncBufferedReader(output: ITestOutputHelper, data, blockSize) =
}
|> Task.toValueTask
- interface IAsyncDisposable with
member _.DisposeAsync() =
try
// this disposes of the mem stream
@@ -57,7 +64,43 @@ type AsyncBufferedReader(output: ITestOutputHelper, data, blockSize) =
// if the previous block raises, we should still try to get rid of the underlying stream
stream.DisposeAsync().AsTask().Wait()
+
type ``Real world tests``(output: ITestOutputHelper) =
+ []
+ let ``Reading a 10MB buffered IAsync through TaskSeq.toArray non-async should succeed`` () = task {
+ use reader = AsyncBufferedReader(output, Array.init 2048 byte, 256)
+ // unreadable error with 'use'
+ //use bla = seq { 1}
+ let expected = Array.init 256 byte |> Array.replicate 8
+ let results = reader |> TaskSeq.toArray
+
+ (results, expected)
+ ||> Array.iter2 (fun a b -> should equal a b)
+ }
+
+ []
+ let ``Reading a user-code IAsync multiple times with TaskSeq.toArrayAsync should succeed`` () = task {
+ use reader = AsyncBufferedReader(output, Array.init 2048 byte, 256)
+ let expected = Array.init 256 byte |> Array.replicate 8
+ // read four times
+ let! results1 = reader |> TaskSeq.toArrayAsync
+ let! results2 = reader |> TaskSeq.toArrayAsync
+ let! results3 = reader |> TaskSeq.toArrayAsync
+ let! results4 = reader |> TaskSeq.toArrayAsync
+
+ (results1, expected)
+ ||> Array.iter2 (fun a b -> should equal a b)
+
+ (results2, expected)
+ ||> Array.iter2 (fun a b -> should equal a b)
+
+ (results3, expected)
+ ||> Array.iter2 (fun a b -> should equal a b)
+
+ (results4, expected)
+ ||> Array.iter2 (fun a b -> should equal a b)
+ }
+
[]
let ``Reading a 10MB buffered IAsync stream from start to finish`` () = task {
let mutable count = 0
@@ -76,6 +119,8 @@ type ``Real world tests``(output: ITestOutputHelper) =
// the following is extremely slow, which is why we just use F#'s comparison instead
// Using this takes 67s, compared to 0.25s using normal F# comparison.
+ // reader |> TaskSeq.toArray |> should equal expected // VERY SLOW!!
+
do! reader |> TaskSeq.iter (should equal expected)
do! reader |> TaskSeq.iter ((=) expected >> (should be True))
let! len = reader |> TaskSeq.mapi (fun i _ -> i + 1) |> TaskSeq.last
diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.StateTransitionBug.Tests.CE.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.StateTransitionBug.Tests.CE.fs
new file mode 100644
index 00000000..25e2e413
--- /dev/null
+++ b/src/FSharpy.TaskSeq.Test/TaskSeq.StateTransitionBug.Tests.CE.fs
@@ -0,0 +1,155 @@
+module FSharpy.Tests.``State transition bug and InvalidState``
+
+open Xunit
+open FsUnit.Xunit
+open FsToolkit.ErrorHandling
+
+open FSharpy
+open System.Threading.Tasks
+open System.Diagnostics
+open System.Collections.Generic
+
+let getEmptyVariant variant : IAsyncEnumerable =
+ match variant with
+ | "do" -> taskSeq { do ignore () }
+ | "do!" -> taskSeq { do! task { return () } } // TODO: this doesn't work with Task, only Task...
+ | "yield! (seq)" -> taskSeq { yield! Seq.empty }
+ | "yield! (taskseq)" -> taskSeq { yield! taskSeq { do ignore () } }
+ | _ -> failwith "Uncovered variant of test"
+
+
+[]
+let ``CE empty taskSeq with MoveNextAsync -- untyped`` () = task {
+ let tskSeq = taskSeq { do ignore () }
+
+ Assert.IsAssignableFrom>(tskSeq)
+ |> ignore
+
+ let! noNext = tskSeq.GetAsyncEnumerator().MoveNextAsync()
+ noNext |> should be False
+}
+
+[]
+let ``CE empty taskSeq with MoveNextAsync -- typed`` variant = task {
+ let tskSeq = getEmptyVariant variant
+
+ Assert.IsAssignableFrom>(tskSeq)
+ |> ignore
+
+ let! noNext = tskSeq.GetAsyncEnumerator().MoveNextAsync()
+ noNext |> should be False
+}
+
+[]
+let ``CE empty taskSeq, GetAsyncEnumerator multiple times`` variant = task {
+ let tskSeq = getEmptyVariant variant
+ use enumerator = tskSeq.GetAsyncEnumerator()
+ use enumerator = tskSeq.GetAsyncEnumerator()
+ use enumerator = tskSeq.GetAsyncEnumerator()
+ ()
+}
+
+[]
+let ``CE empty taskSeq, GetAsyncEnumerator multiple times and then MoveNextAsync`` variant = task {
+ let tskSeq = getEmptyVariant variant
+ use enumerator = tskSeq.GetAsyncEnumerator()
+ use enumerator = tskSeq.GetAsyncEnumerator()
+ let! isNext = enumerator.MoveNextAsync()
+ ()
+}
+
+[]
+let ``CE empty taskSeq, GetAsyncEnumerator + MoveNextAsync multiple times`` variant = task {
+ let tskSeq = getEmptyVariant variant
+ use enumerator = tskSeq.GetAsyncEnumerator()
+ let! isNext = enumerator.MoveNextAsync()
+ use enumerator = tskSeq.GetAsyncEnumerator()
+ let! isNext = enumerator.MoveNextAsync()
+ ()
+}
+
+[]
+let ``CE empty taskSeq, GetAsyncEnumerator + MoveNextAsync in a loop`` variant = task {
+ let tskSeq = getEmptyVariant variant
+
+ // let's get the enumerator a few times
+ for i in 0..10 do
+ printfn "Calling GetAsyncEnumerator for the #%i time" i
+ use enumerator = tskSeq.GetAsyncEnumerator()
+ let! isNext = enumerator.MoveNextAsync()
+ isNext |> should be False
+}
+
+[]
+let ``CE taskSeq with two items, MoveNext once too far`` variant = task {
+ let tskSeq = taskSeq {
+ yield 1
+ yield 2
+ }
+
+ let enum = tskSeq.GetAsyncEnumerator()
+ let! isNext = enum.MoveNextAsync() // true
+ let! isNext = enum.MoveNextAsync() // true
+ let! isNext = enum.MoveNextAsync() // false
+ let! isNext = enum.MoveNextAsync() // error here, see
+ ()
+}
+
+[]
+let ``CE taskSeq with two items, MoveNext too far`` variant = task {
+ let tskSeq = taskSeq {
+ yield 1
+ yield 2
+ }
+
+ // let's call MoveNext multiple times on an empty sequence
+ let enum = tskSeq.GetAsyncEnumerator()
+
+ for i in 0..10 do
+ printfn "Calling MoveNext for the #%i time" i
+ let! isNext = enum.MoveNextAsync()
+ //isNext |> should be False
+ ()
+}
+
+[]
+let ``CE taskSeq with two items, multiple TaskSeq.map`` variant = task {
+ let tskSeq = taskSeq {
+ yield 1
+ yield 2
+ }
+
+ // let's call MoveNext multiple times on an empty sequence
+ let ts1 = tskSeq |> TaskSeq.map (fun i -> i + 1)
+ let result1 = TaskSeq.toArray ts1
+ let ts2 = ts1 |> TaskSeq.map (fun i -> i + 1)
+ let result2 = TaskSeq.toArray ts2
+ ()
+}
+
+[]
+let ``CE taskSeq with two items, multiple TaskSeq.mapAsync`` variant = task {
+ let tskSeq = taskSeq {
+ yield 1
+ yield 2
+ }
+
+ // let's call MoveNext multiple times on an empty sequence
+ let ts1 = tskSeq |> TaskSeq.mapAsync (fun i -> task { return i + 1 })
+ let result1 = TaskSeq.toArray ts1
+ let ts2 = ts1 |> TaskSeq.mapAsync (fun i -> task { return i + 1 })
+ let result2 = TaskSeq.toArray ts2
+ ()
+}
+
+[]
+let ``TaskSeq-toArray can be applied multiple times to the same sequence`` () =
+ let tq = createDummyTaskSeq 10
+ let (results1: _[]) = tq |> TaskSeq.toArray
+ let (results2: _[]) = tq |> TaskSeq.toArray
+ let (results3: _[]) = tq |> TaskSeq.toArray
+ let (results4: _[]) = tq |> TaskSeq.toArray
+ results1 |> should equal [| 1..10 |]
+ results2 |> should equal [| 1..10 |]
+ results3 |> should equal [| 1..10 |]
+ results4 |> should equal [| 1..10 |]
diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Tests.CE.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Tests.CE.fs
index a8d4a0dc..af74e4bf 100644
--- a/src/FSharpy.TaskSeq.Test/TaskSeq.Tests.CE.fs
+++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Tests.CE.fs
@@ -1,13 +1,10 @@
-module FSharpy.Tests.``taskSeq Computation Expression``
+module FSharpy.Tests.``taskSeq Computation Expression``
open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling
open FSharpy
-open System.Threading.Tasks
-open System.Diagnostics
-
[]
let ``CE taskSeq with several yield!`` () = task {