diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 7d06a2c3..23f4edaa 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -36,7 +36,7 @@ jobs: uses: actions/setup-dotnet@v3 # build it, test it, pack it - name: Run dotnet test - release - run: dotnet test -c Release --blame-hang-timeout 15000ms --logger "trx;LogFileName=test-results-release.trx" --logger "console;verbosity=detailed" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj + run: dotnet test -c Release --blame-hang-timeout 60000ms --logger "trx;LogFileName=test-results-release.trx" --logger "console;verbosity=detailed" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj - name: Publish test results - release uses: dorny/test-reporter@v1 if: always() @@ -60,7 +60,7 @@ jobs: uses: actions/setup-dotnet@v3 # build it, test it, pack it - name: Run dotnet test - debug - run: dotnet test -c Debug --blame-hang-timeout 15000ms --logger "trx;LogFileName=test-results-debug.trx" --logger "console;verbosity=detailed" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj + run: dotnet test -c Debug --blame-hang-timeout 60000ms --logger "trx;LogFileName=test-results-debug.trx" --logger "console;verbosity=detailed" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj - name: Publish test results - debug uses: dorny/test-reporter@v1 if: always() diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 3dac727f..b2739dd3 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -17,7 +17,7 @@ jobs: uses: actions/setup-dotnet@v3 # build it, test it, pack it - name: Run dotnet test - release - run: dotnet test -c Release --blame-hang-timeout 15000ms --logger "trx;LogFileName=test-results-release.trx" --logger "console;verbosity=detailed" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj + run: dotnet test -c Release --blame-hang-timeout 60000ms --logger "trx;LogFileName=test-results-release.trx" --logger "console;verbosity=detailed" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj - name: Publish test results - release uses: dorny/test-reporter@v1 if: always() @@ -41,7 +41,7 @@ jobs: uses: actions/setup-dotnet@v3 # build it, test it, pack it - name: Run dotnet test - debug - run: dotnet test -c Debug --blame-hang-timeout 15000ms --logger "trx;LogFileName=test-results-debug.trx" --logger "console;verbosity=detailed" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj + run: dotnet test -c Debug --blame-hang-timeout 60000ms --logger "trx;LogFileName=test-results-debug.trx" --logger "console;verbosity=detailed" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj - name: Publish test results - debug uses: dorny/test-reporter@v1 if: always() @@ -50,4 +50,3 @@ jobs: # this path glob pattern requires forward slashes! path: ./src/FSharpy.TaskSeq.Test/TestResults/test-results-debug.trx reporter: dotnet-trx - \ No newline at end of file diff --git a/src/FSharpy.TaskSeq.Test/AssemblyInfo.fs b/src/FSharpy.TaskSeq.Test/AssemblyInfo.fs index 4f019eaa..c7bd404a 100644 --- a/src/FSharpy.TaskSeq.Test/AssemblyInfo.fs +++ b/src/FSharpy.TaskSeq.Test/AssemblyInfo.fs @@ -2,7 +2,7 @@ namespace FSharpy.Tests open System.Runtime.CompilerServices -[] +[] [] do () diff --git a/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj b/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj index dd013994..200e0f88 100644 --- a/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj +++ b/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj @@ -11,6 +11,7 @@ + diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.AllTests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.AllTests.fs new file mode 100644 index 00000000..f4573b1c --- /dev/null +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.AllTests.fs @@ -0,0 +1,153 @@ +namespace FSharpy.Tests + +open System +open System.Threading.Tasks +open System.Reflection + +open Xunit +open FsUnit.Xunit +open FsToolkit.ErrorHandling + +open FSharpy +open Xunit.Abstractions + + +type AllTests(output: ITestOutputHelper) = + let createParallelRunner () = + let myAsm = Assembly.GetExecutingAssembly() + + let allMethods = [ + for ty in myAsm.DefinedTypes do + for mem in ty.DeclaredMembers do + match mem.MemberType with + | MemberTypes.Method -> + if mem.Name.StartsWith("TaskSeq") || mem.Name.StartsWith("CE") then + yield ty, mem :?> MethodInfo + | _ -> () + ] + + let all = seq { + for (ty, method) in allMethods do + let ctor = ty.GetConstructor [| typeof |] + + if isNull ctor then + failwith "Constructor for test not found" + + let testObj = ctor.Invoke([| output |]) + + if method.ReturnType.Name.Contains "Task" then + //task { + // let! x = Async.StartChildAsTask (Async.ofTask (method.Invoke(testObj, null) :?> Task)) + // return! x + //} + async { + return! + method.Invoke(testObj, null) :?> Task + |> Async.AwaitTask + } + else + async { return method.Invoke(testObj, null) |> ignore } + } + + all |> Async.Parallel |> Async.map ignore + + let multiply f x = + seq { + for i in [ 0..x ] do + yield f () + } + |> Async.Parallel + |> Async.map ignore + + [] + let ``Run all tests 1 times in parallel`` () = task { do! multiply createParallelRunner 1 } + + [] + [] + let ``Run all tests X times in parallel`` i = task { do! multiply createParallelRunner i } + + [] + [] + let ``Run all tests again X times in parallel`` i = task { do! multiply createParallelRunner i } + + [] + [] + let ``Run all tests and once more, X times in parallel`` i = task { do! multiply createParallelRunner i } + + +//[] +//let ``Run all tests 3 times in parallel`` () = +// multiply createParallelRunner 15 +// |> Async.RunSynchronously + + +//[] +//let ``Run all tests 4 times in parallel`` () = +// multiply createParallelRunner 15 +// |> Async.RunSynchronously + + +//[] +//let ``Run all tests 5 times in parallel`` () = +// multiply createParallelRunner 15 +// |> Async.RunSynchronously + + +//[] +//let ``Run all tests 6 times in parallel`` () = +// multiply createParallelRunner 15 +// |> Async.RunSynchronously + + +//[] +//let ``Run all tests 7 times in parallel`` () = +// multiply createParallelRunner 15 +// |> Async.RunSynchronously + + +//[] +//let ``Run all tests 8 times in parallel`` () = +// multiply createParallelRunner 15 +// |> Async.RunSynchronously + + +//[] +//let ``Run all tests 9 times in parallel`` () = +// multiply createParallelRunner 15 +// |> Async.RunSynchronously + + +//[] +//let ``Run all tests 10 times in parallel`` () = +// multiply createParallelRunner 15 +// |> Async.RunSynchronously + + +//[] +//let ``Run all tests 11 times in parallel`` () = +// multiply createParallelRunner 15 +// |> Async.RunSynchronously + + +//[] +//let ``Run all tests 12 times in parallel`` () = +// multiply createParallelRunner 15 +// |> Async.RunSynchronously + + +//[] +//let ``Run all tests 13 times in parallel`` () = +// multiply createParallelRunner 15 +// |> Async.RunSynchronously + + +//[] +//let ``Run all tests 14 times in parallel`` () = +// multiply createParallelRunner 15 +// |> Async.RunSynchronously + + +//[] +//let ``Run all tests 15 times in parallel`` () = +// multiply createParallelRunner 15 +// |> Async.RunSynchronously diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Choose.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Choose.Tests.fs index c70bbd7e..0a8357b9 100644 --- a/src/FSharpy.TaskSeq.Test/TaskSeq.Choose.Tests.fs +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Choose.Tests.fs @@ -1,4 +1,4 @@ -module FSharpy.Tests.Choose +namespace FSharpy.Tests open System open System.Threading.Tasks @@ -8,50 +8,68 @@ open FsUnit.Xunit open FsToolkit.ErrorHandling open FSharpy +open Xunit.Abstractions -[] -let ``ZHang timeout test`` () = task { - let! empty = Task.Delay 30 - - empty |> should be Null -} - -[] -let ``TaskSeq-choose on an empty sequence`` () = task { - let! empty = - TaskSeq.empty - |> TaskSeq.choose (fun _ -> Some 42) - |> TaskSeq.toListAsync - - List.isEmpty empty |> should be True -} - -[] -let ``TaskSeq-chooseAsync on an empty sequence`` () = task { - let! empty = - TaskSeq.empty - |> TaskSeq.chooseAsync (fun _ -> task { return Some 42 }) - |> TaskSeq.toListAsync - - List.isEmpty empty |> should be True -} - -[] -let ``TaskSeq-choose can convert and filter`` () = task { - let! alphabet = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.choose (fun number -> if number <= 26 then Some(char number + '@') else None) - |> TaskSeq.toArrayAsync - - String alphabet |> should equal "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -} - -[] -let ``TaskSeq-chooseAsync can convert and filter`` () = task { - let! alphabet = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.choose (fun number -> if number <= 26 then Some(char number + '@') else None) - |> TaskSeq.toArrayAsync - - String alphabet |> should equal "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -} + +type Choose(output: ITestOutputHelper) = + + [] + let ``ZHang timeout test`` () = + logStart output + + task { + let! empty = Task.Delay 30 + empty |> should be Null + } + + [] + let ``TaskSeq-choose on an empty sequence`` () = + logStart output + + task { + let! empty = + TaskSeq.empty + |> TaskSeq.choose (fun _ -> Some 42) + |> TaskSeq.toListAsync + + List.isEmpty empty |> should be True + } + + [] + let ``TaskSeq-chooseAsync on an empty sequence`` () = + logStart output + + task { + let! empty = + TaskSeq.empty + |> TaskSeq.chooseAsync (fun _ -> task { return Some 42 }) + |> TaskSeq.toListAsync + + List.isEmpty empty |> should be True + } + + [] + let ``TaskSeq-choose can convert and filter`` () = + logStart output + + task { + let! alphabet = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.choose (fun number -> if number <= 26 then Some(char number + '@') else None) + |> TaskSeq.toArrayAsync + + String alphabet |> should equal "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + } + + [] + let ``TaskSeq-chooseAsync can convert and filter`` () = + logStart output + + task { + let! alphabet = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.choose (fun number -> if number <= 26 then Some(char number + '@') else None) + |> TaskSeq.toArrayAsync + + String alphabet |> should equal "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + } diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Collect.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Collect.Tests.fs index 3d405e2d..728b15cc 100644 --- a/src/FSharpy.TaskSeq.Test/TaskSeq.Collect.Tests.fs +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Collect.Tests.fs @@ -1,4 +1,4 @@ -module FSharpy.Tests.Collect +namespace FSharpy.Tests open Xunit open FsUnit.Xunit @@ -6,60 +6,77 @@ open FsToolkit.ErrorHandling open FSharpy -[] -let ``TaskSeq-collect operates in correct order`` () = task { - let! sq = - createDummyTaskSeq 10 - |> TaskSeq.collect (fun item -> taskSeq { - yield char (item + 64) - yield char (item + 65) - }) - |> TaskSeq.toSeqCachedAsync - - sq - |> Seq.map string - |> String.concat "" - |> should equal "ABBCCDDEEFFGGHHIIJJK" -} - -[] -let ``TaskSeq-collectSeq operates in correct order`` () = task { - let! sq = - createDummyTaskSeq 10 - |> TaskSeq.collectSeq (fun item -> seq { - yield char (item + 64) - yield char (item + 65) - }) - |> TaskSeq.toSeqCachedAsync - - sq - |> Seq.map string - |> String.concat "" - |> should equal "ABBCCDDEEFFGGHHIIJJK" -} - -[] -let ``TaskSeq-collect with empty task sequences`` () = task { - let! sq = - createDummyTaskSeq 10 - |> TaskSeq.collect (fun _ -> TaskSeq.ofSeq Seq.empty) - |> TaskSeq.toSeqCachedAsync - - Seq.isEmpty sq |> should be True -} - -[] -let ``TaskSeq-collectSeq with empty sequences`` () = task { - let! sq = - createDummyTaskSeq 10 - |> TaskSeq.collectSeq (fun _ -> Seq.empty) - |> TaskSeq.toSeqCachedAsync - - Seq.isEmpty sq |> should be True -} - -[] -let ``TaskSeq-empty is empty`` () = task { - let! sq = TaskSeq.empty |> TaskSeq.toSeqCachedAsync - Seq.isEmpty sq |> should be True -} +type Collect(output) = + + [] + let ``TaskSeq-collect operates in correct order`` () = + logStart output + + task { + let! sq = + createDummyTaskSeq 10 + |> TaskSeq.collect (fun item -> taskSeq { + yield char (item + 64) + yield char (item + 65) + }) + |> TaskSeq.toSeqCachedAsync + + sq + |> Seq.map string + |> String.concat "" + |> should equal "ABBCCDDEEFFGGHHIIJJK" + } + + [] + let ``TaskSeq-collectSeq operates in correct order`` () = + logStart output + + task { + let! sq = + createDummyTaskSeq 10 + |> TaskSeq.collectSeq (fun item -> seq { + yield char (item + 64) + yield char (item + 65) + }) + |> TaskSeq.toSeqCachedAsync + + sq + |> Seq.map string + |> String.concat "" + |> should equal "ABBCCDDEEFFGGHHIIJJK" + } + + [] + let ``TaskSeq-collect with empty task sequences`` () = + logStart output + + task { + let! sq = + createDummyTaskSeq 10 + |> TaskSeq.collect (fun _ -> TaskSeq.ofSeq Seq.empty) + |> TaskSeq.toSeqCachedAsync + + Seq.isEmpty sq |> should be True + } + + [] + let ``TaskSeq-collectSeq with empty sequences`` () = + logStart output + + task { + let! sq = + createDummyTaskSeq 10 + |> TaskSeq.collectSeq (fun _ -> Seq.empty) + |> TaskSeq.toSeqCachedAsync + + Seq.isEmpty sq |> should be True + } + + [] + let ``TaskSeq-empty is empty`` () = + logStart output + + task { + let! sq = TaskSeq.empty |> TaskSeq.toSeqCachedAsync + Seq.isEmpty sq |> should be True + } diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Filter.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Filter.Tests.fs index 79c413cd..5b78ab80 100644 --- a/src/FSharpy.TaskSeq.Test/TaskSeq.Filter.Tests.fs +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Filter.Tests.fs @@ -1,4 +1,4 @@ -module FSharpy.Tests.Filter +namespace FSharpy.Tests open System open Xunit @@ -7,47 +7,62 @@ open FsToolkit.ErrorHandling open FSharpy -[] -let ``TaskSeq-filter on an empty sequence`` () = task { - let! empty = - TaskSeq.empty - |> TaskSeq.filter ((=) 12) - |> TaskSeq.toListAsync - - List.isEmpty empty |> should be True -} - -[] -let ``TaskSeq-filterAsync on an empty sequence`` () = task { - let! empty = - TaskSeq.empty - |> TaskSeq.filterAsync (fun x -> task { return x = 12 }) - |> TaskSeq.toListAsync - - List.isEmpty empty |> should be True -} - -[] -let ``TaskSeq-filter filters correctly`` () = task { - let! alphabet = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.filter ((<=) 26) // lambda of '>' etc inverts order of args, so this means 'greater than' - |> TaskSeq.map char - |> TaskSeq.map ((+) '@') - |> TaskSeq.toArrayAsync - - // we filtered all digits above-or-equal-to 26 - String alphabet |> should equal "Z[\]^_`abcdefghijklmnopqr" -} - -[] -let ``TaskSeq-filterAsync filters correctly`` () = task { - let! alphabet = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.filterAsync (fun x -> task { return x <= 26 }) - |> TaskSeq.map char - |> TaskSeq.map ((+) '@') - |> TaskSeq.toArrayAsync - - String alphabet |> should equal "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -} + +type Filter(output) = + + [] + let ``TaskSeq-filter on an empty sequence`` () = + logStart output + + task { + let! empty = + TaskSeq.empty + |> TaskSeq.filter ((=) 12) + |> TaskSeq.toListAsync + + List.isEmpty empty |> should be True + } + + [] + let ``TaskSeq-filterAsync on an empty sequence`` () = + logStart output + + task { + let! empty = + TaskSeq.empty + |> TaskSeq.filterAsync (fun x -> task { return x = 12 }) + |> TaskSeq.toListAsync + + List.isEmpty empty |> should be True + } + + [] + let ``TaskSeq-filter filters correctly`` () = + logStart output + + task { + let! alphabet = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.filter ((<=) 26) // lambda of '>' etc inverts order of args, so this means 'greater than' + |> TaskSeq.map char + |> TaskSeq.map ((+) '@') + |> TaskSeq.toArrayAsync + + // we filtered all digits above-or-equal-to 26 + String alphabet |> should equal "Z[\]^_`abcdefghijklmnopqr" + } + + [] + let ``TaskSeq-filterAsync filters correctly`` () = + logStart output + + task { + let! alphabet = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.filterAsync (fun x -> task { return x <= 26 }) + |> TaskSeq.map char + |> TaskSeq.map ((+) '@') + |> TaskSeq.toArrayAsync + + String alphabet |> should equal "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + } diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Find.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Find.Tests.fs index dcf58b28..7837ea87 100644 --- a/src/FSharpy.TaskSeq.Test/TaskSeq.Find.Tests.fs +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Find.Tests.fs @@ -1,4 +1,4 @@ -module FSharpy.Tests.Find +namespace FSharpy.Tests open Xunit open FsUnit.Xunit @@ -7,241 +7,319 @@ open FsToolkit.ErrorHandling open FSharpy open System.Collections.Generic -// -// TaskSeq.find -// TaskSeq.findAsync -// the tryXXX versions are at the bottom half -// - -[] -let ``TaskSeq-find on an empty sequence raises KeyNotFoundException`` () = task { - fun () -> TaskSeq.empty |> TaskSeq.find ((=) 12) |> Task.ignore - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-find on an empty sequence raises KeyNotFoundException - variant`` () = task { - fun () -> taskSeq { do () } |> TaskSeq.find ((=) 12) |> Task.ignore - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-findAsync on an empty sequence raises KeyNotFoundException`` () = task { - fun () -> - TaskSeq.empty - |> TaskSeq.findAsync (fun x -> task { return x = 12 }) - |> Task.ignore - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-find sad path raises KeyNotFoundException`` () = task { - fun () -> - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.find ((=) 0) // dummy tasks sequence starts at 1 - |> Task.ignore - - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-findAsync sad path raises KeyNotFoundException`` () = task { - fun () -> - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.findAsync (fun x -> task { return x = 0 }) // dummy tasks sequence starts at 1 - |> Task.ignore - - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-find sad path raises KeyNotFoundException variant`` () = task { - fun () -> - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.find ((=) 51) // dummy tasks sequence ends at 50 - |> Task.ignore - - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-findAsync sad path raises KeyNotFoundException variant`` () = task { - fun () -> - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.findAsync (fun x -> task { return x = 51 }) // dummy tasks sequence ends at 50 - |> Task.ignore - - |> should throwAsyncExact typeof -} - - -[] -let ``TaskSeq-find happy path middle of seq`` () = task { - let! twentyFive = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.find (fun x -> x < 26 && x > 24) - - twentyFive |> should equal 25 -} - -[] -let ``TaskSeq-findAsync happy path middle of seq`` () = task { - let! twentyFive = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.findAsync (fun x -> task { return x < 26 && x > 24 }) - - twentyFive |> should equal 25 -} - -[] -let ``TaskSeq-find happy path first item of seq`` () = task { - let! first = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.find ((=) 1) // dummy tasks seq starts at 1 - - first |> should equal 1 -} - -[] -let ``TaskSeq-findAsync happy path first item of seq`` () = task { - let! first = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.findAsync (fun x -> task { return x = 1 }) // dummy tasks seq starts at 1 - - first |> should equal 1 -} - -[] -let ``TaskSeq-find happy path last item of seq`` () = task { - let! last = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.find ((=) 50) // dummy tasks seq ends at 50 - - last |> should equal 50 -} - -[] -let ``TaskSeq-findAsync happy path last item of seq`` () = task { - let! last = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.findAsync (fun x -> task { return x = 50 }) // dummy tasks seq ends at 50 - - last |> should equal 50 -} - -// -// TaskSeq.tryFind -// TaskSeq.tryFindAsync -// - -[] -let ``TaskSeq-tryFind on an empty sequence returns None`` () = task { - let! nothing = TaskSeq.empty |> TaskSeq.tryFind ((=) 12) - nothing |> should be None' -} - -[] -let ``TaskSeq-tryFindAsync on an empty sequence returns None`` () = task { - let! nothing = - TaskSeq.empty - |> TaskSeq.tryFindAsync (fun x -> task { return x = 12 }) - - nothing |> should be None' -} - -[] -let ``TaskSeq-tryFind sad path returns None`` () = task { - let! nothing = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryFind ((=) 0) // dummy tasks sequence starts at 1 - - nothing |> should be None' -} - -[] -let ``TaskSeq-tryFindAsync sad path return None`` () = task { - let! nothing = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryFindAsync (fun x -> task { return x = 0 }) // dummy tasks sequence starts at 1 - - nothing |> should be None' -} - -[] -let ``TaskSeq-tryFind sad path returns None variant`` () = task { - let! nothing = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryFind ((<=) 51) // dummy tasks sequence ends at 50 (inverted sign in lambda!) - - nothing |> should be None' -} - -[] -let ``TaskSeq-tryFindAsync sad path return None - variant`` () = task { - let! nothing = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryFindAsync (fun x -> task { return x >= 51 }) // dummy tasks sequence ends at 50 - - nothing |> should be None' -} - - -[] -let ``TaskSeq-tryFind happy path middle of seq`` () = task { - let! twentyFive = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryFind (fun x -> x < 26 && x > 24) - - twentyFive |> should be Some' - twentyFive |> should equal (Some 25) -} - -[] -let ``TaskSeq-tryFindAsync happy path middle of seq`` () = task { - let! twentyFive = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryFindAsync (fun x -> task { return x < 26 && x > 24 }) - - twentyFive |> should be Some' - twentyFive |> should equal (Some 25) -} - -[] -let ``TaskSeq-tryFind happy path first item of seq`` () = task { - let! first = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryFind ((=) 1) // dummy tasks seq starts at 1 - - first |> should be Some' - first |> should equal (Some 1) -} - -[] -let ``TaskSeq-tryFindAsync happy path first item of seq`` () = task { - let! first = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryFindAsync (fun x -> task { return x = 1 }) // dummy tasks seq starts at 1 - - first |> should be Some' - first |> should equal (Some 1) -} - -[] -let ``TaskSeq-tryFind happy path last item of seq`` () = task { - let! last = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryFind ((=) 50) // dummy tasks seq ends at 50 - - last |> should be Some' - last |> should equal (Some 50) -} - -[] -let ``TaskSeq-tryFindAsync happy path last item of seq`` () = task { - let! last = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryFindAsync (fun x -> task { return x = 50 }) // dummy tasks seq ends at 50 - - last |> should be Some' - last |> should equal (Some 50) -} + +type Find(output) = + + // + // TaskSeq.find + // TaskSeq.findAsync + // the tryXXX versions are at the bottom half + // + + [] + let ``TaskSeq-find on an empty sequence raises KeyNotFoundException`` () = + logStart output + + task { + fun () -> TaskSeq.empty |> TaskSeq.find ((=) 12) |> Task.ignore + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-find on an empty sequence raises KeyNotFoundException - variant`` () = + logStart output + + task { + fun () -> taskSeq { do () } |> TaskSeq.find ((=) 12) |> Task.ignore + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-findAsync on an empty sequence raises KeyNotFoundException`` () = + logStart output + + task { + fun () -> + TaskSeq.empty + |> TaskSeq.findAsync (fun x -> task { return x = 12 }) + |> Task.ignore + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-find sad path raises KeyNotFoundException`` () = + logStart output + + task { + fun () -> + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.find ((=) 0) // dummy tasks sequence starts at 1 + |> Task.ignore + + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-findAsync sad path raises KeyNotFoundException`` () = + logStart output + + task { + fun () -> + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.findAsync (fun x -> task { return x = 0 }) // dummy tasks sequence starts at 1 + |> Task.ignore + + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-find sad path raises KeyNotFoundException variant`` () = + logStart output + + task { + fun () -> + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.find ((=) 51) // dummy tasks sequence ends at 50 + |> Task.ignore + + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-findAsync sad path raises KeyNotFoundException variant`` () = + logStart output + + task { + fun () -> + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.findAsync (fun x -> task { return x = 51 }) // dummy tasks sequence ends at 50 + |> Task.ignore + + |> should throwAsyncExact typeof + } + + + [] + let ``TaskSeq-find happy path middle of seq`` () = + logStart output + + task { + let! twentyFive = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.find (fun x -> x < 26 && x > 24) + + twentyFive |> should equal 25 + } + + [] + let ``TaskSeq-findAsync happy path middle of seq`` () = + logStart output + + task { + let! twentyFive = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.findAsync (fun x -> task { return x < 26 && x > 24 }) + + twentyFive |> should equal 25 + } + + [] + let ``TaskSeq-find happy path first item of seq`` () = + logStart output + + task { + let! first = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.find ((=) 1) // dummy tasks seq starts at 1 + + first |> should equal 1 + } + + [] + let ``TaskSeq-findAsync happy path first item of seq`` () = + logStart output + + task { + let! first = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.findAsync (fun x -> task { return x = 1 }) // dummy tasks seq starts at 1 + + first |> should equal 1 + } + + [] + let ``TaskSeq-find happy path last item of seq`` () = + logStart output + + task { + let! last = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.find ((=) 50) // dummy tasks seq ends at 50 + + last |> should equal 50 + } + + [] + let ``TaskSeq-findAsync happy path last item of seq`` () = + logStart output + + task { + let! last = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.findAsync (fun x -> task { return x = 50 }) // dummy tasks seq ends at 50 + + last |> should equal 50 + } + + // + // TaskSeq.tryFind + // TaskSeq.tryFindAsync + // + + [] + let ``TaskSeq-tryFind on an empty sequence returns None`` () = + logStart output + + task { + let! nothing = TaskSeq.empty |> TaskSeq.tryFind ((=) 12) + nothing |> should be None' + } + + [] + let ``TaskSeq-tryFindAsync on an empty sequence returns None`` () = + logStart output + + task { + let! nothing = + TaskSeq.empty + |> TaskSeq.tryFindAsync (fun x -> task { return x = 12 }) + + nothing |> should be None' + } + + [] + let ``TaskSeq-tryFind sad path returns None`` () = + logStart output + + task { + let! nothing = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryFind ((=) 0) // dummy tasks sequence starts at 1 + + nothing |> should be None' + } + + [] + let ``TaskSeq-tryFindAsync sad path return None`` () = + logStart output + + task { + let! nothing = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryFindAsync (fun x -> task { return x = 0 }) // dummy tasks sequence starts at 1 + + nothing |> should be None' + } + + [] + let ``TaskSeq-tryFind sad path returns None variant`` () = + logStart output + + task { + let! nothing = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryFind ((<=) 51) // dummy tasks sequence ends at 50 (inverted sign in lambda!) + + nothing |> should be None' + } + + [] + let ``TaskSeq-tryFindAsync sad path return None - variant`` () = + logStart output + + task { + let! nothing = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryFindAsync (fun x -> task { return x >= 51 }) // dummy tasks sequence ends at 50 + + nothing |> should be None' + } + + + [] + let ``TaskSeq-tryFind happy path middle of seq`` () = + logStart output + + task { + let! twentyFive = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryFind (fun x -> x < 26 && x > 24) + + twentyFive |> should be Some' + twentyFive |> should equal (Some 25) + } + + [] + let ``TaskSeq-tryFindAsync happy path middle of seq`` () = + logStart output + + task { + let! twentyFive = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryFindAsync (fun x -> task { return x < 26 && x > 24 }) + + twentyFive |> should be Some' + twentyFive |> should equal (Some 25) + } + + [] + let ``TaskSeq-tryFind happy path first item of seq`` () = + logStart output + + task { + let! first = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryFind ((=) 1) // dummy tasks seq starts at 1 + + first |> should be Some' + first |> should equal (Some 1) + } + + [] + let ``TaskSeq-tryFindAsync happy path first item of seq`` () = + logStart output + + task { + let! first = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryFindAsync (fun x -> task { return x = 1 }) // dummy tasks seq starts at 1 + + first |> should be Some' + first |> should equal (Some 1) + } + + [] + let ``TaskSeq-tryFind happy path last item of seq`` () = + logStart output + + task { + let! last = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryFind ((=) 50) // dummy tasks seq ends at 50 + + last |> should be Some' + last |> should equal (Some 50) + } + + [] + let ``TaskSeq-tryFindAsync happy path last item of seq`` () = + logStart output + + task { + let! last = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryFindAsync (fun x -> task { return x = 50 }) // dummy tasks seq ends at 50 + + last |> should be Some' + last |> should equal (Some 50) + } diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Fold.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Fold.Tests.fs index fa6e310b..3102ea80 100644 --- a/src/FSharpy.TaskSeq.Test/TaskSeq.Fold.Tests.fs +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Fold.Tests.fs @@ -1,4 +1,4 @@ -module FSharpy.Tests.Fold +namespace FSharpy.Tests open System.Text open Xunit @@ -7,43 +7,56 @@ open FsToolkit.ErrorHandling open FSharpy +type Fold(output) = -[] -let ``TaskSeq-fold folds with every item`` () = task { - let! alphabet = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 26 - |> TaskSeq.fold (fun (state: StringBuilder) item -> state.Append(char item + '@')) (StringBuilder()) - - alphabet.ToString() - |> should equal "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -} - -[] -let ``TaskSeq-foldAsync folds with every item`` () = task { - let! alphabet = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 26 - |> TaskSeq.foldAsync - (fun (state: StringBuilder) item -> task { return state.Append(char item + '@') }) - (StringBuilder()) - - alphabet.ToString() - |> should equal "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -} - -[] -let ``TaskSeq-fold takes state on empty IAsyncEnumberable`` () = task { - let! empty = - TaskSeq.empty - |> TaskSeq.fold (fun _ item -> char (item + 64)) '_' - - empty |> should equal '_' -} - -[] -let ``TaskSeq-foldAsync takes state on empty IAsyncEnumerable`` () = task { - let! alphabet = - TaskSeq.empty - |> TaskSeq.foldAsync (fun _ item -> task { return char (item + 64) }) '_' - - alphabet |> should equal '_' -} + [] + let ``TaskSeq-fold folds with every item`` () = + logStart output + + task { + let! alphabet = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 26 + |> TaskSeq.fold (fun (state: StringBuilder) item -> state.Append(char item + '@')) (StringBuilder()) + + alphabet.ToString() + |> should equal "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + } + + [] + let ``TaskSeq-foldAsync folds with every item`` () = + logStart output + + task { + let! alphabet = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 26 + |> TaskSeq.foldAsync + (fun (state: StringBuilder) item -> task { return state.Append(char item + '@') }) + (StringBuilder()) + + alphabet.ToString() + |> should equal "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + } + + [] + let ``TaskSeq-fold takes state on empty IAsyncEnumberable`` () = + logStart output + + task { + let! empty = + TaskSeq.empty + |> TaskSeq.fold (fun _ item -> char (item + 64)) '_' + + empty |> should equal '_' + } + + [] + let ``TaskSeq-foldAsync takes state on empty IAsyncEnumerable`` () = + logStart output + + task { + let! alphabet = + TaskSeq.empty + |> TaskSeq.foldAsync (fun _ item -> task { return char (item + 64) }) '_' + + alphabet |> should equal '_' + } diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Head.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Head.Tests.fs index f11ad2ac..39d68ef9 100644 --- a/src/FSharpy.TaskSeq.Test/TaskSeq.Head.Tests.fs +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Head.Tests.fs @@ -1,4 +1,4 @@ -module FSharpy.Tests.Head +namespace FSharpy.Tests open System open Xunit @@ -7,51 +7,73 @@ open FsToolkit.ErrorHandling open FSharpy +type Head(output) = -[] -let ``TaskSeq-head throws on empty sequences`` () = task { - fun () -> TaskSeq.empty |> TaskSeq.head |> Task.ignore - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-head throws on empty sequences - variant`` () = task { - fun () -> taskSeq { do () } |> TaskSeq.head |> Task.ignore - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-tryHead returns None on empty sequences`` () = task { - let! nothing = TaskSeq.empty |> TaskSeq.tryHead - nothing |> should be None' -} - -[] -let ``TaskSeq-head gets the first item in a longer sequence`` () = task { - let! head = createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 |> TaskSeq.head - - head |> should equal 1 -} - -[] -let ``TaskSeq-head gets the only item in a singleton sequence`` () = task { - let! head = taskSeq { yield 10 } |> TaskSeq.head - head |> should equal 10 -} - -[] -let ``TaskSeq-tryHead gets the first item in a longer sequence`` () = task { - let! head = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryHead - - head |> should be Some' - head |> should equal (Some 1) -} - -[] -let ``TaskSeq-tryHead gets the only item in a singleton sequence`` () = task { - let! head = taskSeq { yield 10 } |> TaskSeq.tryHead - head |> should be Some' - head |> should equal (Some 10) -} + [] + let ``TaskSeq-head throws on empty sequences`` () = + logStart output + + task { + fun () -> TaskSeq.empty |> TaskSeq.head |> Task.ignore + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-head throws on empty sequences - variant`` () = + logStart output + + task { + fun () -> taskSeq { do () } |> TaskSeq.head |> Task.ignore + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-tryHead returns None on empty sequences`` () = + logStart output + + task { + let! nothing = TaskSeq.empty |> TaskSeq.tryHead + nothing |> should be None' + } + + [] + let ``TaskSeq-head gets the first item in a longer sequence`` () = + logStart output + + task { + let! head = createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 |> TaskSeq.head + + head |> should equal 1 + } + + [] + let ``TaskSeq-head gets the only item in a singleton sequence`` () = + logStart output + + task { + let! head = taskSeq { yield 10 } |> TaskSeq.head + head |> should equal 10 + } + + [] + let ``TaskSeq-tryHead gets the first item in a longer sequence`` () = + logStart output + + task { + let! head = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryHead + + head |> should be Some' + head |> should equal (Some 1) + } + + [] + let ``TaskSeq-tryHead gets the only item in a singleton sequence`` () = + logStart output + + task { + let! head = taskSeq { yield 10 } |> TaskSeq.tryHead + head |> should be Some' + head |> should equal (Some 10) + } diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Item.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Item.Tests.fs index 85c058fc..f7ef0d71 100644 --- a/src/FSharpy.TaskSeq.Test/TaskSeq.Item.Tests.fs +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Item.Tests.fs @@ -1,4 +1,4 @@ -module FSharpy.Tests.Item +namespace FSharpy.Tests open System open Xunit @@ -7,237 +7,307 @@ open FsToolkit.ErrorHandling open FSharpy +type Item(output) = -[] -let ``TaskSeq-item throws on empty sequences`` () = task { - fun () -> TaskSeq.empty |> TaskSeq.item 0 |> Task.ignore - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-item throws on empty sequence - variant`` () = task { - fun () -> taskSeq { do () } |> TaskSeq.item 50000 |> Task.ignore - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-item throws when not found`` () = task { - fun () -> - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.item 51 - |> Task.ignore - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-item throws when not found - variant`` () = task { - fun () -> - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.item Int32.MaxValue - |> Task.ignore - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-item throws when accessing 2nd item in singleton sequence`` () = task { - fun () -> taskSeq { yield 10 } |> TaskSeq.item 1 |> Task.ignore // zero-based! - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-item always throws with negative values`` () = task { - let make50 () = createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - - fun () -> make50 () |> TaskSeq.item -1 |> Task.ignore - |> should throwAsyncExact typeof - - fun () -> make50 () |> TaskSeq.item -10000 |> Task.ignore - |> should throwAsyncExact typeof - - fun () -> make50 () |> TaskSeq.item Int32.MinValue |> Task.ignore - |> should throwAsyncExact typeof - - fun () -> TaskSeq.empty |> TaskSeq.item -1 |> Task.ignore - |> should throwAsyncExact typeof - - fun () -> TaskSeq.empty |> TaskSeq.item -10000 |> Task.ignore - |> should throwAsyncExact typeof - - fun () -> - TaskSeq.empty - |> TaskSeq.item Int32.MinValue - |> Task.ignore - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-tryItem returns None on empty sequences`` () = task { - let! nothing = TaskSeq.empty |> TaskSeq.tryItem 0 - nothing |> should be None' -} - -[] -let ``TaskSeq-tryItem returns None on empty sequence - variant`` () = task { - let! nothing = taskSeq { do () } |> TaskSeq.tryItem 50000 - nothing |> should be None' -} - -[] -let ``TaskSeq-tryItem returns None when not found`` () = task { - let! nothing = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryItem 50 // zero-based index, so a sequence of 50 items has its last item at index 49 - - nothing |> should be None' -} - -[] -let ``TaskSeq-tryItem returns None when not found - variant`` () = task { - let! nothing = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryItem Int32.MaxValue - - nothing |> should be None' -} - -[] -let ``TaskSeq-tryItem returns None when accessing 2nd item in singleton sequence`` () = task { - let! nothing = taskSeq { yield 10 } |> TaskSeq.tryItem 1 // zero-based! - nothing |> should be None' -} - -[] -let ``TaskSeq-tryItem returns None throws with negative values`` () = task { - let make50 () = createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - - let! nothing = make50 () |> TaskSeq.tryItem -1 - nothing |> should be None' - - let! nothing = make50 () |> TaskSeq.tryItem -10000 - nothing |> should be None' - - let! nothing = make50 () |> TaskSeq.tryItem Int32.MinValue - nothing |> should be None' - - let! nothing = TaskSeq.empty |> TaskSeq.tryItem -1 - nothing |> should be None' - - let! nothing = TaskSeq.empty |> TaskSeq.tryItem -10000 - nothing |> should be None' - - let! nothing = TaskSeq.empty |> TaskSeq.tryItem Int32.MinValue - nothing |> should be None' -} - -[] -let ``TaskSeq-item can get the first item in a longer sequence`` () = task { - let! head = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.item 0 - - head |> should equal 1 -} - -[] -let ``TaskSeq-item can get the last item in a longer sequence`` () = task { - let! head = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.item 49 - - head |> should equal 50 -} - -[] -let ``TaskSeq-item can get the first item in a singleton sequence`` () = task { - let! head = taskSeq { yield 10 } |> TaskSeq.item 0 // zero-based index! - head |> should equal 10 -} - -[] -let ``TaskSeq-tryItem can get the first item in a longer sequence`` () = task { - let! head = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryItem 0 // zero-based! - - head |> should be Some' - head |> should equal (Some 1) -} - -[] -let ``TaskSeq-tryItem in a very long sequence (5_000 items - slow variant)`` () = task { - let! head = createDummyDirectTaskSeq 5_001 |> TaskSeq.tryItem 5_000 // zero-based! - - head |> should be Some' - head |> should equal (Some 5_001) -} - -[] -let ``TaskSeq-tryItem in a very long sequence (50_000 items - slow variant)`` () = task { - let! head = createDummyDirectTaskSeq 50_001 |> TaskSeq.tryItem 50_000 // zero-based! - - head |> should be Some' - head |> should equal (Some 50_001) -} - -[] -let ``TaskSeq-tryItem in a very long sequence (50_000 items - fast variant)`` () = task { - let! head = - // using taskSeq instead of the delayed-task approach above, which creates an extra closure for each - // task, we can really see the speed of the 'taskSeq' CE!! This is - taskSeq { - for i in [ 0..50_000 ] do - yield i - } - |> TaskSeq.tryItem 50_000 // zero-based! - - head |> should be Some' - head |> should equal (Some 50_000) -} - -[] -let ``TaskSeq-tryItem in a very long sequence (50_000 items - using sync Seq)`` () = task { - // this test is just for smoke-test perf comparison with TaskSeq above - let head = - seq { - for i in [ 0..50_000 ] do - yield i - } - |> Seq.tryItem 50_000 // zero-based! - - head |> should be Some' - head |> should equal (Some 50_000) -} - -[] -let ``TaskSeq-tryItem in a very long sequence (500_000 items - fast variant)`` () = task { - let! head = - taskSeq { - for i in [ 0..500_000 ] do - yield i - } - |> TaskSeq.tryItem 500_000 // zero-based! - - head |> should be Some' - head |> should equal (Some 500_000) -} - -[] -let ``TaskSeq-tryItem in a very long sequence (500_000 items - using sync Seq)`` () = task { - // this test is just for smoke-test perf comparison with TaskSeq above - let head = - seq { - for i in [ 0..500_000 ] do - yield i - } - |> Seq.tryItem 500_000 // zero-based! - - head |> should be Some' - head |> should equal (Some 500_000) -} - -[] -let ``TaskSeq-tryItem gets the first item in a singleton sequence`` () = task { - let! head = taskSeq { yield 10 } |> TaskSeq.tryItem 0 // zero-based! - head |> should be Some' - head |> should equal (Some 10) -} + [] + let ``TaskSeq-item throws on empty sequences`` () = + logStart output + + task { + fun () -> TaskSeq.empty |> TaskSeq.item 0 |> Task.ignore + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-item throws on empty sequence - variant`` () = + logStart output + + task { + fun () -> taskSeq { do () } |> TaskSeq.item 50000 |> Task.ignore + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-item throws when not found`` () = + logStart output + + task { + fun () -> + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.item 51 + |> Task.ignore + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-item throws when not found - variant`` () = + logStart output + + task { + fun () -> + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.item Int32.MaxValue + |> Task.ignore + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-item throws when accessing 2nd item in singleton sequence`` () = + logStart output + + task { + fun () -> taskSeq { yield 10 } |> TaskSeq.item 1 |> Task.ignore // zero-based! + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-item always throws with negative values`` () = + logStart output + + task { + let make50 () = createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + + fun () -> make50 () |> TaskSeq.item -1 |> Task.ignore + |> should throwAsyncExact typeof + + fun () -> make50 () |> TaskSeq.item -10000 |> Task.ignore + |> should throwAsyncExact typeof + + fun () -> make50 () |> TaskSeq.item Int32.MinValue |> Task.ignore + |> should throwAsyncExact typeof + + fun () -> TaskSeq.empty |> TaskSeq.item -1 |> Task.ignore + |> should throwAsyncExact typeof + + fun () -> TaskSeq.empty |> TaskSeq.item -10000 |> Task.ignore + |> should throwAsyncExact typeof + + fun () -> + TaskSeq.empty + |> TaskSeq.item Int32.MinValue + |> Task.ignore + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-tryItem returns None on empty sequences`` () = + logStart output + + task { + let! nothing = TaskSeq.empty |> TaskSeq.tryItem 0 + nothing |> should be None' + } + + [] + let ``TaskSeq-tryItem returns None on empty sequence - variant`` () = + logStart output + + task { + let! nothing = taskSeq { do () } |> TaskSeq.tryItem 50000 + nothing |> should be None' + } + + [] + let ``TaskSeq-tryItem returns None when not found`` () = + logStart output + + task { + let! nothing = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryItem 50 // zero-based index, so a sequence of 50 items has its last item at index 49 + + nothing |> should be None' + } + + [] + let ``TaskSeq-tryItem returns None when not found - variant`` () = + logStart output + + task { + let! nothing = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryItem Int32.MaxValue + + nothing |> should be None' + } + + [] + let ``TaskSeq-tryItem returns None when accessing 2nd item in singleton sequence`` () = + logStart output + + task { + let! nothing = taskSeq { yield 10 } |> TaskSeq.tryItem 1 // zero-based! + nothing |> should be None' + } + + [] + let ``TaskSeq-tryItem returns None throws with negative values`` () = + logStart output + + task { + let make50 () = createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + + let! nothing = make50 () |> TaskSeq.tryItem -1 + nothing |> should be None' + + let! nothing = make50 () |> TaskSeq.tryItem -10000 + nothing |> should be None' + + let! nothing = make50 () |> TaskSeq.tryItem Int32.MinValue + nothing |> should be None' + + let! nothing = TaskSeq.empty |> TaskSeq.tryItem -1 + nothing |> should be None' + + let! nothing = TaskSeq.empty |> TaskSeq.tryItem -10000 + nothing |> should be None' + + let! nothing = TaskSeq.empty |> TaskSeq.tryItem Int32.MinValue + nothing |> should be None' + } + + [] + let ``TaskSeq-item can get the first item in a longer sequence`` () = + logStart output + + task { + let! head = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.item 0 + + head |> should equal 1 + } + + [] + let ``TaskSeq-item can get the last item in a longer sequence`` () = + logStart output + + task { + let! head = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.item 49 + + head |> should equal 50 + } + + [] + let ``TaskSeq-item can get the first item in a singleton sequence`` () = + logStart output + + task { + let! head = taskSeq { yield 10 } |> TaskSeq.item 0 // zero-based index! + head |> should equal 10 + } + + [] + let ``TaskSeq-tryItem can get the first item in a longer sequence`` () = + logStart output + + task { + let! head = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryItem 0 // zero-based! + + head |> should be Some' + head |> should equal (Some 1) + } + + [] + let ``TaskSeq-tryItem in a very long sequence (5_000 items - slow variant)`` () = + logStart output + + task { + let! head = createDummyDirectTaskSeq 5_001 |> TaskSeq.tryItem 5_000 // zero-based! + + head |> should be Some' + head |> should equal (Some 5_001) + } + + [] + let ``TaskSeq-tryItem in a very long sequence (50_000 items - slow variant)`` () = + logStart output + + task { + let! head = createDummyDirectTaskSeq 50_001 |> TaskSeq.tryItem 50_000 // zero-based! + + head |> should be Some' + head |> should equal (Some 50_001) + } + + [] + let ``TaskSeq-tryItem in a very long sequence (50_000 items - fast variant)`` () = + logStart output + + task { + let! head = + // using taskSeq instead of the delayed-task approach above, which creates an extra closure for each + // task, we can really see the speed of the 'taskSeq' CE!! This is + taskSeq { + for i in [ 0..50_000 ] do + yield i + } + |> TaskSeq.tryItem 50_000 // zero-based! + + head |> should be Some' + head |> should equal (Some 50_000) + } + + [] + let ``TaskSeq-tryItem in a very long sequence (50_000 items - using sync Seq)`` () = + logStart output + + task { + // this test is just for smoke-test perf comparison with TaskSeq above + let head = + seq { + for i in [ 0..50_000 ] do + yield i + } + |> Seq.tryItem 50_000 // zero-based! + + head |> should be Some' + head |> should equal (Some 50_000) + } + + [] + let ``TaskSeq-tryItem in a very long sequence (500_000 items - fast variant)`` () = + logStart output + + task { + let! head = + taskSeq { + for i in [ 0..500_000 ] do + yield i + } + |> TaskSeq.tryItem 500_000 // zero-based! + + head |> should be Some' + head |> should equal (Some 500_000) + } + + [] + let ``TaskSeq-tryItem in a very long sequence (500_000 items - using sync Seq)`` () = + logStart output + + task { + // this test is just for smoke-test perf comparison with TaskSeq above + let head = + seq { + for i in [ 0..500_000 ] do + yield i + } + |> Seq.tryItem 500_000 // zero-based! + + head |> should be Some' + head |> should equal (Some 500_000) + } + + [] + let ``TaskSeq-tryItem gets the first item in a singleton sequence`` () = + logStart output + + task { + let! head = taskSeq { yield 10 } |> TaskSeq.tryItem 0 // zero-based! + head |> should be Some' + head |> should equal (Some 10) + } diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Iter.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Iter.Tests.fs index f8418b21..012efd4c 100644 --- a/src/FSharpy.TaskSeq.Test/TaskSeq.Iter.Tests.fs +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Iter.Tests.fs @@ -1,4 +1,4 @@ -module FSharpy.Tests.Iter +namespace FSharpy.Tests open Xunit open FsUnit.Xunit @@ -7,42 +7,56 @@ open FsToolkit.ErrorHandling open FSharpy -[] -let ``TaskSeq-iteri should go over all items`` () = task { - let tq = createDummyTaskSeq 10 - let mutable sum = 0 - do! tq |> TaskSeq.iteri (fun i _ -> sum <- sum + i) - sum |> should equal 45 // index starts at 0 -} - -[] -let ``TaskSeq-iter should go over all items`` () = task { - let tq = createDummyTaskSeq 10 - let mutable sum = 0 - do! tq |> TaskSeq.iter (fun item -> sum <- sum + item) - sum |> should equal 55 // task-dummies started at 1 -} - -[] -let ``TaskSeq-iteriAsync should go over all items`` () = task { - let tq = createDummyTaskSeq 10 - let mutable sum = 0 - - do! - tq - |> TaskSeq.iteriAsync (fun i _ -> task { sum <- sum + i }) - - sum |> should equal 45 // index starts at 0 -} - -[] -let ``TaskSeq-iterAsync should go over all items`` () = task { - let tq = createDummyTaskSeq 10 - let mutable sum = 0 - - do! - tq - |> TaskSeq.iterAsync (fun item -> task { sum <- sum + item }) - - sum |> should equal 55 // task-dummies started at 1 -} +type Iter(output) = + + [] + let ``TaskSeq-iteri should go over all items`` () = + logStart output + + task { + let tq = createDummyTaskSeq 10 + let mutable sum = 0 + do! tq |> TaskSeq.iteri (fun i _ -> sum <- sum + i) + sum |> should equal 45 // index starts at 0 + } + + [] + let ``TaskSeq-iter should go over all items`` () = + logStart output + + task { + let tq = createDummyTaskSeq 10 + let mutable sum = 0 + do! tq |> TaskSeq.iter (fun item -> sum <- sum + item) + sum |> should equal 55 // task-dummies started at 1 + } + + [] + let ``TaskSeq-iteriAsync should go over all items`` () = + logStart output + + task { + let tq = createDummyTaskSeq 10 + let mutable sum = 0 + + do! + tq + |> TaskSeq.iteriAsync (fun i _ -> task { sum <- sum + i }) + + sum |> should equal 45 // index starts at 0 + } + + [] + let ``TaskSeq-iterAsync should go over all items`` () = + logStart output + + task { + let tq = createDummyTaskSeq 10 + let mutable sum = 0 + + do! + tq + |> TaskSeq.iterAsync (fun item -> task { sum <- sum + item }) + + sum |> should equal 55 // task-dummies started at 1 + } diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Last.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Last.Tests.fs index 8453457e..a957e51f 100644 --- a/src/FSharpy.TaskSeq.Test/TaskSeq.Last.Tests.fs +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Last.Tests.fs @@ -1,4 +1,4 @@ -module FSharpy.Tests.Last +namespace FSharpy.Tests open System open Xunit @@ -7,51 +7,72 @@ open FsToolkit.ErrorHandling open FSharpy +type Last(output) = + [] + let ``TaskSeq-last throws on empty sequences`` () = + logStart output -[] -let ``TaskSeq-last throws on empty sequences`` () = task { - fun () -> TaskSeq.empty |> TaskSeq.last |> Task.ignore - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-last throws on empty sequences - variant`` () = task { - fun () -> taskSeq { do () } |> TaskSeq.last |> Task.ignore - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-tryLast returns None on empty sequences`` () = task { - let! nothing = TaskSeq.empty |> TaskSeq.tryLast - nothing |> should be None' -} - -[] -let ``TaskSeq-last gets the last item in a longer sequence`` () = task { - let! last = createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 |> TaskSeq.last - - last |> should equal 50 -} - -[] -let ``TaskSeq-last gets the only item in a singleton sequence`` () = task { - let! last = taskSeq { yield 10 } |> TaskSeq.last - last |> should equal 10 -} - -[] -let ``TaskSeq-tryLast gets the last item in a longer sequence`` () = task { - let! last = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryLast - - last |> should be Some' - last |> should equal (Some 50) -} - -[] -let ``TaskSeq-tryLast gets the only item in a singleton sequence`` () = task { - let! last = taskSeq { yield 10 } |> TaskSeq.tryLast - last |> should be Some' - last |> should equal (Some 10) -} + task { + fun () -> TaskSeq.empty |> TaskSeq.last |> Task.ignore + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-last throws on empty sequences - variant`` () = + logStart output + + task { + fun () -> taskSeq { do () } |> TaskSeq.last |> Task.ignore + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-tryLast returns None on empty sequences`` () = + logStart output + + task { + let! nothing = TaskSeq.empty |> TaskSeq.tryLast + nothing |> should be None' + } + + [] + let ``TaskSeq-last gets the last item in a longer sequence`` () = + logStart output + + task { + let! last = createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 |> TaskSeq.last + + last |> should equal 50 + } + + [] + let ``TaskSeq-last gets the only item in a singleton sequence`` () = + logStart output + + task { + let! last = taskSeq { yield 10 } |> TaskSeq.last + last |> should equal 10 + } + + [] + let ``TaskSeq-tryLast gets the last item in a longer sequence`` () = + logStart output + + task { + let! last = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryLast + + last |> should be Some' + last |> should equal (Some 50) + } + + [] + let ``TaskSeq-tryLast gets the only item in a singleton sequence`` () = + logStart output + + task { + let! last = taskSeq { yield 10 } |> TaskSeq.tryLast + last |> should be Some' + last |> should equal (Some 10) + } diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Map.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Map.Tests.fs index 46dc1154..f8ffbee8 100644 --- a/src/FSharpy.TaskSeq.Test/TaskSeq.Map.Tests.fs +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Map.Tests.fs @@ -1,4 +1,4 @@ -module FSharpy.Tests.Map +namespace FSharpy.Tests open Xunit open FsUnit.Xunit @@ -6,29 +6,36 @@ open FsToolkit.ErrorHandling open FSharpy +type Map(output) = -[] -let ``TaskSeq-map maps in correct order`` () = task { - let! sq = - createDummyTaskSeq 10 - |> TaskSeq.map (fun item -> char (item + 64)) - |> TaskSeq.toSeqCachedAsync - - sq - |> Seq.map string - |> String.concat "" - |> should equal "ABCDEFGHIJ" -} - -[] -let ``TaskSeq-mapAsync maps in correct order`` () = task { - let! sq = - createDummyTaskSeq 10 - |> TaskSeq.mapAsync (fun item -> task { return char (item + 64) }) - |> TaskSeq.toSeqCachedAsync - - sq - |> Seq.map string - |> String.concat "" - |> should equal "ABCDEFGHIJ" -} + [] + let ``TaskSeq-map maps in correct order`` () = + logStart output + + task { + let! sq = + createDummyTaskSeq 10 + |> TaskSeq.map (fun item -> char (item + 64)) + |> TaskSeq.toSeqCachedAsync + + sq + |> Seq.map string + |> String.concat "" + |> should equal "ABCDEFGHIJ" + } + + [] + let ``TaskSeq-mapAsync maps in correct order`` () = + logStart output + + task { + let! sq = + createDummyTaskSeq 10 + |> TaskSeq.mapAsync (fun item -> task { return char (item + 64) }) + |> TaskSeq.toSeqCachedAsync + + sq + |> Seq.map string + |> String.concat "" + |> should equal "ABCDEFGHIJ" + } diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.OfXXX.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.OfXXX.Tests.fs index 0cb51387..b67d0c2a 100644 --- a/src/FSharpy.TaskSeq.Test/TaskSeq.OfXXX.Tests.fs +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.OfXXX.Tests.fs @@ -1,4 +1,4 @@ -module FSharpy.Tests.``Conversion-From`` +namespace FSharpy.Tests open Xunit open FsUnit.Xunit @@ -6,52 +6,72 @@ open FsToolkit.ErrorHandling open FSharpy -let validateSequence sq = task { - let! sq = TaskSeq.toArrayAsync sq - do sq |> Seq.toArray |> should equal [| 0..9 |] -} - -[] -let ``TaskSeq-ofAsyncArray should succeed`` () = - Array.init 10 (fun x -> async { return x }) - |> TaskSeq.ofAsyncArray - |> validateSequence - -[] -let ``TaskSeq-ofAsyncList should succeed`` () = - List.init 10 (fun x -> async { return x }) - |> TaskSeq.ofAsyncList - |> validateSequence - -[] -let ``TaskSeq-ofAsyncSeq should succeed`` () = - Seq.init 10 (fun x -> async { return x }) - |> TaskSeq.ofAsyncSeq - |> validateSequence - -[] -let ``TaskSeq-ofTaskArray should succeed`` () = - Array.init 10 (fun x -> task { return x }) - |> TaskSeq.ofTaskArray - |> validateSequence - -[] -let ``TaskSeq-ofTaskList should succeed`` () = - List.init 10 (fun x -> task { return x }) - |> TaskSeq.ofTaskList - |> validateSequence - -[] -let ``TaskSeq-ofTaskSeq should succeed`` () = - Seq.init 10 (fun x -> task { return x }) - |> TaskSeq.ofTaskSeq - |> validateSequence - -[] -let ``TaskSeq-ofArray should succeed`` () = Array.init 10 id |> TaskSeq.ofArray |> validateSequence - -[] -let ``TaskSeq-ofList should succeed`` () = List.init 10 id |> TaskSeq.ofList |> validateSequence - -[] -let ``TaskSeq-ofSeq should succeed`` () = Seq.init 10 id |> TaskSeq.ofSeq |> validateSequence +type ``Conversion-From``(output) = + + let validateSequence sq = task { + let! sq = TaskSeq.toArrayAsync sq + do sq |> Seq.toArray |> should equal [| 0..9 |] + } + + [] + let ``TaskSeq-ofAsyncArray should succeed`` () = + logStart output + + Array.init 10 (fun x -> async { return x }) + |> TaskSeq.ofAsyncArray + |> validateSequence + + [] + let ``TaskSeq-ofAsyncList should succeed`` () = + logStart output + + List.init 10 (fun x -> async { return x }) + |> TaskSeq.ofAsyncList + |> validateSequence + + [] + let ``TaskSeq-ofAsyncSeq should succeed`` () = + logStart output + + Seq.init 10 (fun x -> async { return x }) + |> TaskSeq.ofAsyncSeq + |> validateSequence + + [] + let ``TaskSeq-ofTaskArray should succeed`` () = + logStart output + + Array.init 10 (fun x -> task { return x }) + |> TaskSeq.ofTaskArray + |> validateSequence + + [] + let ``TaskSeq-ofTaskList should succeed`` () = + logStart output + + List.init 10 (fun x -> task { return x }) + |> TaskSeq.ofTaskList + |> validateSequence + + [] + let ``TaskSeq-ofTaskSeq should succeed`` () = + logStart output + + Seq.init 10 (fun x -> task { return x }) + |> TaskSeq.ofTaskSeq + |> validateSequence + + [] + let ``TaskSeq-ofArray should succeed`` () = + logStart output + Array.init 10 id |> TaskSeq.ofArray |> validateSequence + + [] + let ``TaskSeq-ofList should succeed`` () = + logStart output + List.init 10 id |> TaskSeq.ofList |> validateSequence + + [] + let ``TaskSeq-ofSeq should succeed`` () = + logStart output + Seq.init 10 id |> TaskSeq.ofSeq |> validateSequence diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Pick.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Pick.Tests.fs index 6e8c63de..ba50370b 100644 --- a/src/FSharpy.TaskSeq.Test/TaskSeq.Pick.Tests.fs +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Pick.Tests.fs @@ -1,4 +1,4 @@ -module FSharpy.Tests.Pick +namespace FSharpy.Tests open System open System.Collections.Generic @@ -8,251 +8,327 @@ open FsToolkit.ErrorHandling open FSharpy +type Pick(output) = + + // + // TaskSeq.pick + // TaskSeq.pickAsync + // the tryXXX versions are at the bottom half + // + + [] + let ``TaskSeq-pick on an empty sequence raises KeyNotFoundException`` () = + logStart output + + task { + fun () -> + TaskSeq.empty + |> TaskSeq.pick (fun x -> if x = 12 then Some x else None) + |> Task.ignore + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-pick on an empty sequence raises KeyNotFoundException - variant`` () = + logStart output + + task { + fun () -> + taskSeq { do () } + |> TaskSeq.pick (fun x -> if x = 12 then Some x else None) + |> Task.ignore + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-pickAsync on an empty sequence raises KeyNotFoundException`` () = + logStart output + + task { + fun () -> + TaskSeq.empty + |> TaskSeq.pickAsync (fun x -> task { return if x = 12 then Some x else None }) + |> Task.ignore + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-pick sad path raises KeyNotFoundException`` () = + logStart output + + task { + fun () -> + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.pick (fun x -> if x = 0 then Some x else None) // dummy tasks sequence starts at 1 + |> Task.ignore + + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-pickAsync sad path raises KeyNotFoundException`` () = + logStart output + + task { + fun () -> + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.pickAsync (fun x -> task { return if x < 0 then Some x else None }) // dummy tasks sequence starts at 1 + |> Task.ignore + + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-pick sad path raises KeyNotFoundException variant`` () = + logStart output + + task { + fun () -> + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.pick (fun x -> if x = 51 then Some x else None) // dummy tasks sequence ends at 50 + |> Task.ignore + + |> should throwAsyncExact typeof + } + + [] + let ``TaskSeq-pickAsync sad path raises KeyNotFoundException variant`` () = + logStart output + + task { + fun () -> + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.pickAsync (fun x -> task { return if x = 51 then Some x else None }) // dummy tasks sequence ends at 50 + |> Task.ignore + + |> should throwAsyncExact typeof + } + + + [] + let ``TaskSeq-pick happy path middle of seq`` () = + logStart output + + task { + let! twentyFive = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.pick (fun x -> if x < 26 && x > 24 then Some "foo" else None) + + twentyFive |> should equal "foo" + } + + [] + let ``TaskSeq-pickAsync happy path middle of seq`` () = + logStart output + + task { + let! twentyFive = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.pickAsync (fun x -> task { return if x < 26 && x > 24 then Some "foo" else None }) + + twentyFive |> should equal "foo" + } + + [] + let ``TaskSeq-pick happy path first item of seq`` () = + logStart output + + task { + let! first = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.pick (fun x -> if x = 1 then Some $"first{x}" else None) // dummy tasks seq starts at 1 + + first |> should equal "first1" + } + + [] + let ``TaskSeq-pickAsync happy path first item of seq`` () = + logStart output + + task { + let! first = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.pickAsync (fun x -> task { return if x = 1 then Some $"first{x}" else None }) // dummy tasks seq starts at 1 + + first |> should equal "first1" + } + + [] + let ``TaskSeq-pick happy path last item of seq`` () = + logStart output -// -// TaskSeq.pick -// TaskSeq.pickAsync -// the tryXXX versions are at the bottom half -// - -[] -let ``TaskSeq-pick on an empty sequence raises KeyNotFoundException`` () = task { - fun () -> - TaskSeq.empty - |> TaskSeq.pick (fun x -> if x = 12 then Some x else None) - |> Task.ignore - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-pick on an empty sequence raises KeyNotFoundException - variant`` () = task { - fun () -> - taskSeq { do () } - |> TaskSeq.pick (fun x -> if x = 12 then Some x else None) - |> Task.ignore - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-pickAsync on an empty sequence raises KeyNotFoundException`` () = task { - fun () -> - TaskSeq.empty - |> TaskSeq.pickAsync (fun x -> task { return if x = 12 then Some x else None }) - |> Task.ignore - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-pick sad path raises KeyNotFoundException`` () = task { - fun () -> - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.pick (fun x -> if x = 0 then Some x else None) // dummy tasks sequence starts at 1 - |> Task.ignore - - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-pickAsync sad path raises KeyNotFoundException`` () = task { - fun () -> - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.pickAsync (fun x -> task { return if x < 0 then Some x else None }) // dummy tasks sequence starts at 1 - |> Task.ignore - - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-pick sad path raises KeyNotFoundException variant`` () = task { - fun () -> - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.pick (fun x -> if x = 51 then Some x else None) // dummy tasks sequence ends at 50 - |> Task.ignore - - |> should throwAsyncExact typeof -} - -[] -let ``TaskSeq-pickAsync sad path raises KeyNotFoundException variant`` () = task { - fun () -> - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.pickAsync (fun x -> task { return if x = 51 then Some x else None }) // dummy tasks sequence ends at 50 - |> Task.ignore - - |> should throwAsyncExact typeof -} - - -[] -let ``TaskSeq-pick happy path middle of seq`` () = task { - let! twentyFive = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.pick (fun x -> if x < 26 && x > 24 then Some "foo" else None) - - twentyFive |> should equal "foo" -} - -[] -let ``TaskSeq-pickAsync happy path middle of seq`` () = task { - let! twentyFive = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.pickAsync (fun x -> task { return if x < 26 && x > 24 then Some "foo" else None }) - - twentyFive |> should equal "foo" -} - -[] -let ``TaskSeq-pick happy path first item of seq`` () = task { - let! first = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.pick (fun x -> if x = 1 then Some $"first{x}" else None) // dummy tasks seq starts at 1 - - first |> should equal "first1" -} - -[] -let ``TaskSeq-pickAsync happy path first item of seq`` () = task { - let! first = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.pickAsync (fun x -> task { return if x = 1 then Some $"first{x}" else None }) // dummy tasks seq starts at 1 - - first |> should equal "first1" -} - -[] -let ``TaskSeq-pick happy path last item of seq`` () = task { - let! last = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.pick (fun x -> if x = 50 then Some $"last{x}" else None) // dummy tasks seq ends at 50 - - last |> should equal "last50" -} - -[] -let ``TaskSeq-pickAsync happy path last item of seq`` () = task { - let! last = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.pickAsync (fun x -> task { return if x = 50 then Some $"last{x}" else None }) // dummy tasks seq ends at 50 - - last |> should equal "last50" -} - -// -// TaskSeq.tryPick -// TaskSeq.tryPickAsync -// - -[] -let ``TaskSeq-tryPick on an empty sequence returns None`` () = task { - let! nothing = - TaskSeq.empty - |> TaskSeq.tryPick (fun x -> if x = 12 then Some x else None) - - nothing |> should be None' -} - -[] -let ``TaskSeq-tryPickAsync on an empty sequence returns None`` () = task { - let! nothing = - TaskSeq.empty - |> TaskSeq.tryPickAsync (fun x -> task { return if x = 12 then Some x else None }) - - nothing |> should be None' -} - -[] -let ``TaskSeq-tryPick sad path returns None`` () = task { - let! nothing = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryPick (fun x -> if x = 0 then Some x else None) // dummy tasks sequence starts at 1 - - nothing |> should be None' -} - -[] -let ``TaskSeq-tryPickAsync sad path return None`` () = task { - let! nothing = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryPickAsync (fun x -> task { return if x = 0 then Some x else None }) // dummy tasks sequence starts at 1 - - nothing |> should be None' -} - -[] -let ``TaskSeq-tryPick sad path returns None variant`` () = task { - let! nothing = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryPick (fun x -> if x >= 51 then Some x else None) // dummy tasks sequence ends at 50 (inverted sign in lambda!) - - nothing |> should be None' -} - -[] -let ``TaskSeq-tryPickAsync sad path return None - variant`` () = task { - let! nothing = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryPickAsync (fun x -> task { return if x >= 51 then Some x else None }) // dummy tasks sequence ends at 50 - - nothing |> should be None' -} - - -[] -let ``TaskSeq-tryPick happy path middle of seq`` () = task { - let! twentyFive = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryPick (fun x -> if x < 26 && x > 24 then Some $"foo{x}" else None) - - twentyFive |> should be Some' - twentyFive |> should equal (Some "foo25") -} - -[] -let ``TaskSeq-tryPickAsync happy path middle of seq`` () = task { - let! twentyFive = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryPickAsync (fun x -> task { return if x < 26 && x > 24 then Some $"foo{x}" else None }) - - twentyFive |> should be Some' - twentyFive |> should equal (Some "foo25") -} - -[] -let ``TaskSeq-tryPick happy path first item of seq`` () = task { - let! first = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryPick (sprintf "foo%i" >> Some) // dummy tasks seq starts at 1 - - first |> should be Some' - first |> should equal (Some "foo1") -} - -[] -let ``TaskSeq-tryPickAsync happy path first item of seq`` () = task { - let! first = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryPickAsync (fun x -> task { return (sprintf "foo%i" >> Some) x }) // dummy tasks seq starts at 1 - - first |> should be Some' - first |> should equal (Some "foo1") -} - -[] -let ``TaskSeq-tryPick happy path last item of seq`` () = task { - let! last = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryPick (fun x -> if x = 50 then Some $"foo{x}" else None) // dummy tasks seq ends at 50 - - last |> should be Some' - last |> should equal (Some "foo50") -} - -[] -let ``TaskSeq-tryPickAsync happy path last item of seq`` () = task { - let! last = - createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 - |> TaskSeq.tryPickAsync (fun x -> task { return if x = 50 then Some $"foo{x}" else None }) // dummy tasks seq ends at 50 - - last |> should be Some' - last |> should equal (Some "foo50") -} + task { + let! last = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.pick (fun x -> if x = 50 then Some $"last{x}" else None) // dummy tasks seq ends at 50 + + last |> should equal "last50" + } + + [] + let ``TaskSeq-pickAsync happy path last item of seq`` () = + logStart output + + task { + let! last = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.pickAsync (fun x -> task { return if x = 50 then Some $"last{x}" else None }) // dummy tasks seq ends at 50 + + last |> should equal "last50" + } + + // + // TaskSeq.tryPick + // TaskSeq.tryPickAsync + // + + [] + let ``TaskSeq-tryPick on an empty sequence returns None`` () = + logStart output + + task { + let! nothing = + TaskSeq.empty + |> TaskSeq.tryPick (fun x -> if x = 12 then Some x else None) + + nothing |> should be None' + } + + [] + let ``TaskSeq-tryPickAsync on an empty sequence returns None`` () = + logStart output + + task { + let! nothing = + TaskSeq.empty + |> TaskSeq.tryPickAsync (fun x -> task { return if x = 12 then Some x else None }) + + nothing |> should be None' + } + + [] + let ``TaskSeq-tryPick sad path returns None`` () = + logStart output + + task { + let! nothing = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryPick (fun x -> if x = 0 then Some x else None) // dummy tasks sequence starts at 1 + + nothing |> should be None' + } + + [] + let ``TaskSeq-tryPickAsync sad path return None`` () = + logStart output + + task { + let! nothing = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryPickAsync (fun x -> task { return if x = 0 then Some x else None }) // dummy tasks sequence starts at 1 + + nothing |> should be None' + } + + [] + let ``TaskSeq-tryPick sad path returns None variant`` () = + logStart output + + task { + let! nothing = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryPick (fun x -> if x >= 51 then Some x else None) // dummy tasks sequence ends at 50 (inverted sign in lambda!) + + nothing |> should be None' + } + + [] + let ``TaskSeq-tryPickAsync sad path return None - variant`` () = + logStart output + + task { + let! nothing = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryPickAsync (fun x -> task { return if x >= 51 then Some x else None }) // dummy tasks sequence ends at 50 + + nothing |> should be None' + } + + + [] + let ``TaskSeq-tryPick happy path middle of seq`` () = + logStart output + + task { + let! twentyFive = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryPick (fun x -> if x < 26 && x > 24 then Some $"foo{x}" else None) + + twentyFive |> should be Some' + twentyFive |> should equal (Some "foo25") + } + + [] + let ``TaskSeq-tryPickAsync happy path middle of seq`` () = + logStart output + + task { + let! twentyFive = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryPickAsync (fun x -> task { return if x < 26 && x > 24 then Some $"foo{x}" else None }) + + twentyFive |> should be Some' + twentyFive |> should equal (Some "foo25") + } + + [] + let ``TaskSeq-tryPick happy path first item of seq`` () = + logStart output + + task { + let! first = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryPick (sprintf "foo%i" >> Some) // dummy tasks seq starts at 1 + + first |> should be Some' + first |> should equal (Some "foo1") + } + + [] + let ``TaskSeq-tryPickAsync happy path first item of seq`` () = + logStart output + + task { + let! first = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryPickAsync (fun x -> task { return (sprintf "foo%i" >> Some) x }) // dummy tasks seq starts at 1 + + first |> should be Some' + first |> should equal (Some "foo1") + } + + [] + let ``TaskSeq-tryPick happy path last item of seq`` () = + logStart output + + task { + let! last = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryPick (fun x -> if x = 50 then Some $"foo{x}" else None) // dummy tasks seq ends at 50 + + last |> should be Some' + last |> should equal (Some "foo50") + } + + [] + let ``TaskSeq-tryPickAsync happy path last item of seq`` () = + logStart output + + task { + let! last = + createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 + |> TaskSeq.tryPickAsync (fun x -> task { return if x = 50 then Some $"foo{x}" else None }) // dummy tasks seq ends at 50 + + last |> should be Some' + last |> should equal (Some "foo50") + } diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.PocTests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.PocTests.fs index 23d4c003..5eb7c01e 100644 --- a/src/FSharpy.TaskSeq.Test/TaskSeq.PocTests.fs +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.PocTests.fs @@ -15,77 +15,92 @@ open FSharpy ///////////////////////////////////////////////////////////////////////////// -module ``PoC's for seq of tasks`` = - - [] - let ``Good: Show joining tasks with continuation is good`` () = task { - // acts like a fold - let! results = createAndJoinMultipleTasks 10 joinWithContinuation - results |> should equal 10 - } - - [] - let ``Good: Show that joining tasks with 'bind' in task CE is good`` () = task { - let! tasks = createAndJoinMultipleTasks 10 joinIdentityDelayed - - let tasks = tasks |> Array.ofList - let len = Array.length tasks - let results = Array.zeroCreate len - - for i in 0 .. len - 1 do - // this uses Task.bind under the hood, which ensures order-of-execution and wait-for-previous - let! item = tasks[i]() // only now are we delay-executing the task in the array - results[i] <- item - - results |> should equal <| Array.init len ((+) 1) - } - - [] - let ``Good: Show that joining tasks with 'taskSeq' is good`` () = task { - let! tasks = createAndJoinMultipleTasks 10 joinIdentityDelayed - - let asAsyncSeq = taskSeq { - for task in tasks do - // cannot use `yield!` here, as `taskSeq` expects it to return a seq - let! x = task () - yield x +type ``PoC's for seq of tasks``(output) = + + [] + let ``Good: Show joining tasks with continuation is good`` () = + logStart output + + task { + // acts like a fold + let! results = createAndJoinMultipleTasks 10 joinWithContinuation + results |> should equal 10 } - let! results = asAsyncSeq |> TaskSeq.toArrayAsync + [] + let ``Good: Show that joining tasks with 'bind' in task CE is good`` () = + logStart output - results |> should equal - <| Array.init (Array.length results) ((+) 1) - } + task { + let! tasks = createAndJoinMultipleTasks 10 joinIdentityDelayed - [] - let ``Bad: Show that joining tasks with 'traverseTaskResult' can be bad`` () = task { - let! taskList = createAndJoinMultipleTasks 10 joinIdentityHotStarted + let tasks = tasks |> Array.ofList + let len = Array.length tasks + let results = Array.zeroCreate len - // since tasks are hot-started, by this time they are already *all* running - let! results = - taskList - |> List.map (Task.map Result.Ok) - |> List.traverseTaskResultA id + for i in 0 .. len - 1 do + // this uses Task.bind under the hood, which ensures order-of-execution and wait-for-previous + let! item = tasks[i]() // only now are we delay-executing the task in the array + results[i] <- item - match results with - | Ok results -> - results |> should not' - <| equal (List.init (List.length results) ((+) 1)) - | Error err -> failwith $"Impossible: {err}" - } + results |> should equal <| Array.init len ((+) 1) + } + + [] + let ``Good: Show that joining tasks with 'taskSeq' is good`` () = + logStart output + + task { + let! tasks = createAndJoinMultipleTasks 10 joinIdentityDelayed + + let asAsyncSeq = taskSeq { + for task in tasks do + // cannot use `yield!` here, as `taskSeq` expects it to return a seq + let! x = task () + yield x + } + + let! results = asAsyncSeq |> TaskSeq.toArrayAsync + + results |> should equal + <| Array.init (Array.length results) ((+) 1) + } + + [] + let ``Bad: Show that joining tasks with 'traverseTaskResult' can be bad`` () = + logStart output + + task { + let! taskList = createAndJoinMultipleTasks 10 joinIdentityHotStarted - [] - let ``Bad: Show that joining tasks as a list of tasks can be bad`` () = task { - let! taskList = createAndJoinMultipleTasks 10 joinIdentityHotStarted + // since tasks are hot-started, by this time they are already *all* running + let! results = + taskList + |> List.map (Task.map Result.Ok) + |> List.traverseTaskResultA id + + match results with + | Ok results -> + results |> should not' + <| equal (List.init (List.length results) ((+) 1)) + | Error err -> failwith $"Impossible: {err}" + } - // since tasks are hot-started, by this time they are already *all* running - let tasks = taskList |> Array.ofList - let results = Array.zeroCreate 10 + [] + let ``Bad: Show that joining tasks as a list of tasks can be bad`` () = + logStart output - for i in 0..9 do - let! item = tasks[i] - results[i] <- item + task { + let! taskList = createAndJoinMultipleTasks 10 joinIdentityHotStarted - results |> should not' - <| equal (Array.init (Array.length results) ((+) 1)) - } + // since tasks are hot-started, by this time they are already *all* running + let tasks = taskList |> Array.ofList + let results = Array.zeroCreate 10 + + for i in 0..9 do + let! item = tasks[i] + results[i] <- item + + results |> should not' + <| equal (Array.init (Array.length results) ((+) 1)) + } diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Tests.CE.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Tests.CE.fs index a8d4a0dc..def62005 100644 --- a/src/FSharpy.TaskSeq.Test/TaskSeq.Tests.CE.fs +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Tests.CE.fs @@ -1,4 +1,4 @@ -module FSharpy.Tests.``taskSeq Computation Expression`` +namespace FSharpy.Tests open Xunit open FsUnit.Xunit @@ -8,82 +8,86 @@ open FSharpy open System.Threading.Tasks open System.Diagnostics +type ``taskSeq Computation Expression``(output) = -[] -let ``CE taskSeq with several yield!`` () = task { - let tskSeq = taskSeq { - yield! createDummyTaskSeq 10 - yield! createDummyTaskSeq 5 - yield! createDummyTaskSeq 10 - yield! createDummyTaskSeq 5 - } + [] + let ``CE taskSeq with several yield!`` () = + logStart output - let! data = tskSeq |> TaskSeq.toListAsync + task { + let tskSeq = taskSeq { + yield! createDummyTaskSeq 10 + yield! createDummyTaskSeq 5 + yield! createDummyTaskSeq 10 + yield! createDummyTaskSeq 5 + } - data - |> should equal (List.concat [ [ 1..10 ]; [ 1..5 ]; [ 1..10 ]; [ 1..5 ] ]) -} + let! data = tskSeq |> TaskSeq.toListAsync -[] -let ``CE taskSeq with nested yield!`` () = task { - let control = seq { - yield! [ 1..10 ] + data + |> should equal (List.concat [ [ 1..10 ]; [ 1..5 ]; [ 1..10 ]; [ 1..5 ] ]) + } - for i in 0..9 do - yield! [ 1..2 ] + [] + let ``CE taskSeq with nested yield!`` () = + logStart output - for i in 0..2 do - yield! seq { yield 42 } + task { + let control = seq { + yield! [ 1..10 ] - for i in 100..102 do - yield! seq { yield! seq { yield i } } - } + for i in 0..9 do + yield! [ 1..2 ] - let tskSeq = taskSeq { - yield! createDummyTaskSeq 10 + for i in 0..2 do + yield! seq { yield 42 } + + for i in 100..102 do + yield! seq { yield! seq { yield i } } + } - for i in 0..9 do - yield! createDummyTaskSeq 2 + let tskSeq = taskSeq { + yield! createDummyTaskSeq 10 - for i in 0..2 do - yield! taskSeq { yield 42 } + for i in 0..9 do + yield! createDummyTaskSeq 2 - for i in 100..102 do - yield! taskSeq { yield! taskSeq { yield i } } - } + for i in 0..2 do + yield! taskSeq { yield 42 } - let! data = tskSeq |> TaskSeq.toListAsync + for i in 100..102 do + yield! taskSeq { yield! taskSeq { yield i } } + } - data |> should equal (List.ofSeq control) - data |> should haveLength 150 -} + let! data = tskSeq |> TaskSeq.toListAsync -[] -let ``CE taskSeq with nested deeply yield! perf test 8521 nested tasks`` () = task { - let control = seq { - yield! [ 1..10 ] + data |> should equal (List.ofSeq control) + data |> should haveLength 150 + } - // original: - yield! Seq.concat <| Seq.init 4251 (fun _ -> [ 1; 2 ]) - //yield! Seq.concat <| Seq.init 120 (fun _ -> [ 1; 2 ]) - } + [] + let ``CE taskSeq with nested deeply yield! perf test 8521 nested tasks`` () = + logStart output - let createTasks = createDummyTaskSeqWith 1L<µs> 10L<µs> - // FIXME: it appears that deeply nesting adds to performance degradation, need to benchmark/profile this - // probably cause: since this is *fast* with DirectTask, the reason is likely the way the Task.Delay causes - // *many* subtasks to be delayed, resulting in exponential delay. Reason: max accuracy of Delay is about 15ms (!) + task { + let control = seq { + yield! [ 1..10 ] - // RESOLUTION: seems to have been caused by erratic Task.Delay which has only a 15ms resolution - let tskSeq = taskSeq { - yield! createTasks 10 + // original: + yield! Seq.concat <| Seq.init 4251 (fun _ -> [ 1; 2 ]) + //yield! Seq.concat <| Seq.init 120 (fun _ -> [ 1; 2 ]) + } - // nestings amount to 8512 sequences of [1;2] - for i in 0..2 do - yield! createTasks 2 + let createTasks = createDummyTaskSeqWith 1L<µs> 10L<µs> + // FIXME: it appears that deeply nesting adds to performance degradation, need to benchmark/profile this + // probably cause: since this is *fast* with DirectTask, the reason is likely the way the Task.Delay causes + // *many* subtasks to be delayed, resulting in exponential delay. Reason: max accuracy of Delay is about 15ms (!) - for i in 0..2 do - yield! createTasks 2 + // RESOLUTION: seems to have been caused by erratic Task.Delay which has only a 15ms resolution + let tskSeq = taskSeq { + yield! createTasks 10 + // nestings amount to 8512 sequences of [1;2] for i in 0..2 do yield! createTasks 2 @@ -96,123 +100,153 @@ let ``CE taskSeq with nested deeply yield! perf test 8521 nested tasks`` () = ta for i in 0..2 do yield! createTasks 2 - for i in 0..2 do - yield! createTasks 2 + for i in 0..2 do + yield! createTasks 2 - for i in 0..2 do - yield! createTasks 2 + for i in 0..2 do + yield! createTasks 2 for i in 0..2 do yield! createTasks 2 - yield! TaskSeq.empty - } - - let! data = tskSeq |> TaskSeq.toListAsync - data |> List.length |> should equal 8512 - data |> should equal (List.ofSeq control) -} - -[] -let ``CE taskSeq with several return!`` () = task { - // TODO: should we even support this? Traditional 'seq' doesn't. - let tskSeq = taskSeq { - return! createDummyTaskSeq 10 - return! createDummyTaskSeq 5 - } - - let! data = tskSeq |> TaskSeq.toListAsync - - // FIXME!!! This behavior is *probably* not correct - data |> should equal [ 1..10 ] -} - - -[] -let ``CE taskSeq with mixing yield! and yield`` () = task { - let tskSeq = taskSeq { - yield! createDummyTaskSeq 10 - yield 42 - yield! createDummyTaskSeq 5 - yield 42 - yield! createDummyTaskSeq 10 - yield 42 - yield! createDummyTaskSeq 5 - } - - let! data = tskSeq |> TaskSeq.toListAsync - - data - |> should equal (List.concat [ [ 1..10 ]; [ 42 ]; [ 1..5 ]; [ 42 ]; [ 1..10 ]; [ 42 ]; [ 1..5 ] ]) -} - -[] -let ``CE taskSeq: 1000 TaskDelay-delayed tasks using yield!`` () = task { - // Smoke performance test - // Runs in slightly over half a second (average of spin-wait, plus small overhead) - // should generally be about as fast as `task`, see below for equivalent test. - let tskSeq = taskSeq { yield! createDummyTaskSeqWith 50L<µs> 1000L<µs> 1000 } - let! data = tskSeq |> TaskSeq.toListAsync - data |> should equal [ 1..1000 ] -} - -[] -let ``CE taskSeq: 1000 sync-running tasks using yield!`` () = task { - // Smoke performance test - // Runs in a few 10's of ms, because of absense of Task.Delay - // should generally be about as fast as `task`, see below - let tskSeq = taskSeq { yield! createDummyDirectTaskSeq 1000 } - let! data = tskSeq |> TaskSeq.toListAsync - data |> should equal [ 1..1000 ] -} - -[] -let ``CE taskSeq: 5000 sync-running tasks using yield!`` () = task { - // Smoke performance test - // Compare with task-ce test below. Uses a no-delay hot-started sequence of tasks. - let tskSeq = taskSeq { yield! createDummyDirectTaskSeq 5000 } - let! data = tskSeq |> TaskSeq.toListAsync - data |> should equal [ 1..5000 ] -} - -[] -let ``CE task: 1000 TaskDelay-delayed tasks using for-loop`` () = task { - // Uses SpinWait for effective task-delaying - // for smoke-test comparison with taskSeq - let tasks = DummyTaskFactory(50L<µs>, 1000L<µs>).CreateDelayedTasks 1000 - let mutable i = 0 - - for t in tasks do - i <- i + 1 - do! t () |> Task.ignore - - i |> should equal 1000 -} - -[] -let ``CE task: 1000 list of sync-running tasks using for-loop`` () = task { - // runs in a few 10's of ms, because of absense of Task.Delay - // for smoke-test comparison with taskSeq - let tasks = DummyTaskFactory().CreateDirectTasks 1000 - let mutable i = 0 - - for t in tasks do - i <- i + 1 - do! t () |> Task.ignore - - i |> should equal 1000 -} - -[] -let ``CE task: 5000 list of sync-running tasks using for-loop`` () = task { - // runs in a few 100's of ms, because of absense of Task.Delay - // for smoke-test comparison with taskSeq - let tasks = DummyTaskFactory().CreateDirectTasks 5000 - let mutable i = 0 - - for t in tasks do - i <- i + 1 - do! t () |> Task.ignore - - i |> should equal 5000 -} + for i in 0..2 do + yield! createTasks 2 + + for i in 0..2 do + yield! createTasks 2 + + yield! TaskSeq.empty + } + + let! data = tskSeq |> TaskSeq.toListAsync + data |> List.length |> should equal 8512 + data |> should equal (List.ofSeq control) + } + + [] + let ``CE taskSeq with several return!`` () = + logStart output + + task { + // TODO: should we even support this? Traditional 'seq' doesn't. + let tskSeq = taskSeq { + return! createDummyTaskSeq 10 + return! createDummyTaskSeq 5 + } + + let! data = tskSeq |> TaskSeq.toListAsync + + // FIXME!!! This behavior is *probably* not correct + data |> should equal [ 1..10 ] + } + + + [] + let ``CE taskSeq with mixing yield! and yield`` () = + logStart output + + task { + let tskSeq = taskSeq { + yield! createDummyTaskSeq 10 + yield 42 + yield! createDummyTaskSeq 5 + yield 42 + yield! createDummyTaskSeq 10 + yield 42 + yield! createDummyTaskSeq 5 + } + + let! data = tskSeq |> TaskSeq.toListAsync + + data + |> should equal (List.concat [ [ 1..10 ]; [ 42 ]; [ 1..5 ]; [ 42 ]; [ 1..10 ]; [ 42 ]; [ 1..5 ] ]) + } + + [] + let ``CE taskSeq: 1000 TaskDelay-delayed tasks using yield!`` () = + logStart output + + task { + // Smoke performance test + // Runs in slightly over half a second (average of spin-wait, plus small overhead) + // should generally be about as fast as `task`, see below for equivalent test. + let tskSeq = taskSeq { yield! createDummyTaskSeqWith 50L<µs> 1000L<µs> 1000 } + let! data = tskSeq |> TaskSeq.toListAsync + data |> should equal [ 1..1000 ] + } + + [] + let ``CE taskSeq: 1000 sync-running tasks using yield!`` () = + logStart output + + task { + // Smoke performance test + // Runs in a few 10's of ms, because of absense of Task.Delay + // should generally be about as fast as `task`, see below + let tskSeq = taskSeq { yield! createDummyDirectTaskSeq 1000 } + let! data = tskSeq |> TaskSeq.toListAsync + data |> should equal [ 1..1000 ] + } + + [] + let ``CE taskSeq: 5000 sync-running tasks using yield!`` () = + logStart output + + task { + // Smoke performance test + // Compare with task-ce test below. Uses a no-delay hot-started sequence of tasks. + let tskSeq = taskSeq { yield! createDummyDirectTaskSeq 5000 } + let! data = tskSeq |> TaskSeq.toListAsync + data |> should equal [ 1..5000 ] + } + + [] + let ``CE task: 1000 TaskDelay-delayed tasks using for-loop`` () = + logStart output + + task { + // Uses SpinWait for effective task-delaying + // for smoke-test comparison with taskSeq + let tasks = DummyTaskFactory(50L<µs>, 1000L<µs>).CreateDelayedTasks 1000 + let mutable i = 0 + + for t in tasks do + i <- i + 1 + do! t () |> Task.ignore + + i |> should equal 1000 + } + + [] + let ``CE task: 1000 list of sync-running tasks using for-loop`` () = + logStart output + + task { + // runs in a few 10's of ms, because of absense of Task.Delay + // for smoke-test comparison with taskSeq + let tasks = DummyTaskFactory().CreateDirectTasks 1000 + let mutable i = 0 + + for t in tasks do + i <- i + 1 + do! t () |> Task.ignore + + i |> should equal 1000 + } + + [] + let ``CE task: 5000 list of sync-running tasks using for-loop`` () = + logStart output + + task { + // runs in a few 100's of ms, because of absense of Task.Delay + // for smoke-test comparison with taskSeq + let tasks = DummyTaskFactory().CreateDirectTasks 5000 + let mutable i = 0 + + for t in tasks do + i <- i + 1 + do! t () |> Task.ignore + + i |> should equal 5000 + } diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Tests.Other.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Tests.Other.fs index bd0100f7..0f925b02 100644 --- a/src/FSharpy.TaskSeq.Test/TaskSeq.Tests.Other.fs +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Tests.Other.fs @@ -1,4 +1,4 @@ -module FSharpy.Tests.``Other functions`` +namespace FSharpy.Tests open Xunit open FsUnit.Xunit @@ -6,22 +6,32 @@ open FsToolkit.ErrorHandling open FSharpy +type ``Other functions``(output) = -[] -let ``TaskSeq-empty returns an empty sequence`` () = task { - let! sq = TaskSeq.empty |> TaskSeq.toSeqCachedAsync - Seq.isEmpty sq |> should be True - Seq.length sq |> should equal 0 -} - -[] -let ``TaskSeq-isEmpty returns true for empty`` () = task { - let! isEmpty = TaskSeq.empty |> TaskSeq.isEmpty - isEmpty |> should be True -} - -[] -let ``TaskSeq-isEmpty returns false for non-empty`` () = task { - let! isEmpty = taskSeq { yield 42 } |> TaskSeq.isEmpty - isEmpty |> should be False -} + [] + let ``TaskSeq-empty returns an empty sequence`` () = + logStart output + + task { + let! sq = TaskSeq.empty |> TaskSeq.toSeqCachedAsync + Seq.isEmpty sq |> should be True + Seq.length sq |> should equal 0 + } + + [] + let ``TaskSeq-isEmpty returns true for empty`` () = + logStart output + + task { + let! isEmpty = TaskSeq.empty |> TaskSeq.isEmpty + isEmpty |> should be True + } + + [] + let ``TaskSeq-isEmpty returns false for non-empty`` () = + logStart output + + task { + let! isEmpty = taskSeq { yield 42 } |> TaskSeq.isEmpty + isEmpty |> should be False + } diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.ToXXX.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.ToXXX.Tests.fs index 5d57feca..b5395fd0 100644 --- a/src/FSharpy.TaskSeq.Test/TaskSeq.ToXXX.Tests.fs +++ b/src/FSharpy.TaskSeq.Test/TaskSeq.ToXXX.Tests.fs @@ -1,4 +1,4 @@ -module FSharpy.Tests.``Conversion-To`` +namespace FSharpy.Tests open Xunit open FsUnit.Xunit @@ -20,55 +20,72 @@ open System.Collections.Generic /// /// //////////////////////////////////////////////////////////////////////////// -[] -let ``TaskSeq-toArrayAsync should succeed`` () = task { - let tq = createDummyTaskSeq 10 - let! (results: _[]) = tq |> TaskSeq.toArrayAsync - results |> should equal [| 1..10 |] -} - -[] -let ``TaskSeq-toListAsync should succeed`` () = task { - let tq = createDummyTaskSeq 10 - let! (results: list<_>) = tq |> TaskSeq.toListAsync - results |> should equal [ 1..10 ] -} - -[] -let ``TaskSeq-toSeqCachedAsync should succeed`` () = task { - let tq = createDummyTaskSeq 10 - let! (results: seq<_>) = tq |> TaskSeq.toSeqCachedAsync - results |> Seq.toArray |> should equal [| 1..10 |] -} - -[] -let ``TaskSeq-toIListAsync should succeed`` () = task { - let tq = createDummyTaskSeq 10 - let! (results: IList<_>) = tq |> TaskSeq.toIListAsync - results |> Seq.toArray |> should equal [| 1..10 |] -} - -[] -let ``TaskSeq-toResizeArray should succeed`` () = task { - let tq = createDummyTaskSeq 10 - let! (results: ResizeArray<_>) = tq |> TaskSeq.toResizeArrayAsync - results |> Seq.toArray |> should equal [| 1..10 |] -} - -[] -let ``TaskSeq-toArray should succeed and be blocking`` () = - let tq = createDummyTaskSeq 10 - let (results: _[]) = tq |> TaskSeq.toArray - results |> should equal [| 1..10 |] - -[] -let ``TaskSeq-toList should succeed and be blocking`` () = - let tq = createDummyTaskSeq 10 - let (results: list<_>) = tq |> TaskSeq.toList - results |> should equal [ 1..10 ] - -[] -let ``TaskSeq-toSeqCached should succeed and be blocking`` () = - let tq = createDummyTaskSeq 10 - let (results: seq<_>) = tq |> TaskSeq.toSeqCached - results |> Seq.toArray |> should equal [| 1..10 |] +type ``Conversion-To``(output) = + + [] + let ``TaskSeq-toArrayAsync should succeed`` () = + logStart output + + task { + let tq = createDummyTaskSeq 10 + let! (results: _[]) = tq |> TaskSeq.toArrayAsync + results |> should equal [| 1..10 |] + } + + [] + let ``TaskSeq-toListAsync should succeed`` () = + logStart output + + task { + let tq = createDummyTaskSeq 10 + let! (results: list<_>) = tq |> TaskSeq.toListAsync + results |> should equal [ 1..10 ] + } + + [] + let ``TaskSeq-toSeqCachedAsync should succeed`` () = + logStart output + + task { + let tq = createDummyTaskSeq 10 + let! (results: seq<_>) = tq |> TaskSeq.toSeqCachedAsync + results |> Seq.toArray |> should equal [| 1..10 |] + } + + [] + let ``TaskSeq-toIListAsync should succeed`` () = + logStart output + + task { + let tq = createDummyTaskSeq 10 + let! (results: IList<_>) = tq |> TaskSeq.toIListAsync + results |> Seq.toArray |> should equal [| 1..10 |] + } + + [] + let ``TaskSeq-toResizeArray should succeed`` () = + logStart output + + task { + let tq = createDummyTaskSeq 10 + let! (results: ResizeArray<_>) = tq |> TaskSeq.toResizeArrayAsync + results |> Seq.toArray |> should equal [| 1..10 |] + } + + [] + let ``TaskSeq-toArray should succeed and be blocking`` () = + let tq = createDummyTaskSeq 10 + let (results: _[]) = tq |> TaskSeq.toArray + results |> should equal [| 1..10 |] + + [] + let ``TaskSeq-toList should succeed and be blocking`` () = + let tq = createDummyTaskSeq 10 + let (results: list<_>) = tq |> TaskSeq.toList + results |> should equal [ 1..10 ] + + [] + let ``TaskSeq-toSeqCached should succeed and be blocking`` () = + let tq = createDummyTaskSeq 10 + let (results: seq<_>) = tq |> TaskSeq.toSeqCached + results |> Seq.toArray |> should equal [| 1..10 |] diff --git a/src/FSharpy.TaskSeq.Test/TestUtils.fs b/src/FSharpy.TaskSeq.Test/TestUtils.fs index 0f16920d..2d187fa5 100644 --- a/src/FSharpy.TaskSeq.Test/TestUtils.fs +++ b/src/FSharpy.TaskSeq.Test/TestUtils.fs @@ -1,4 +1,4 @@ -namespace FSharpy.Tests +namespace FSharpy.Tests open System open System.Threading @@ -8,6 +8,8 @@ open System.Diagnostics open FsToolkit.ErrorHandling open FSharpy +open Xunit.Abstractions +open System.Reflection /// Milliseconds [] @@ -17,6 +19,13 @@ type ms [] type µs +[] +module Log = + let inline logStart (output: ITestOutputHelper) = + let name = MethodBase.GetCurrentMethod().Name + output.WriteLine $"Starting test: {name}" + + /// Helpers for short waits, as Task.Delay has about 15ms precision. /// Inspired by IoT code: https://github.com/dotnet/iot/pull/235/files module DelayHelper = @@ -65,12 +74,13 @@ type DummyTaskFactory(µsecMin: int64<µs>, µsecMax: int64<µs>) = //let! _ = Task.Delay(rnd ()) let! _ = Task.Delay 0 // this creates a resume state, which seems more efficient than SpinWait.SpinOnce, see DelayHelper. DelayHelper.delayMicroseconds (rnd ()) false - x <- x + 1 + Interlocked.Increment &x |> ignore + //x <- x + 1 return x // this dereferences the variable } let runTaskDirect i = backgroundTask { - x <- x + 1 + Interlocked.Increment &x |> ignore return x } diff --git a/src/FSharpy.TaskSeq.v3.ncrunchsolution b/src/FSharpy.TaskSeq.v3.ncrunchsolution new file mode 100644 index 00000000..10420ac9 --- /dev/null +++ b/src/FSharpy.TaskSeq.v3.ncrunchsolution @@ -0,0 +1,6 @@ + + + True + True + + \ No newline at end of file