Compare commits

...

6 Commits

Author SHA1 Message Date
dependabot[bot]
4da60d7e54 Bump NUnit3TestAdapter to 5.1.0
---
updated-dependencies:
- dependency-name: NUnit3TestAdapter
  dependency-version: 5.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: NUnit3TestAdapter
  dependency-version: 5.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: NUnit3TestAdapter
  dependency-version: 5.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 00:32:00 +00:00
patrick-conscriptus[bot]
12e3fc0e4f Automated commit (#284)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-08-10 00:55:19 +00:00
dependabot[bot]
208b809096 Bump fsharp-analyzers from 0.32.0 to 0.32.1 (#282)
* Bump fsharp-analyzers from 0.32.0 to 0.32.1

---
updated-dependencies:
- dependency-name: fsharp-analyzers
  dependency-version: 0.32.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-08-05 19:07:01 +00:00
dependabot[bot]
b4e5baddcf Bump NasAmin/trx-parser from 0.6.0 to 0.7.0 (#283)
* Bump NasAmin/trx-parser from 0.6.0 to 0.7.0

Bumps [NasAmin/trx-parser](https://github.com/nasamin/trx-parser) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/nasamin/trx-parser/releases)
- [Changelog](https://github.com/NasAmin/trx-parser/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nasamin/trx-parser/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: NasAmin/trx-parser
  dependency-version: 0.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Don't run external dependency on main

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-08-05 19:59:56 +01:00
patrick-conscriptus[bot]
5597b3f2f8 Automated commit (#281)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-08-03 00:57:08 +00:00
Patrick Stevens
fcfdcef6cf Async tests (#280) 2025-07-29 23:07:12 +00:00
9 changed files with 187 additions and 98 deletions

View File

@@ -9,7 +9,7 @@
] ]
}, },
"fsharp-analyzers": { "fsharp-analyzers": {
"version": "0.32.0", "version": "0.32.1",
"commands": [ "commands": [
"fsharp-analyzers" "fsharp-analyzers"
] ]

View File

@@ -96,8 +96,8 @@ jobs:
- name: Test using self - name: Test using self
run: 'nix develop --command dotnet exec ./WoofWare.NUnitTestRunner/bin/Release/net6.0/WoofWare.NUnitTestRunner.dll ./Consumer/bin/Release/net8.0/Consumer.dll --trx TrxOut/out.trx' run: 'nix develop --command dotnet exec ./WoofWare.NUnitTestRunner/bin/Release/net6.0/WoofWare.NUnitTestRunner.dll ./Consumer/bin/Release/net8.0/Consumer.dll --trx TrxOut/out.trx'
- name: Parse Trx files - name: Parse Trx files
uses: NasAmin/trx-parser@v0.6.0 uses: NasAmin/trx-parser@v0.7.0
if: always() if: always() && github.ref_name != 'main'
id: trx-parser id: trx-parser
with: with:
TRX_PATH: ${{ github.workspace }}/TrxOut TRX_PATH: ${{ github.workspace }}/TrxOut

View File

@@ -10,6 +10,7 @@
<Compile Include="NoAttribute.fs" /> <Compile Include="NoAttribute.fs" />
<Compile Include="Inconclusive.fs" /> <Compile Include="Inconclusive.fs" />
<Compile Include="RunSubProcess.fs" /> <Compile Include="RunSubProcess.fs" />
<Compile Include="TestAsync.fs" />
<Compile Include="TestExplicit.fs" /> <Compile Include="TestExplicit.fs" />
<Compile Include="TestNonParallel.fs" /> <Compile Include="TestNonParallel.fs" />
<Compile Include="TestParallel.fs" /> <Compile Include="TestParallel.fs" />
@@ -30,7 +31,7 @@
<PackageReference Include="FsUnit" Version="7.1.1" /> <PackageReference Include="FsUnit" Version="7.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
<PackageReference Include="NUnit" Version="4.3.2"/> <PackageReference Include="NUnit" Version="4.3.2"/>
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/> <PackageReference Include="NUnit3TestAdapter" Version="5.1.0"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

23
Consumer/TestAsync.fs Normal file
View File

@@ -0,0 +1,23 @@
namespace Consumer
open System
open System.Threading.Tasks
open FsUnitTyped
open NUnit.Framework
[<TestFixture>]
module TestAsync =
[<Test>]
let ``an async test`` () =
async {
do! Async.Sleep (TimeSpan.FromMilliseconds 20.0)
1 |> shouldEqual 1
}
[<Test>]
let ``an async test, task-based`` () =
task {
do! Task.Delay (TimeSpan.FromMilliseconds 20.0)
1 |> shouldEqual 1
}

View File

@@ -14,7 +14,7 @@
<PackageReference Include="FsUnit" Version="7.1.1" /> <PackageReference Include="FsUnit" Version="7.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
<PackageReference Include="NUnit" Version="4.3.2"/> <PackageReference Include="NUnit" Version="4.3.2"/>
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/> <PackageReference Include="NUnit3TestAdapter" Version="5.1.0"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -76,105 +76,168 @@ module TestFixture =
(test : MethodInfo) (test : MethodInfo)
(containingObject : obj) (containingObject : obj)
(args : obj[]) (args : obj[])
: Result<TestMemberSuccess, TestFailure list> * IndividualTestRunMetadata : Async<Result<TestMemberSuccess, TestFailure list> * IndividualTestRunMetadata>
= =
let rec runMethods let rec runMethods
(wrap : UserMethodFailure -> TestFailure) (wrap : UserMethodFailure -> TestFailure)
(toRun : MethodInfo list) (toRun : MethodInfo list)
(args : obj[]) (args : obj[])
: Result<unit, _> : Result<unit, TestFailure> Async
= =
match toRun with match toRun with
| [] -> Ok () | [] -> async.Return (Ok ())
| head :: rest -> | head :: rest ->
let result = async {
try let result =
head.Invoke (containingObject, args) |> Ok try
with head.Invoke (containingObject, args) |> Ok
| :? TargetInvocationException as e -> Error (UserMethodFailure.Threw (head.Name, e.InnerException)) with
| :? TargetParameterCountException -> | :? TargetInvocationException as e ->
UserMethodFailure.BadParameters ( Error (UserMethodFailure.Threw (head.Name, e.InnerException))
head.Name, | :? TargetParameterCountException ->
head.GetParameters () |> Array.map (fun pm -> pm.ParameterType), UserMethodFailure.BadParameters (
args head.Name,
) head.GetParameters () |> Array.map (fun pm -> pm.ParameterType),
|> Error args
)
|> Error
match result with let! ct = Async.CancellationToken
| Error e -> Error (wrap e)
| Ok result ->
match result with
| :? unit -> runMethods wrap rest args
| ret -> UserMethodFailure.ReturnedNonUnit (head.Name, ret) |> wrap |> Error
let start = DateTimeOffset.Now let! result =
match result with
| Error e -> async.Return (Error (wrap e))
| Ok result ->
match result with
| :? unit -> runMethods wrap rest args
| :? Task as result ->
async {
let mutable exc = None
let sw = Stopwatch.StartNew () try
do! Async.AwaitTask result
with e ->
exc <- Some e
let metadata () = match exc with
let name = | None -> return! runMethods wrap rest args
if args.Length = 0 then | Some e -> return Error (UserMethodFailure.Threw (head.Name, e) |> wrap)
test.Name }
else // We'd like to do this type-test:
let argsStr = args |> Seq.map string<obj> |> String.concat "," // | :? Async<unit> as result ->
$"%s{test.Name}(%s{argsStr})" // but instead we have to do all this reflective nonsense, because FSharpAsync is not part
// of the .NET runtime, so is instead in a different AssemblyLoadContext to us!
// It's in the user-code context, not ours.
| ret ->
let ty = ret.GetType ()
{ if ty.Namespace = "Microsoft.FSharp.Control" && ty.Name = "FSharpAsync`1" then
End = DateTimeOffset.Now match ty.GenericTypeArguments |> Array.map (fun t -> t.FullName) with
Start = start | [| "Microsoft.FSharp.Core.Unit" |] ->
Total = sw.Elapsed let asyncModule = ty.Assembly.GetType ("Microsoft.FSharp.Control.FSharpAsync")
ComputerName = Environment.MachineName // let catch = asyncModule.GetMethod("Catch").MakeGenericMethod [| ty.GenericTypeArguments.[0] |]
ExecutionId = Guid.NewGuid () // let caught = catch.Invoke ((null: obj), [| ret |])
TestId = testId let startAsTask =
TestName = name asyncModule.GetMethod("StartAsTask").MakeGenericMethod
ClassName = test.DeclaringType.FullName [| ty.GenericTypeArguments.[0] |]
StdOut =
match contexts.DumpStdout outputId with
| "" -> None
| v -> Some v
StdErr =
match contexts.DumpStderr outputId with
| "" -> None
| v -> Some v
}
let setUpResult = runMethods TestFailure.SetUpFailed setUp [||] let started =
sw.Stop () startAsTask.Invoke ((null : obj), [| ret ; (null : obj) ; (null : obj) |])
|> unbox<Task>
match setUpResult with async {
| Error e -> Error [ e ], metadata () let! res = Async.AwaitTask started |> Async.Catch
| Ok () ->
sw.Start () match res with
| Choice1Of2 () -> return! runMethods wrap rest args
| Choice2Of2 e ->
return
Error (
UserMethodFailure.Threw (head.Name, started.Exception) |> wrap
)
}
| _ ->
UserMethodFailure.ReturnedNonUnit (head.Name, ret)
|> wrap
|> Error
|> async.Return
else
async.Return (UserMethodFailure.ReturnedNonUnit (head.Name, ret) |> wrap |> Error)
let result = return result
let result = runMethods TestFailure.TestFailed [ test ] args }
async {
let start = DateTimeOffset.Now
let sw = Stopwatch.StartNew ()
let metadata () =
let name =
if args.Length = 0 then
test.Name
else
let argsStr = args |> Seq.map string<obj> |> String.concat ","
$"%s{test.Name}(%s{argsStr})"
{
End = DateTimeOffset.Now
Start = start
Total = sw.Elapsed
ComputerName = Environment.MachineName
ExecutionId = Guid.NewGuid ()
TestId = testId
TestName = name
ClassName = test.DeclaringType.FullName
StdOut =
match contexts.DumpStdout outputId with
| "" -> None
| v -> Some v
StdErr =
match contexts.DumpStderr outputId with
| "" -> None
| v -> Some v
}
let! setUpResult = runMethods TestFailure.SetUpFailed setUp [||]
sw.Stop () sw.Stop ()
match result with match setUpResult with
| Ok () -> Ok None | Error e -> return Error [ e ], metadata ()
| Error (TestFailure.TestFailed (UserMethodFailure.Threw (_, exc)) as orig) -> | Ok () ->
match exc.GetType().FullName with
| "NUnit.Framework.SuccessException" -> Ok None
| "NUnit.Framework.IgnoreException" -> Ok (Some (TestMemberSuccess.Ignored (Option.ofObj exc.Message)))
| "NUnit.Framework.InconclusiveException" ->
Ok (Some (TestMemberSuccess.Inconclusive (Option.ofObj exc.Message)))
| _ -> Error orig
| Error orig -> Error orig
// Unconditionally run TearDown after tests, even if tests failed. sw.Start ()
sw.Start () let! result = runMethods TestFailure.TestFailed [ test ] args
let tearDownResult = runMethods TestFailure.TearDownFailed tearDown [||] sw.Stop ()
sw.Stop ()
let metadata = metadata () let result =
match result with
| Ok () -> Ok None
| Error (TestFailure.TestFailed (UserMethodFailure.Threw (_, exc)) as orig) ->
match exc.GetType().FullName with
| "NUnit.Framework.SuccessException" -> Ok None
| "NUnit.Framework.IgnoreException" ->
Ok (Some (TestMemberSuccess.Ignored (Option.ofObj exc.Message)))
| "NUnit.Framework.InconclusiveException" ->
Ok (Some (TestMemberSuccess.Inconclusive (Option.ofObj exc.Message)))
| _ -> Error orig
| Error orig -> Error orig
match result, tearDownResult with // Unconditionally run TearDown after tests, even if tests failed.
| Ok None, Ok () -> Ok TestMemberSuccess.Ok, metadata sw.Start ()
| Ok (Some s), Ok () -> Ok s, metadata let! tearDownResult = runMethods TestFailure.TearDownFailed tearDown [||]
| Error e, Ok () sw.Stop ()
| Ok _, Error e -> Error [ e ], metadata
| Error e1, Error e2 -> Error [ e1 ; e2 ], metadata let metadata = metadata ()
return
match result, tearDownResult with
| Ok None, Ok () -> Ok TestMemberSuccess.Ok, metadata
| Ok (Some s), Ok () -> Ok s, metadata
| Error e, Ok ()
| Ok _, Error e -> Error [ e ], metadata
| Error e1, Error e2 -> Error [ e1 ; e2 ], metadata
}
let private getValues (test : SingleTestMethod) = let private getValues (test : SingleTestMethod) =
let valuesAttrs = let valuesAttrs =
@@ -395,20 +458,22 @@ module TestFixture =
|> Seq.map (fun (testGuid, args) -> |> Seq.map (fun (testGuid, args) ->
task { task {
let runMe () = let runMe () =
progress.OnTestMemberStart test.Name async {
let oldValue = contexts.AsyncLocal.Value progress.OnTestMemberStart test.Name
let outputId = contexts.NewOutputs () let oldValue = contexts.AsyncLocal.Value
contexts.AsyncLocal.Value <- outputId let outputId = contexts.NewOutputs ()
contexts.AsyncLocal.Value <- outputId
let result, meta = let! result, meta =
runOne outputId contexts setUp tearDown testGuid test.Method containingObject args runOne outputId contexts setUp tearDown testGuid test.Method containingObject args
contexts.AsyncLocal.Value <- oldValue contexts.AsyncLocal.Value <- oldValue
progress.OnTestMemberFinished test.Name progress.OnTestMemberFinished test.Name
result, meta return result, meta
}
let! results, summary = par.Run running test.Parallelize runMe let! results, summary = par.RunAsync running test.Parallelize runMe
match results with match results with
| Ok results -> return Ok results, summary | Ok results -> return Ok results, summary

View File

@@ -22,7 +22,7 @@
<PackageReference Include="FsUnit" Version="7.1.1" /> <PackageReference Include="FsUnit" Version="7.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="NUnit" Version="4.3.2" /> <PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/> <PackageReference Include="NUnit3TestAdapter" Version="5.1.0"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1753432016, "lastModified": 1754711617,
"narHash": "sha256-cnL5WWn/xkZoyH/03NNUS7QgW5vI7D1i74g48qplCvg=", "narHash": "sha256-WrZ280bT6NzNbBo+CKeJA/NW1rhvN/RUPZczqCpu2mI=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "6027c30c8e9810896b92429f0092f624f7b1aace", "rev": "00b574b1ba8a352f0601c4dde4faff4b534ebb1e",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -26,8 +26,8 @@
}, },
{ {
"pname": "fsharp-analyzers", "pname": "fsharp-analyzers",
"version": "0.32.0", "version": "0.32.1",
"hash": "sha256-MnhsK5tOeexL6uQhsV4nTRz8CGbz2o8VyHwAK8x91pE=" "hash": "sha256-le6rPnAF7cKGBZ2w8H2u9glK+6rT2ZjiAVnrkH2IhrM="
}, },
{ {
"pname": "FSharp.Core", "pname": "FSharp.Core",