Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@
</ItemGroup>

<ItemGroup>
<Compile Include="TestUtils.fs" />
<Compile Include="SmokeTests.fs" />
<Compile Include="TaskSeq.PocTests.fs" />
</ItemGroup>

<ItemGroup>
<!-- our smoketests use the highest RTM (non-alpha) FSharp.Core, contrary to the base test project, which uses the lowest possible denominator -->
<PackageReference Update="FSharp.Core" Version="7.0.401" />
<PackageReference Include="FSharp.Control.TaskSeq" Version="0.4.0-alpha.1" />
<PackageReference Include="FsToolkit.ErrorHandling.TaskResult" Version="4.10.0" />
<PackageReference Include="FsUnit.xUnit" Version="5.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="xunit" Version="2.5.3" />
Expand Down
131 changes: 131 additions & 0 deletions src/FSharp.Control.TaskSeq.SmokeTests/TestUtils.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
namespace TaskSeq.Tests

open System
open System.Threading
open System.Threading.Tasks
open System.Diagnostics
open System.Collections.Generic

open Xunit
open Xunit.Abstractions
open FsUnit.Xunit

open FSharp.Control

/// Milliseconds
[<Measure>]
type ms

/// Microseconds
[<Measure>]
type µs

/// 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 =

let private rnd = Random()

/// <summary>
/// Delay for at least the specified <paramref name="microseconds"/>.
/// </summary>
/// <param name="microseconds">The number of microseconds to delay.</param>
/// <param name="allowThreadYield">
/// True to allow yielding the thread. If this is set to false, on single-proc systems
/// this will prevent all other code from running.
/// </param>
let spinWaitDelay (microseconds: int64<µs>) (allowThreadYield: bool) =
let start = Stopwatch.GetTimestamp()
let minimumTicks = int64 microseconds * Stopwatch.Frequency / 1_000_000L

// FIXME: though this is part of official IoT code, the `allowThreadYield` version is extremely slow
// slower than would be expected from a simple SpinOnce. Though this may be caused by scenarios with
// many tasks at once. Have to investigate. See perf smoke tests.
if allowThreadYield then
let spinWait = SpinWait()

while Stopwatch.GetTimestamp() - start < minimumTicks do
spinWait.SpinOnce(1)

else
while Stopwatch.GetTimestamp() - start < minimumTicks do
Thread.SpinWait(1)

let delayTask (µsecMin: int64<µs>) (µsecMax: int64<µs>) f = task {
let rnd () = rnd.NextInt64(int64 µsecMin, int64 µsecMax) * 1L<µs>

// ensure unequal running lengths and points-in-time for assigning the variable
// DO NOT use Thead.Sleep(), it's blocking!
// WARNING: Task.Delay only has a 15ms timer resolution!!!

// TODO: check this! The following comment may not be correct
// this creates a resume state, which seems more efficient than SpinWait.SpinOnce, see DelayHelper.
let! _ = Task.Delay 0
let delay = rnd ()

// typical minimum accuracy of Task.Delay is 15.6ms
// for delay-cases shorter than that, we use SpinWait
if delay < 15_000L<µs> then
do spinWaitDelay (rnd ()) false
else
do! Task.Delay(int <| float delay / 1_000.0)

return f ()
}

/// <summary>
/// Creates dummy backgroundTasks with a randomized delay and a mutable state,
/// to ensure we properly test whether processing is done ordered or not.
/// Default for <paramref name="µsecMin" /> and <paramref name="µsecMax" />
/// are 10,000µs and 30,000µs respectively (or 10ms and 30ms).
/// </summary>
type DummyTaskFactory(µsecMin: int64<µs>, µsecMax: int64<µs>) =
let mutable x = 0

/// <summary>
/// Creates dummy tasks with a randomized delay and a mutable state,
/// to ensure we properly test whether processing is done ordered or not.
/// Uses the defaults for <paramref name="µsecMin" /> and <paramref name="µsecMax" />
/// with 10,000µs and 30,000µs respectively (or 10ms and 30ms).
/// </summary>
new() = new DummyTaskFactory(10_000L<µs>, 30_000L<µs>)


/// Bunch of delayed tasks that randomly have a yielding delay of 10-30ms, therefore having overlapping execution times.
member _.CreateDelayedTasks_SideEffect total = [
for i in 0 .. total - 1 do
fun () -> DelayHelper.delayTask µsecMin µsecMax (fun _ -> Interlocked.Increment &x)
]

/// Just some dummy task generators, copied over from the base test project, with artificial delays,
/// mostly to ensure sequential async operation of side effects.
module Gen =
/// Joins two tasks using merely BCL methods. This approach is what you can use to
/// properly, sequentially execute a chain of tasks in a non-blocking, non-overlapping way.
let joinWithContinuation tasks =
let simple (t: unit -> Task<_>) (source: unit -> Task<_>) : unit -> Task<_> =
fun () ->
source()
.ContinueWith((fun (_: Task) -> t ()), TaskContinuationOptions.OnlyOnRanToCompletion)
.Unwrap()
:?> Task<_>

let rec combine acc (tasks: (unit -> Task<_>) list) =
match tasks with
| [] -> acc
| t :: tail -> combine (simple t acc) tail

match tasks with
| first :: rest -> combine first rest
| [] -> failwith "oh oh, no tasks given!"

let joinIdentityHotStarted tasks () = task { return tasks |> List.map (fun t -> t ()) }

let joinIdentityDelayed tasks () = task { return tasks }

