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 {