diff --git a/paket.dependencies b/paket.dependencies index 3fb6836..fba2022 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -15,6 +15,7 @@ group Test source https://www.nuget.org/api/v2 source https://api.nuget.org/v3/index.json nuget Expecto >= 10.1.0 + nuget Expecto.FsCheck nuget Microsoft.Bcl.TimeProvider nuget Microsoft.Bcl.AsyncInterfaces >= 8 nuget TimeProviderExtensions diff --git a/paket.lock b/paket.lock index c3228da..cbac881 100644 --- a/paket.lock +++ b/paket.lock @@ -364,6 +364,11 @@ NUGET Expecto (10.1) FSharp.Core (>= 7.0.200) - restriction: >= net6.0 Mono.Cecil (>= 0.11.4 < 1.0) - restriction: >= net6.0 + Expecto.FsCheck (10.1) + Expecto (>= 10.1) - restriction: >= net6.0 + FsCheck (>= 2.16.5 < 3.0) - restriction: >= net6.0 + FsCheck (2.16.6) - restriction: >= net6.0 + FSharp.Core (>= 4.2.3) - restriction: || (>= net452) (>= netstandard1.6) FSharp.Control.TaskSeq (0.3) FSharp.Core (>= 6.0.2) - restriction: >= netstandard2.1 FSharp.Core (7.0.401) - restriction: >= netstandard2.1 diff --git a/src/IcedTasks/CancellablePoolingValueTask.fs b/src/IcedTasks/CancellablePoolingValueTask.fs index 3d30f72..cd66e52 100644 --- a/src/IcedTasks/CancellablePoolingValueTask.fs +++ b/src/IcedTasks/CancellablePoolingValueTask.fs @@ -146,62 +146,90 @@ module CancellablePoolingValueTasks = /// Specify a Source of CancellationToken -> ValueTask<_> on the real type to allow type inference to work member inline _.Source - (x: CancellationToken -> ValueTask<_>) + ([] x: CancellationToken -> ValueTask<_>) : CancellationToken -> Awaiter, _> = fun ct -> (x ct) |> Awaitable.GetAwaiter + [] member inline this.MergeSources ( [] left: CancellationToken -> 'Awaiter1, [] right: CancellationToken -> 'Awaiter2 ) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + this.Source( + this.Run( + this.Bind( + (fun ct -> this.Source(ValueTask<_> ct)), + fun ct -> + let left = left ct + let right = right ct + + (this.Bind( + left, + fun leftR -> + this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + )) + ) ) ) - >> Awaitable.GetAwaiter - + [] member inline this.MergeSources ( left: 'Awaiter1, [] right: CancellationToken -> 'Awaiter2 ) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + this.Source( + this.Run( + this.Bind( + (fun ct -> this.Source(ValueTask<_> ct)), + fun ct -> + let right = right ct + + (this.Bind( + left, + fun leftR -> + this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + )) + ) ) ) - >> Awaitable.GetAwaiter - + [] member inline this.MergeSources ( [] left: CancellationToken -> 'Awaiter1, right: 'Awaiter2 ) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + + this.Source( + this.Run( + this.Bind( + (fun ct -> this.Source(ValueTask<_> ct)), + fun ct -> + let left = left ct + + (this.Bind( + left, + fun leftR -> + this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + )) + ) ) ) - >> Awaitable.GetAwaiter - + [] member inline this.MergeSources(left: 'Awaiter1, right: 'Awaiter2) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + this.Source( + this.Run( + this.Bind( + left, + fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + ) ) ) - >> Awaitable.GetAwaiter /// Contains the cancellableTask computation expression builder. diff --git a/src/IcedTasks/CancellableTask.fs b/src/IcedTasks/CancellableTask.fs index 378b614..8b29889 100644 --- a/src/IcedTasks/CancellableTask.fs +++ b/src/IcedTasks/CancellableTask.fs @@ -143,60 +143,88 @@ module CancellableTasks = /// Specify a Source of CancellationToken -> Task<_> on the real type to allow type inference to work member inline _.Source - (x: CancellationToken -> Task<_>) + ([] x: CancellationToken -> Task<_>) : CancellationToken -> Awaiter, _> = fun ct -> Awaitable.GetTaskAwaiter(x ct) + [] member inline this.MergeSources ( [] left: CancellationToken -> 'Awaiter1, [] right: CancellationToken -> 'Awaiter2 ) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + this.Source( + this.Run( + this.Bind( + (fun ct -> this.Source(ValueTask<_> ct)), + fun ct -> + let left = left ct + let right = right ct + + (this.Bind( + left, + fun leftR -> + this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + )) + ) ) ) - >> Awaitable.GetTaskAwaiter - + [] member inline this.MergeSources ( left: 'Awaiter1, [] right: CancellationToken -> 'Awaiter2 ) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + this.Source( + this.Run( + this.Bind( + (fun ct -> this.Source(ValueTask<_> ct)), + fun ct -> + let right = right ct + + (this.Bind( + left, + fun leftR -> + this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + )) + ) ) ) - >> Awaitable.GetTaskAwaiter - + [] member inline this.MergeSources ( [] left: CancellationToken -> 'Awaiter1, right: 'Awaiter2 ) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + + this.Source( + this.Run( + this.Bind( + (fun ct -> this.Source(ValueTask<_> ct)), + fun ct -> + let left = left ct + + (this.Bind( + left, + fun leftR -> + this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + )) + ) ) ) - >> Awaitable.GetTaskAwaiter - + [] member inline this.MergeSources(left: 'Awaiter1, right: 'Awaiter2) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + this.Source( + this.Run( + this.Bind( + left, + fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + ) ) ) - >> Awaitable.GetTaskAwaiter /// Contains methods to build CancellableTasks using the F# computation expression syntax diff --git a/src/IcedTasks/CancellableValueTask.fs b/src/IcedTasks/CancellableValueTask.fs index 508ef27..a51e228 100644 --- a/src/IcedTasks/CancellableValueTask.fs +++ b/src/IcedTasks/CancellableValueTask.fs @@ -143,62 +143,90 @@ module CancellableValueTasks = /// Specify a Source of CancellationToken -> ValueTask<_> on the real type to allow type inference to work member inline _.Source - (x: CancellationToken -> ValueTask<_>) + ([] x: CancellationToken -> ValueTask<_>) : CancellationToken -> Awaiter, _> = fun ct -> (x ct) |> Awaitable.GetAwaiter + [] member inline this.MergeSources ( [] left: CancellationToken -> 'Awaiter1, [] right: CancellationToken -> 'Awaiter2 ) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + this.Source( + this.Run( + this.Bind( + (fun ct -> this.Source(ValueTask<_> ct)), + fun ct -> + let left = left ct + let right = right ct + + (this.Bind( + left, + fun leftR -> + this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + )) + ) ) ) - >> Awaitable.GetAwaiter - + [] member inline this.MergeSources ( left: 'Awaiter1, [] right: CancellationToken -> 'Awaiter2 ) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + this.Source( + this.Run( + this.Bind( + (fun ct -> this.Source(ValueTask<_> ct)), + fun ct -> + let right = right ct + + (this.Bind( + left, + fun leftR -> + this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + )) + ) ) ) - >> Awaitable.GetAwaiter - + [] member inline this.MergeSources ( [] left: CancellationToken -> 'Awaiter1, right: 'Awaiter2 ) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + + this.Source( + this.Run( + this.Bind( + (fun ct -> this.Source(ValueTask<_> ct)), + fun ct -> + let left = left ct + + (this.Bind( + left, + fun leftR -> + this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + )) + ) ) ) - >> Awaitable.GetAwaiter - + [] member inline this.MergeSources(left: 'Awaiter1, right: 'Awaiter2) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + this.Source( + this.Run( + this.Bind( + left, + fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + ) ) ) - >> Awaitable.GetAwaiter /// Contains the cancellableTask computation expression builder. diff --git a/src/IcedTasks/PoolingValueTask.fs b/src/IcedTasks/PoolingValueTask.fs index 933a59e..5571f09 100644 --- a/src/IcedTasks/PoolingValueTask.fs +++ b/src/IcedTasks/PoolingValueTask.fs @@ -125,14 +125,16 @@ module PoolingValueTasks = /// Specify a Source of ValueTask<_> on the real type to allow type inference to work member inline _.Source(v: ValueTask<_>) = Awaitable.GetAwaiter v + [] member inline this.MergeSources(left, right) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + this.Source( + this.Run( + this.Bind( + left, + fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + ) ) ) - |> Awaitable.GetAwaiter /// Contains the poolingValueTask computation expression builder. diff --git a/src/IcedTasks/Task.fs b/src/IcedTasks/Task.fs index 683fff7..3bdc4e1 100644 --- a/src/IcedTasks/Task.fs +++ b/src/IcedTasks/Task.fs @@ -128,14 +128,16 @@ module Tasks = /// Specify a Source of Task<_> on the real type to allow type inference to work member inline _.Source(v: Task<_>) = Awaitable.GetTaskAwaiter v + [] member inline this.MergeSources(left, right) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + this.Source( + this.Run( + this.Bind( + left, + fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + ) ) ) - |> Awaitable.GetTaskAwaiter /// Contains methods to build Tasks using the F# computation expression syntax @@ -214,14 +216,16 @@ module Tasks = /// Specify a Source of Task<_> on the real type to allow type inference to work member inline _.Source(v: Task<_>) = Awaitable.GetTaskAwaiter v + [] member inline this.MergeSources(left, right) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + this.Source( + this.Run( + this.Bind( + left, + fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + ) ) ) - |> Awaitable.GetTaskAwaiter /// Contains the task computation expression builder. [] diff --git a/src/IcedTasks/TaskLike.fs b/src/IcedTasks/TaskLike.fs index 041cac9..fa9fd35 100644 --- a/src/IcedTasks/TaskLike.fs +++ b/src/IcedTasks/TaskLike.fs @@ -2,6 +2,7 @@ namespace IcedTasks open System.Runtime.CompilerServices open Microsoft.FSharp.Core.CompilerServices +open System.Threading /// /// @@ -18,6 +19,15 @@ type Awaiter<'Awaiter, 'TResult and 'Awaiter: (member get_IsCompleted: unit -> bool) and 'Awaiter: (member GetResult: unit -> 'TResult)> = 'Awaiter +/// A structure that looks like an Awaiter that returns a Result type +type AwaiterOfResult<'Awaiter, 'TResult, 'Error + when 'Awaiter :> ICriticalNotifyCompletion + and 'Awaiter: (member get_IsCompleted: unit -> bool) + and 'Awaiter: (member GetResult: unit -> Result<'TResult, 'Error>)> = 'Awaiter + +type CancellableAwaiter<'Awaiter, 'TResult when Awaiter<'Awaiter, 'TResult>> = + CancellationToken -> Awaiter<'Awaiter, 'TResult> + /// Functions for Awaiters type Awaiter = /// Gets a value that indicates whether the asynchronous task has completed diff --git a/src/IcedTasks/TaskUnit.fs b/src/IcedTasks/TaskUnit.fs index 6fbbabf..1e3a9f1 100644 --- a/src/IcedTasks/TaskUnit.fs +++ b/src/IcedTasks/TaskUnit.fs @@ -134,15 +134,16 @@ module TasksUnit = /// Specify a Source of Task on the real type to allow type inference to work member inline _.Source(v: Task) = Awaitable.GetAwaiter v + [] member inline this.MergeSources(left, right) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + this.Source( + this.Run( + this.Bind( + left, + fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + ) ) ) - |> Awaitable.GetAwaiter - /// Contains methods to build Tasks using the F# computation expression syntax type BackgroundTaskUnitBuilder() = @@ -225,14 +226,16 @@ module TasksUnit = /// Specify a Source of Task on the real type to allow type inference to work member inline _.Source(v: Task) = Awaitable.GetAwaiter v + [] member inline this.MergeSources(left, right) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + this.Source( + this.Run( + this.Bind( + left, + fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + ) ) ) - |> Awaitable.GetAwaiter /// Contains the taskUnit computation expression builder. [] diff --git a/src/IcedTasks/ValueTask.fs b/src/IcedTasks/ValueTask.fs index abe48a6..0a9c377 100644 --- a/src/IcedTasks/ValueTask.fs +++ b/src/IcedTasks/ValueTask.fs @@ -188,13 +188,14 @@ module ValueTasks = member inline _.Source(v: ValueTask<_>) = Awaitable.GetAwaiter v member inline this.MergeSources(left, right) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + this.Source( + this.Run( + this.Bind( + left, + fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + ) ) ) - |> Awaitable.GetAwaiter /// Contains the valueTask computation expression builder. [] diff --git a/src/IcedTasks/ValueTaskUnit.fs b/src/IcedTasks/ValueTaskUnit.fs index 57e1b17..44d4f73 100644 --- a/src/IcedTasks/ValueTaskUnit.fs +++ b/src/IcedTasks/ValueTaskUnit.fs @@ -137,14 +137,16 @@ module ValueTasksUnit = /// Specify a Source of ValueTask on the real type to allow type inference to work member inline _.Source(v: ValueTask) = Awaitable.GetAwaiter v + [] member inline this.MergeSources(left, right) = - this.Run( - this.Bind( - left, - fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + this.Source( + this.Run( + this.Bind( + left, + fun leftR -> this.BindReturn(right, (fun rightR -> struct (leftR, rightR))) + ) ) ) - |> Awaitable.GetAwaiter /// Contains the valueTask computation expression builder. [] diff --git a/tests/IcedTasks.Tests.NS20/paket.references b/tests/IcedTasks.Tests.NS20/paket.references index c9ecf3b..3deef8f 100644 --- a/tests/IcedTasks.Tests.NS20/paket.references +++ b/tests/IcedTasks.Tests.NS20/paket.references @@ -1,6 +1,7 @@ group Test FSharp.Core Expecto +Expecto.FsCheck Microsoft.NET.Test.Sdk YoloDev.Expecto.TestSdk TimeProviderExtensions diff --git a/tests/IcedTasks.Tests.NS21/paket.references b/tests/IcedTasks.Tests.NS21/paket.references index 900398f..b320237 100644 --- a/tests/IcedTasks.Tests.NS21/paket.references +++ b/tests/IcedTasks.Tests.NS21/paket.references @@ -1,6 +1,7 @@ group Test FSharp.Core Expecto +Expecto.FsCheck Microsoft.NET.Test.Sdk YoloDev.Expecto.TestSdk TimeProviderExtensions diff --git a/tests/IcedTasks.Tests/CancellablePoolingValueTaskTests.fs b/tests/IcedTasks.Tests/CancellablePoolingValueTaskTests.fs index 760f0ac..ea6c6b9 100644 --- a/tests/IcedTasks.Tests/CancellablePoolingValueTaskTests.fs +++ b/tests/IcedTasks.Tests/CancellablePoolingValueTaskTests.fs @@ -853,12 +853,98 @@ module CancellablePoolingValueTaskTests = } ] + testList "MergeSources" [ - testCaseAsync "and! 6" + + testCaseAsync "and! cancellableTask x cancellableTask" + <| async { + let! actual = + cancellablePoolingValueTask { + let! a = cancellablePoolingValueTask { return 1 } + and! b = cancellablePoolingValueTask { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! cancellableTask x task" <| async { let! actual = cancellablePoolingValueTask { let! a = cancellablePoolingValueTask { return 1 } + and! b = task { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! task x cancellableTask" + <| async { + let! actual = + cancellablePoolingValueTask { + let! a = task { return 1 } + and! b = cancellablePoolingValueTask { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! task x task" + <| async { + let! actual = + cancellablePoolingValueTask { + let! a = task { return 1 } + and! b = task { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableT" + <| async { + let! actual = + cancellablePoolingValueTask { + let! a = valueTask { return 1 } + and! b = valueTask { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableUnit" + <| async { + let! actual = + cancellablePoolingValueTask { + let! a = valueTask { return 2 } + and! _ = Task.Yield() + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! awaitableUnit x awaitableT " + <| async { + let! actual = + cancellablePoolingValueTask { + let! _ = Task.Yield() + and! a = valueTask { return 2 } + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! 6 random" + <| async { + let! actual = + cancellablePoolingValueTask { + let! a = cancellableTask { return 1 } and! b = coldTask { return 2 } and! _ = Task.Yield() and! _ = ValueTask.CompletedTask @@ -867,8 +953,98 @@ module CancellablePoolingValueTaskTests = } Expect.equal actual 6 "" - } + + ] + + testList "MergeSourcesParallel" [ + testPropertyWithConfig Expecto.fsCheckConfig "parallelism" + <| fun () -> + asyncEx { + let! ct = Async.CancellationToken + let sequencedList = ResizeArray<_>() + let parallelList = ResizeArray<_>() + + let doOtherStuff (l: ResizeArray<_>) x = + cancellablePoolingValueTask { + l.Add(x) + do! Task.Delay(15) + let dt = DateTimeOffset.UtcNow + l.Add(x) + return dt + } + + let! sequenced = + cancellablePoolingValueTask { + let! a = doOtherStuff sequencedList 1 + let! b = doOtherStuff sequencedList 2 + let! c = doOtherStuff sequencedList 3 + let! d = doOtherStuff sequencedList 4 + let! e = doOtherStuff sequencedList 5 + let! f = doOtherStuff sequencedList 6 + + return [ + a + b + c + d + e + f + ] + } + + let! paralleled = + cancellablePoolingValueTask { + let! a = doOtherStuff parallelList 1 + and! b = doOtherStuff parallelList 2 + and! c = doOtherStuff parallelList 3 + and! d = doOtherStuff parallelList 4 + and! e = doOtherStuff parallelList 5 + and! f = doOtherStuff parallelList 6 + + return [ + a + b + c + d + e + f + ] + } + + let sequencedEntrances = + sequencedList + |> Seq.toList + + let parallelEntrances = + parallelList + |> Seq.toList + + let sequencedAlwaysOrdered = + sequencedEntrances = [ + 1 + 1 + 2 + 2 + 3 + 3 + 4 + 4 + 5 + 5 + 6 + 6 + ] + + let parallelNotSequenced = + parallelEntrances + <> sequencedEntrances + + return + sequencedAlwaysOrdered + && parallelNotSequenced + } + |> Async.RunSynchronously ] testList "Cancellation Semantics" [ diff --git a/tests/IcedTasks.Tests/CancellableTaskTests.fs b/tests/IcedTasks.Tests/CancellableTaskTests.fs index 3282d53..aca6c51 100644 --- a/tests/IcedTasks.Tests/CancellableTaskTests.fs +++ b/tests/IcedTasks.Tests/CancellableTaskTests.fs @@ -812,12 +812,94 @@ module CancellableTaskTests = } ) } - ] + testList "MergeSources" [ + testCaseAsync "and! cancellableTask x cancellableTask" + <| async { + let! actual = + cancellableTask { + let! a = cancellableTask { return 1 } + and! b = cancellableTask { return 2 } + return a + b + } - testList "MergeSources" [ - testCaseAsync "and! 6" + Expect.equal actual 3 "" + } + + testCaseAsync "and! cancellableTask x task" + <| async { + let! actual = + cancellableTask { + let! a = cancellableTask { return 1 } + and! b = task { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! task x cancellableTask" + <| async { + let! actual = + cancellableTask { + let! a = task { return 1 } + and! b = cancellableTask { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! task x task" + <| async { + let! actual = + cancellableTask { + let! a = task { return 1 } + and! b = task { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableT" + <| async { + let! actual = + cancellableTask { + let! a = valueTask { return 1 } + and! b = valueTask { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableUnit" + <| async { + let! actual = + cancellableTask { + let! a = valueTask { return 2 } + and! _ = Task.Yield() + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! awaitableUnit x awaitableT " + <| async { + let! actual = + cancellableTask { + let! _ = Task.Yield() + and! a = valueTask { return 2 } + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! 6 random" <| async { let! actual = cancellableTask { @@ -830,8 +912,98 @@ module CancellableTaskTests = } Expect.equal actual 6 "" - } + + ] + + testList "MergeSourcesParallel" [ + testPropertyWithConfig Expecto.fsCheckConfig "parallelism" + <| fun () -> + asyncEx { + let! ct = Async.CancellationToken + let sequencedList = ResizeArray<_>() + let parallelList = ResizeArray<_>() + + let doOtherStuff (l: ResizeArray<_>) x = + cancellableTask { + l.Add(x) + do! Task.Delay(15) + let dt = DateTimeOffset.UtcNow + l.Add(x) + return dt + } + + let! sequenced = + cancellableTask { + let! a = doOtherStuff sequencedList 1 + let! b = doOtherStuff sequencedList 2 + let! c = doOtherStuff sequencedList 3 + let! d = doOtherStuff sequencedList 4 + let! e = doOtherStuff sequencedList 5 + let! f = doOtherStuff sequencedList 6 + + return [ + a + b + c + d + e + f + ] + } + + let! paralleled = + cancellableTask { + let! a = doOtherStuff parallelList 1 + and! b = doOtherStuff parallelList 2 + and! c = doOtherStuff parallelList 3 + and! d = doOtherStuff parallelList 4 + and! e = doOtherStuff parallelList 5 + and! f = doOtherStuff parallelList 6 + + return [ + a + b + c + d + e + f + ] + } + + let sequencedEntrances = + sequencedList + |> Seq.toList + + let parallelEntrances = + parallelList + |> Seq.toList + + let sequencedAlwaysOrdered = + sequencedEntrances = [ + 1 + 1 + 2 + 2 + 3 + 3 + 4 + 4 + 5 + 5 + 6 + 6 + ] + + let parallelNotSequenced = + parallelEntrances + <> sequencedEntrances + + return + sequencedAlwaysOrdered + && parallelNotSequenced + } + |> Async.RunSynchronously ] testList "Cancellation Semantics" [ diff --git a/tests/IcedTasks.Tests/CancellableValueTaskTests.fs b/tests/IcedTasks.Tests/CancellableValueTaskTests.fs index 57d5391..479a9c3 100644 --- a/tests/IcedTasks.Tests/CancellableValueTaskTests.fs +++ b/tests/IcedTasks.Tests/CancellableValueTaskTests.fs @@ -854,7 +854,91 @@ module CancellableValueTaskTests = ] testList "MergeSources" [ - testCaseAsync "and! 6" + testCaseAsync "and! cancellableTask x cancellableTask" + <| async { + let! actual = + cancellableValueTask { + let! a = cancellableValueTask { return 1 } + and! b = cancellableValueTask { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! cancellableTask x task" + <| async { + let! actual = + cancellableValueTask { + let! a = cancellableValueTask { return 1 } + and! b = task { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! task x cancellableTask" + <| async { + let! actual = + cancellableValueTask { + let! a = task { return 1 } + and! b = cancellableValueTask { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! task x task" + <| async { + let! actual = + cancellableValueTask { + let! a = task { return 1 } + and! b = task { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableT" + <| async { + let! actual = + cancellableValueTask { + let! a = valueTask { return 1 } + and! b = valueTask { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableUnit" + <| async { + let! actual = + cancellableValueTask { + let! a = valueTask { return 2 } + and! _ = Task.Yield() + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! awaitableUnit x awaitableT " + <| async { + let! actual = + cancellableValueTask { + let! _ = Task.Yield() + and! a = valueTask { return 2 } + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! 6 random" <| async { let! actual = cancellableValueTask { @@ -867,10 +951,100 @@ module CancellableValueTaskTests = } Expect.equal actual 6 "" - } ] + testList "MergeSourcesParallel" [ + testPropertyWithConfig Expecto.fsCheckConfig "parallelism" + <| fun () -> + asyncEx { + let! ct = Async.CancellationToken + let sequencedList = ResizeArray<_>() + let parallelList = ResizeArray<_>() + + let doOtherStuff (l: ResizeArray<_>) x = + cancellableValueTask { + l.Add(x) + do! Task.Delay(15) + let dt = DateTimeOffset.UtcNow + l.Add(x) + return dt + } + + let! sequenced = + cancellableValueTask { + let! a = doOtherStuff sequencedList 1 + let! b = doOtherStuff sequencedList 2 + let! c = doOtherStuff sequencedList 3 + let! d = doOtherStuff sequencedList 4 + let! e = doOtherStuff sequencedList 5 + let! f = doOtherStuff sequencedList 6 + + return [ + a + b + c + d + e + f + ] + } + + let! paralleled = + cancellableValueTask { + let! a = doOtherStuff parallelList 1 + and! b = doOtherStuff parallelList 2 + and! c = doOtherStuff parallelList 3 + and! d = doOtherStuff parallelList 4 + and! e = doOtherStuff parallelList 5 + and! f = doOtherStuff parallelList 6 + + return [ + a + b + c + d + e + f + ] + } + + let sequencedEntrances = + sequencedList + |> Seq.toList + + let parallelEntrances = + parallelList + |> Seq.toList + + let sequencedAlwaysOrdered = + sequencedEntrances = [ + 1 + 1 + 2 + 2 + 3 + 3 + 4 + 4 + 5 + 5 + 6 + 6 + ] + + let parallelNotSequenced = + parallelEntrances + <> sequencedEntrances + + return + sequencedAlwaysOrdered + && parallelNotSequenced + } + |> Async.RunSynchronously + ] + + testList "Cancellation Semantics" [ testCaseAsync "Simple Cancellation" diff --git a/tests/IcedTasks.Tests/Expect.fs b/tests/IcedTasks.Tests/Expect.fs index 3a719af..4507cc5 100644 --- a/tests/IcedTasks.Tests/Expect.fs +++ b/tests/IcedTasks.Tests/Expect.fs @@ -22,6 +22,40 @@ module TestHelpers = SynchronizationContext.SetSynchronizationContext newContext makeDisposable (fun () -> SynchronizationContext.SetSynchronizationContext oldContext) + +module Expecto = + open Expecto + + let environVarAsBoolOrDefault varName defaultValue = + let truthyConsts = [ + "1" + "Y" + "YES" + "T" + "TRUE" + ] + + try + let envvar = (Environment.GetEnvironmentVariable varName).ToUpper() + + truthyConsts + |> List.exists ((=) envvar) + with _ -> + defaultValue + + let isInCI () = environVarAsBoolOrDefault "CI" false + + let fsCheckConfig = + if isInCI () then + // these tests can be slow on CI so reduce the number of tests + { + FsCheckConfig.defaultConfig with + maxTest = 10 + } + else + FsCheckConfig.defaultConfig + + module Expect = open Expecto diff --git a/tests/IcedTasks.Tests/PoolingValueTaskDynamicTests.fs b/tests/IcedTasks.Tests/PoolingValueTaskDynamicTests.fs index c4a88b0..2eeae9d 100644 --- a/tests/IcedTasks.Tests/PoolingValueTaskDynamicTests.fs +++ b/tests/IcedTasks.Tests/PoolingValueTaskDynamicTests.fs @@ -627,9 +627,59 @@ module PoolingValueTaskDynamicTests = } ) ] + testList "MergeSources" [ - testCaseAsync "and! 5" - <| async { + + testCaseAsync "and! task x task" + <| asyncEx { + let! actual = + dPoolingValueTask { + let! a = task { return 1 } + and! b = task { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableT" + <| asyncEx { + let! actual = + dPoolingValueTask { + let! a = valueTask { return 1 } + and! b = valueTask { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableUnit" + <| asyncEx { + let! actual = + dPoolingValueTask { + let! a = valueTask { return 2 } + and! _ = Task.Yield() + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! awaitableUnit x awaitableT " + <| asyncEx { + let! actual = + dPoolingValueTask { + let! _ = Task.Yield() + and! a = valueTask { return 2 } + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! 5 random" + <| asyncEx { let! actual = dPoolingValueTask { let! a = ValueTask.FromResult 1 @@ -637,13 +687,10 @@ module PoolingValueTaskDynamicTests = and! c = coldTask { return 3 } and! _ = Task.Yield() and! _ = ValueTask.CompletedTask - // and! c = fun () -> PoolingValueTask.FromResult(3) return a + b + c } - |> Async.AwaitValueTask Expect.equal actual 6 "" - } ] ] diff --git a/tests/IcedTasks.Tests/PoolingValueTaskTests.fs b/tests/IcedTasks.Tests/PoolingValueTaskTests.fs index 03f372b..c99392c 100644 --- a/tests/IcedTasks.Tests/PoolingValueTaskTests.fs +++ b/tests/IcedTasks.Tests/PoolingValueTaskTests.fs @@ -612,9 +612,59 @@ module PoolingValueTaskTests = } ) ] + testList "MergeSources" [ - testCaseAsync "and! 5" - <| async { + + testCaseAsync "and! task x task" + <| asyncEx { + let! actual = + poolingValueTask { + let! a = task { return 1 } + and! b = task { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableT" + <| asyncEx { + let! actual = + poolingValueTask { + let! a = valueTask { return 1 } + and! b = valueTask { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableUnit" + <| asyncEx { + let! actual = + poolingValueTask { + let! a = valueTask { return 2 } + and! _ = Task.Yield() + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! awaitableUnit x awaitableT " + <| asyncEx { + let! actual = + poolingValueTask { + let! _ = Task.Yield() + and! a = valueTask { return 2 } + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! 5 random" + <| asyncEx { let! actual = poolingValueTask { let! a = ValueTask.FromResult 1 @@ -622,14 +672,102 @@ module PoolingValueTaskTests = and! c = coldTask { return 3 } and! _ = Task.Yield() and! _ = ValueTask.CompletedTask - // and! c = fun () -> PoolingValueTask.FromResult(3) return a + b + c } - |> Async.AwaitValueTask Expect.equal actual 6 "" - } + + ] + + testList "MergeSourcesParallel" [ + testPropertyWithConfig Expecto.fsCheckConfig "parallelism" + <| fun () -> + asyncEx { + let! ct = Async.CancellationToken + let sequencedList = ResizeArray<_>() + let parallelList = ResizeArray<_>() + + let doOtherStuff (l: ResizeArray<_>) x = + poolingValueTask { + l.Add(x) + do! Task.Delay(15) + let dt = DateTimeOffset.UtcNow + l.Add(x) + return dt + } + + let! sequenced = + poolingValueTask { + let! a = doOtherStuff sequencedList 1 + let! b = doOtherStuff sequencedList 2 + let! c = doOtherStuff sequencedList 3 + let! d = doOtherStuff sequencedList 4 + let! e = doOtherStuff sequencedList 5 + let! f = doOtherStuff sequencedList 6 + + return [ + a + b + c + d + e + f + ] + } + + let! paralleled = + poolingValueTask { + let! a = doOtherStuff parallelList 1 + and! b = doOtherStuff parallelList 2 + and! c = doOtherStuff parallelList 3 + and! d = doOtherStuff parallelList 4 + and! e = doOtherStuff parallelList 5 + and! f = doOtherStuff parallelList 6 + + return [ + a + b + c + d + e + f + ] + } + + let sequencedEntrances = + sequencedList + |> Seq.toList + + let parallelEntrances = + parallelList + |> Seq.toList + + let sequencedAlwaysOrdered = + sequencedEntrances = [ + 1 + 1 + 2 + 2 + 3 + 3 + 4 + 4 + 5 + 5 + 6 + 6 + ] + + let parallelNotSequenced = + parallelEntrances + <> sequencedEntrances + + return + sequencedAlwaysOrdered + && parallelNotSequenced + } + |> Async.RunSynchronously ] ] diff --git a/tests/IcedTasks.Tests/TaskBackgroundTests.fs b/tests/IcedTasks.Tests/TaskBackgroundTests.fs index 96e47bb..f1e3636 100644 --- a/tests/IcedTasks.Tests/TaskBackgroundTests.fs +++ b/tests/IcedTasks.Tests/TaskBackgroundTests.fs @@ -615,9 +615,59 @@ module TaskBackgroundTests = ) ] + testList "MergeSources" [ - testCaseAsync "and! 5" - <| async { + + testCaseAsync "and! task x task" + <| asyncEx { + let! actual = + backgroundTask { + let! a = task { return 1 } + and! b = task { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableT" + <| asyncEx { + let! actual = + backgroundTask { + let! a = valueTask { return 1 } + and! b = valueTask { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableUnit" + <| asyncEx { + let! actual = + backgroundTask { + let! a = valueTask { return 2 } + and! _ = Task.Yield() + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! awaitableUnit x awaitableT " + <| asyncEx { + let! actual = + backgroundTask { + let! _ = Task.Yield() + and! a = valueTask { return 2 } + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! 5 random" + <| asyncEx { let! actual = backgroundTask { let! a = ValueTask.FromResult 1 @@ -627,11 +677,10 @@ module TaskBackgroundTests = and! _ = ValueTask.CompletedTask return a + b + c } - |> Async.AwaitTask Expect.equal actual 6 "" - } + ] ] diff --git a/tests/IcedTasks.Tests/TaskDynamicTests.fs b/tests/IcedTasks.Tests/TaskDynamicTests.fs index 635c8a8..dbac837 100644 --- a/tests/IcedTasks.Tests/TaskDynamicTests.fs +++ b/tests/IcedTasks.Tests/TaskDynamicTests.fs @@ -627,9 +627,58 @@ module TaskDynamicTests = ) ] + testList "MergeSources" [ - testCaseAsync "and! 5" - <| async { + testCaseAsync "and! task x task" + <| asyncEx { + let! actual = + dTask { + let! a = task { return 1 } + and! b = task { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableT" + <| asyncEx { + let! actual = + dTask { + let! a = valueTask { return 1 } + and! b = valueTask { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableUnit" + <| asyncEx { + let! actual = + dTask { + let! a = valueTask { return 2 } + and! _ = Task.Yield() + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! awaitableUnit x awaitableT " + <| asyncEx { + let! actual = + dTask { + let! _ = Task.Yield() + and! a = valueTask { return 2 } + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! 5 random" + <| asyncEx { let! actual = dTask { let! a = ValueTask.FromResult 1 @@ -639,11 +688,10 @@ module TaskDynamicTests = and! _ = ValueTask.CompletedTask return a + b + c } - |> Async.AwaitTask Expect.equal actual 6 "" - } + ] ] diff --git a/tests/IcedTasks.Tests/TaskTests.fs b/tests/IcedTasks.Tests/TaskTests.fs index 98259ca..913b5cc 100644 --- a/tests/IcedTasks.Tests/TaskTests.fs +++ b/tests/IcedTasks.Tests/TaskTests.fs @@ -615,9 +615,59 @@ module TaskTests = ) ] + testList "MergeSources" [ - testCaseAsync "and! 5" - <| async { + + testCaseAsync "and! task x task" + <| asyncEx { + let! actual = + task { + let! a = task { return 1 } + and! b = task { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableT" + <| asyncEx { + let! actual = + task { + let! a = valueTask { return 1 } + and! b = valueTask { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableUnit" + <| asyncEx { + let! actual = + task { + let! a = valueTask { return 2 } + and! _ = Task.Yield() + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! awaitableUnit x awaitableT " + <| asyncEx { + let! actual = + task { + let! _ = Task.Yield() + and! a = valueTask { return 2 } + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! 5 random" + <| asyncEx { let! actual = task { let! a = ValueTask.FromResult 1 @@ -627,11 +677,100 @@ module TaskTests = and! _ = ValueTask.CompletedTask return a + b + c } - |> Async.AwaitTask Expect.equal actual 6 "" - } + + ] + + testList "MergeSourcesParallel" [ + testPropertyWithConfig Expecto.fsCheckConfig "parallelism" + <| fun () -> + asyncEx { + let! ct = Async.CancellationToken + let sequencedList = ResizeArray<_>() + let parallelList = ResizeArray<_>() + + let doOtherStuff (l: ResizeArray<_>) x = + task { + l.Add(x) + do! Task.Delay(15) + let dt = DateTimeOffset.UtcNow + l.Add(x) + return dt + } + + let! sequenced = + task { + let! a = doOtherStuff sequencedList 1 + let! b = doOtherStuff sequencedList 2 + let! c = doOtherStuff sequencedList 3 + let! d = doOtherStuff sequencedList 4 + let! e = doOtherStuff sequencedList 5 + let! f = doOtherStuff sequencedList 6 + + return [ + a + b + c + d + e + f + ] + } + + let! paralleled = + task { + let! a = doOtherStuff parallelList 1 + and! b = doOtherStuff parallelList 2 + and! c = doOtherStuff parallelList 3 + and! d = doOtherStuff parallelList 4 + and! e = doOtherStuff parallelList 5 + and! f = doOtherStuff parallelList 6 + + return [ + a + b + c + d + e + f + ] + } + + let sequencedEntrances = + sequencedList + |> Seq.toList + + let parallelEntrances = + parallelList + |> Seq.toList + + let sequencedAlwaysOrdered = + sequencedEntrances = [ + 1 + 1 + 2 + 2 + 3 + 3 + 4 + 4 + 5 + 5 + 6 + 6 + ] + + let parallelNotSequenced = + parallelEntrances + <> sequencedEntrances + + return + sequencedAlwaysOrdered + && parallelNotSequenced + } + |> Async.RunSynchronously ] ] diff --git a/tests/IcedTasks.Tests/ValueTaskDynamicTests.fs b/tests/IcedTasks.Tests/ValueTaskDynamicTests.fs index dac25de..fcf3c5f 100644 --- a/tests/IcedTasks.Tests/ValueTaskDynamicTests.fs +++ b/tests/IcedTasks.Tests/ValueTaskDynamicTests.fs @@ -626,9 +626,59 @@ module ValueTaskDynamicTests = ) ] + testList "MergeSources" [ - testCaseAsync "and! 5" - <| async { + + testCaseAsync "and! task x task" + <| asyncEx { + let! actual = + dValueTask { + let! a = task { return 1 } + and! b = task { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableT" + <| asyncEx { + let! actual = + dValueTask { + let! a = valueTask { return 1 } + and! b = valueTask { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableUnit" + <| asyncEx { + let! actual = + dValueTask { + let! a = valueTask { return 2 } + and! _ = Task.Yield() + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! awaitableUnit x awaitableT " + <| asyncEx { + let! actual = + dValueTask { + let! _ = Task.Yield() + and! a = valueTask { return 2 } + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! 5 random" + <| asyncEx { let! actual = dValueTask { let! a = ValueTask.FromResult 1 @@ -638,10 +688,8 @@ module ValueTaskDynamicTests = and! _ = ValueTask.CompletedTask return a + b + c } - |> Async.AwaitValueTask Expect.equal actual 6 "" - } ] ] diff --git a/tests/IcedTasks.Tests/ValueTaskTests.fs b/tests/IcedTasks.Tests/ValueTaskTests.fs index 9e1fc6b..4144188 100644 --- a/tests/IcedTasks.Tests/ValueTaskTests.fs +++ b/tests/IcedTasks.Tests/ValueTaskTests.fs @@ -616,8 +616,56 @@ module ValueTaskTests = ] testList "MergeSources" [ - testCaseAsync "and! 5" - <| async { + testCaseAsync "and! task x task" + <| asyncEx { + let! actual = + valueTask { + let! a = task { return 1 } + and! b = task { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableT" + <| asyncEx { + let! actual = + valueTask { + let! a = valueTask { return 1 } + and! b = valueTask { return 2 } + return a + b + } + + Expect.equal actual 3 "" + } + + testCaseAsync "and! awaitableT x awaitableUnit" + <| asyncEx { + let! actual = + valueTask { + let! a = valueTask { return 2 } + and! _ = Task.Yield() + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! awaitableUnit x awaitableT " + <| asyncEx { + let! actual = + valueTask { + let! _ = Task.Yield() + and! a = valueTask { return 2 } + return a + } + + Expect.equal actual 2 "" + } + + testCaseAsync "and! 5 random" + <| asyncEx { let! actual = valueTask { let! a = ValueTask.FromResult 1 @@ -625,15 +673,102 @@ module ValueTaskTests = and! c = coldTask { return 3 } and! _ = Task.Yield() and! _ = ValueTask.CompletedTask - // and! c = fun () -> ValueTask.FromResult(3) return a + b + c } - |> Async.AwaitValueTask Expect.equal actual 6 "" - } ] + + testList "MergeSourcesParallel" [ + testPropertyWithConfig Expecto.fsCheckConfig "parallelism" + <| fun () -> + asyncEx { + let! ct = Async.CancellationToken + let sequencedList = ResizeArray<_>() + let parallelList = ResizeArray<_>() + + let doOtherStuff (l: ResizeArray<_>) x = + valueTask { + l.Add(x) + do! Task.Delay(15) + let dt = DateTimeOffset.UtcNow + l.Add(x) + return dt + } + + let! sequenced = + valueTask { + let! a = doOtherStuff sequencedList 1 + let! b = doOtherStuff sequencedList 2 + let! c = doOtherStuff sequencedList 3 + let! d = doOtherStuff sequencedList 4 + let! e = doOtherStuff sequencedList 5 + let! f = doOtherStuff sequencedList 6 + + return [ + a + b + c + d + e + f + ] + } + + let! paralleled = + valueTask { + let! a = doOtherStuff parallelList 1 + and! b = doOtherStuff parallelList 2 + and! c = doOtherStuff parallelList 3 + and! d = doOtherStuff parallelList 4 + and! e = doOtherStuff parallelList 5 + and! f = doOtherStuff parallelList 6 + + return [ + a + b + c + d + e + f + ] + } + + let sequencedEntrances = + sequencedList + |> Seq.toList + + let parallelEntrances = + parallelList + |> Seq.toList + + let sequencedAlwaysOrdered = + sequencedEntrances = [ + 1 + 1 + 2 + 2 + 3 + 3 + 4 + 4 + 5 + 5 + 6 + 6 + ] + + let parallelNotSequenced = + parallelEntrances + <> sequencedEntrances + + return + sequencedAlwaysOrdered + && parallelNotSequenced + } + |> Async.RunSynchronously + ] ] let functionTests = diff --git a/tests/IcedTasks.Tests/paket.references b/tests/IcedTasks.Tests/paket.references index 900398f..b320237 100644 --- a/tests/IcedTasks.Tests/paket.references +++ b/tests/IcedTasks.Tests/paket.references @@ -1,6 +1,7 @@ group Test FSharp.Core Expecto +Expecto.FsCheck Microsoft.NET.Test.Sdk YoloDev.Expecto.TestSdk TimeProviderExtensions