let createAndJoinMultipleTasks total joiner : Task<_> =
// the actual creation of tasks
let tasks = DummyTaskFactory().CreateDelayedTasks_SideEffect total
let combinedTask = joiner tasks
// start the combined tasks
combinedTask ()
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
<Compile Include="TaskSeq.Tests.CE.fs" />
<Compile Include="TaskSeq.StateTransitionBug.Tests.CE.fs" />
<Compile Include="TaskSeq.StateTransitionBug-delayed.Tests.CE.fs" />
<Compile Include="TaskSeq.PocTests.fs" />
<Compile Include="TaskSeq.Realworld.fs" />
<Compile Include="TaskSeq.AsyncExtensions.Tests.fs" />
<Compile Include="TaskSeq.TaskExtensions.Tests.fs" />
Expand All @@ -52,10 +51,8 @@
</ItemGroup>

<ItemGroup>
<!-- align test project with minimal required version for TaskSeq -->
<!-- we use 6.0.3 here and not 6.0.2 because TaskResult lib requires it-->
<PackageReference Update="FSharp.Core" Version="6.0.3" />
<PackageReference Include="FsToolkit.ErrorHandling.TaskResult" Version="3.2.0" />
<!-- align test project with minimal required version for TaskSeq, which is 6.0.1 at the moment -->
<PackageReference Update="FSharp.Core" Version="6.0.1" />
<PackageReference Include="FsUnit.xUnit" Version="5.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="xunit" Version="2.5.3" />
Expand Down
2 changes: 1 addition & 1 deletion src/FSharp.Control.TaskSeq.Test/Nunit.Extensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ namespace TaskSeq.Tests

open System
open System.Threading.Tasks

open FsUnit
open NHamcrest.Core
open Microsoft.FSharp.Reflection

open FsToolkit.ErrorHandling
open Xunit
open Xunit.Sdk

Expand Down
4 changes: 0 additions & 4 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Append.Tests.fs
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
module TaskSeq.Tests.Append

open System

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control
open System.Collections.Generic

//
// TaskSeq.append
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
module TaskSeq.Tests.AsyncExtensions

open System
open Xunit
open FsUnit.Xunit

Expand Down
1 change: 0 additions & 1 deletion src/FSharp.Control.TaskSeq.Test/TaskSeq.Cast.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ open System

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control

Expand Down
2 changes: 0 additions & 2 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Choose.Tests.fs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
module TaskSeq.Tests.Choose

open System
open System.Threading.Tasks

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control

Expand Down
1 change: 0 additions & 1 deletion src/FSharp.Control.TaskSeq.Test/TaskSeq.Collect.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ module TaskSeq.Tests.Collect

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control

Expand Down
4 changes: 1 addition & 3 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
module TaskSeq.Tests.Concat

open System
open System.Collections.Generic

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control
open System.Collections.Generic

//
// TaskSeq.concat
Expand Down
1 change: 0 additions & 1 deletion src/FSharp.Control.TaskSeq.Test/TaskSeq.Contains.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ module TaskSeq.Tests.Contains

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control

Expand Down
4 changes: 0 additions & 4 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Delay.Tests.fs
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
module TaskSeq.Tests.Delay

open System

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control
open System.Collections.Generic

//
// TaskSeq.delay
Expand Down
2 changes: 1 addition & 1 deletion src/FSharp.Control.TaskSeq.Test/TaskSeq.Do.Tests.fs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module TaskSeq.Tests.Do

open System
open System.Threading.Tasks

open FsUnit
open Xunit

Expand Down
3 changes: 1 addition & 2 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Empty.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module TaskSeq.Tests.Empty
open System.Threading.Tasks
open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control

Expand Down Expand Up @@ -42,7 +41,7 @@ let ``TaskSeq-empty of unit in a taskSeq context`` () = task {
[<Fact>]
let ``TaskSeq-empty of more complex type in a taskSeq context`` () = task {
let! sq =
taskSeq { yield! TaskSeq.empty<Result<Task<string>, int>> }
taskSeq { yield! TaskSeq.empty<Result<Task<string>, int>> } // not a TaskResult, but a ResultTask lol
|> TaskSeq.toArrayAsync

Array.isEmpty sq |> should be True
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module TaskSeq.Tests.ExactlyOne

open System

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control

Expand Down
2 changes: 0 additions & 2 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Except.Tests.fs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
module TaskSeq.Tests.Except

open System
open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control

Expand Down
1 change: 0 additions & 1 deletion src/FSharp.Control.TaskSeq.Test/TaskSeq.Exists.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ module TaskSeq.Tests.Exists

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control

Expand Down
2 changes: 1 addition & 1 deletion src/FSharp.Control.TaskSeq.Test/TaskSeq.Filter.Tests.fs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module TaskSeq.Tests.Filter

open System

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control

Expand Down
4 changes: 2 additions & 2 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Find.Tests.fs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
module TaskSeq.Tests.Find

open System.Collections.Generic

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control
open System.Collections.Generic

//
// TaskSeq.find
Expand Down
4 changes: 2 additions & 2 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.FindIndex.Tests.fs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
module TaskSeq.Tests.FindIndex

open System.Collections.Generic

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control
open System.Collections.Generic

//
// TaskSeq.findIndex
Expand Down
2 changes: 1 addition & 1 deletion src/FSharp.Control.TaskSeq.Test/TaskSeq.Fold.Tests.fs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module TaskSeq.Tests.Fold

open System.Text

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control

Expand Down
2 changes: 1 addition & 1 deletion src/FSharp.Control.TaskSeq.Test/TaskSeq.Head.Tests.fs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module TaskSeq.Tests.Head

open System

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control

Expand Down
1 change: 0 additions & 1 deletion src/FSharp.Control.TaskSeq.Test/TaskSeq.Indexed.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ module TaskSeq.Tests.Indexed

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control

Expand Down
Loading