Skip to content

Commit

Permalink
Issue #39, adding bunch of tests that show various ways of getting In…
Browse files Browse the repository at this point in the history
…validState exceptions
  • Loading branch information
abelbraaksma committed Oct 24, 2022
1 parent 3ee38a0 commit 63a011e
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<Compile Include="TaskSeq.OfXXX.Tests.fs" />
<Compile Include="TaskSeq.Tests.Other.fs" />
<Compile Include="TaskSeq.Tests.CE.fs" />
<Compile Include="TaskSeq.StateTransitionBug.Tests.CE.fs" />
<Compile Include="TaskSeq.PocTests.fs" />
<Compile Include="TaskSeq.Realworld.fs" />
<Compile Include="Program.fs" />
Expand Down
51 changes: 48 additions & 3 deletions src/FSharpy.TaskSeq.Test/TaskSeq.Realworld.fs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ type AsyncBufferedReader(output: ITestOutputHelper, data, blockSize) =

interface IAsyncEnumerable<byte[]> 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<byte[]> with
member _.Current =
Expand All @@ -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<Guid> = seq { 1 } |> Seq.cast

if bytesRead > 0 then
current <- ValueSome mem
return true
Expand All @@ -48,7 +56,6 @@ type AsyncBufferedReader(output: ITestOutputHelper, data, blockSize) =
}
|> Task.toValueTask

interface IAsyncDisposable with
member _.DisposeAsync() =
try
// this disposes of the mem stream
Expand All @@ -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) =
[<Fact>]
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)
}

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

[<Fact>]
let ``Reading a 10MB buffered IAsync stream from start to finish`` () = task {
let mutable count = 0
Expand All @@ -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
Expand Down
155 changes: 155 additions & 0 deletions src/FSharpy.TaskSeq.Test/TaskSeq.StateTransitionBug.Tests.CE.fs
Original file line number Diff line number Diff line change
@@ -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<int> =
match variant with
| "do" -> taskSeq { do ignore () }
| "do!" -> taskSeq { do! task { return () } } // TODO: this doesn't work with Task, only Task<unit>...
| "yield! (seq)" -> taskSeq { yield! Seq.empty<int> }
| "yield! (taskseq)" -> taskSeq { yield! taskSeq { do ignore () } }
| _ -> failwith "Uncovered variant of test"


[<Fact>]
let ``CE empty taskSeq with MoveNextAsync -- untyped`` () = task {
let tskSeq = taskSeq { do ignore () }

Assert.IsAssignableFrom<IAsyncEnumerable<obj>>(tskSeq)
|> ignore

let! noNext = tskSeq.GetAsyncEnumerator().MoveNextAsync()
noNext |> should be False
}

[<Theory; InlineData "do"; InlineData "do!"; InlineData "yield! (seq)"; InlineData "yield! (taskseq)">]
let ``CE empty taskSeq with MoveNextAsync -- typed`` variant = task {
let tskSeq = getEmptyVariant variant

Assert.IsAssignableFrom<IAsyncEnumerable<int>>(tskSeq)
|> ignore

let! noNext = tskSeq.GetAsyncEnumerator().MoveNextAsync()
noNext |> should be False
}

[<Theory; InlineData "do"; InlineData "do!"; InlineData "yield! (seq)"; InlineData "yield! (taskseq)">]
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()
()
}

[<Theory; InlineData "do"; InlineData "do!"; InlineData "yield! (seq)"; InlineData "yield! (taskseq)">]
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()
()
}

[<Theory; InlineData "do"; InlineData "do!"; InlineData "yield! (seq)"; InlineData "yield! (taskseq)">]
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()
()
}

[<Theory; InlineData "do"; InlineData "do!"; InlineData "yield! (seq)"; InlineData "yield! (taskseq)">]
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
}

[<Theory; InlineData "do"; InlineData "do!"; InlineData "yield! (seq)"; InlineData "yield! (taskseq)">]
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
()
}

[<Theory; InlineData "do"; InlineData "do!"; InlineData "yield! (seq)"; InlineData "yield! (taskseq)">]
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
()
}

[<Theory; InlineData "do"; InlineData "do!"; InlineData "yield! (seq)"; InlineData "yield! (taskseq)">]
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
()
}

[<Theory; InlineData "do"; InlineData "do!"; InlineData "yield! (seq)"; InlineData "yield! (taskseq)">]
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
()
}

[<Fact>]
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 |]
5 changes: 1 addition & 4 deletions src/FSharpy.TaskSeq.Test/TaskSeq.Tests.CE.fs
Original file line number Diff line number Diff line change
@@ -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


[<Fact>]
let ``CE taskSeq with several yield!`` () = task {
Expand Down

0 comments on commit 63a011e

Please sign in to comment.