Skip to content
Closed
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 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
12 changes: 12 additions & 0 deletions src/FSharpy.TaskSeq.Test/TaskSeq.Iter.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ let ``TaskSeq-iter should go over all items`` () = task {
sum |> should equal 55 // task-dummies started at 1
}


[<Fact>]
let ``TaskSeq-iter multiple iterations over same sequence`` () = task {
let tq = createDummyTaskSeq 10
let mutable sum = 0
do! tq |> TaskSeq.iter (fun item -> sum <- sum + item)
do! tq |> TaskSeq.iter (fun item -> sum <- sum + item)
do! tq |> TaskSeq.iter (fun item -> sum <- sum + item)
do! tq |> TaskSeq.iter (fun item -> sum <- sum + item)
sum |> should equal 220 // task-dummies started at 1
}

[<Fact>]
let ``TaskSeq-iteriAsync should go over all items`` () = task {
let tq = createDummyTaskSeq 10
Expand Down
26 changes: 26 additions & 0 deletions src/FSharpy.TaskSeq.Test/TaskSeq.Map.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,32 @@ let ``TaskSeq-mapAsync maps in correct order`` () = task {
validateSequence sq
}

[<Fact>]
let ``TaskSeq-mapAsync can map the same sequence multiple times`` () = task {
let mapAndCache =
TaskSeq.mapAsync (fun item -> task { return char (item + 64) })
>> TaskSeq.toSeqCachedAsync

let ts = createDummyDirectTaskSeq 10

let! result1 =
printfn "starting first"
mapAndCache ts
//let! result3 = mapAndCache ts
//let! result4 = mapAndCache ts

validateSequence result1

let! result2 =
printfn "starting second"
mapAndCache ts

validateSequence result2
//validateSequence result3
//validateSequence result4
()
}

[<Fact>]
let ``TaskSeq-mapiAsync maps in correct order`` () = task {
let! sq =
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
13 changes: 13 additions & 0 deletions src/FSharpy.TaskSeq.Test/TaskSeq.ToXXX.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ let ``TaskSeq-toArrayAsync should succeed`` () = task {
results |> should equal [| 1..10 |]
}

[<Fact>]
let ``TaskSeq-toArrayAsync can be applied multiple times to the same sequence`` () = task {
let tq = createDummyTaskSeq 10
let! (results1: _[]) = tq |> TaskSeq.toArrayAsync
let! (results2: _[]) = tq |> TaskSeq.toArrayAsync
let! (results3: _[]) = tq |> TaskSeq.toArrayAsync
let! (results4: _[]) = tq |> TaskSeq.toArrayAsync
results1 |> should equal [| 1..10 |]
results2 |> should equal [| 1..10 |]
results3 |> should equal [| 1..10 |]
results4 |> should equal [| 1..10 |]
}

[<Fact>]
let ``TaskSeq-toListAsync should succeed`` () = task {
let tq = createDummyTaskSeq 10
Expand Down
Loading