mirror of
https://github.com/Smaug123/unofficial-nunit-runner
synced 2025-10-07 02:08:40 +00:00
Compare commits
8 Commits
WoofWare.N
...
WoofWare.N
Author | SHA1 | Date | |
---|---|---|---|
|
81c6b584a4 | ||
|
40824e06e7 | ||
|
fb945c04ac | ||
|
85cd116d52 | ||
|
8e7c54cc83 | ||
|
378a0169f8 | ||
|
870804d6ef | ||
|
9f5f22c644 |
@@ -1,40 +1,40 @@
|
|||||||
root=true
|
root = true
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
charset=utf-8
|
charset = utf-8
|
||||||
trim_trailing_whitespace=true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline=true
|
insert_final_newline = true
|
||||||
indent_style=space
|
indent_style = space
|
||||||
indent_size=4
|
indent_size = 4
|
||||||
|
|
||||||
# ReSharper properties
|
# ReSharper properties
|
||||||
resharper_xml_indent_size=2
|
resharper_xml_indent_size = 2
|
||||||
resharper_xml_max_line_length=100
|
resharper_xml_max_line_length = 100
|
||||||
resharper_xml_tab_width=2
|
resharper_xml_tab_width = 2
|
||||||
|
|
||||||
[*.{csproj,fsproj,sqlproj,targets,props,ts,tsx,css,json}]
|
[*.{csproj,fsproj,sqlproj,targets,props,ts,tsx,css,json}]
|
||||||
indent_style=space
|
indent_style = space
|
||||||
indent_size=2
|
indent_size = 2
|
||||||
|
|
||||||
[*.{fs,fsi}]
|
[*.{fs,fsi}]
|
||||||
fsharp_bar_before_discriminated_union_declaration=true
|
fsharp_bar_before_discriminated_union_declaration = true
|
||||||
fsharp_space_before_uppercase_invocation=true
|
fsharp_space_before_uppercase_invocation = true
|
||||||
fsharp_space_before_class_constructor=true
|
fsharp_space_before_class_constructor = true
|
||||||
fsharp_space_before_member=true
|
fsharp_space_before_member = true
|
||||||
fsharp_space_before_colon=true
|
fsharp_space_before_colon = true
|
||||||
fsharp_space_before_semicolon=true
|
fsharp_space_before_semicolon = true
|
||||||
fsharp_multiline_bracket_style=aligned
|
fsharp_multiline_bracket_style = aligned
|
||||||
fsharp_newline_between_type_definition_and_members=true
|
fsharp_newline_between_type_definition_and_members = true
|
||||||
fsharp_align_function_signature_to_indentation=true
|
fsharp_align_function_signature_to_indentation = true
|
||||||
fsharp_alternative_long_member_definitions=true
|
fsharp_alternative_long_member_definitions = true
|
||||||
fsharp_multi_line_lambda_closing_newline=true
|
fsharp_multi_line_lambda_closing_newline = true
|
||||||
fsharp_experimental_keep_indent_in_branch=true
|
fsharp_experimental_keep_indent_in_branch = true
|
||||||
fsharp_max_value_binding_width=80
|
fsharp_max_value_binding_width = 80
|
||||||
fsharp_max_record_width=0
|
fsharp_max_record_width = 0
|
||||||
max_line_length=120
|
max_line_length = 120
|
||||||
end_of_line=lf
|
end_of_line = lf
|
||||||
|
|
||||||
[*.{appxmanifest,build,dtd,nuspec,xaml,xamlx,xoml,xsd}]
|
[*.{appxmanifest,build,dtd,nuspec,xaml,xamlx,xoml,xsd}]
|
||||||
indent_style=space
|
indent_style = space
|
||||||
indent_size=2
|
indent_size = 2
|
||||||
tab_width=2
|
tab_width = 2
|
||||||
|
54
.github/workflows/dotnet.yaml
vendored
54
.github/workflows/dotnet.yaml
vendored
@@ -38,7 +38,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: 'nix develop --command dotnet build --no-restore --configuration ${{matrix.config}}'
|
run: 'nix develop --command dotnet build --no-restore --configuration ${{matrix.config}}'
|
||||||
- name: Test
|
- name: Test
|
||||||
run: 'nix develop --command dotnet test --no-build --verbosity normal --configuration ${{matrix.config}}'
|
run: 'nix develop --command dotnet test --no-build --verbosity normal --configuration ${{matrix.config}} --framework net8.0'
|
||||||
|
|
||||||
selftest:
|
selftest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -71,7 +71,49 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: 'nix develop --command dotnet build --no-restore --configuration Release'
|
run: 'nix develop --command dotnet build --no-restore --configuration Release'
|
||||||
- name: Test using self
|
- name: Test using self
|
||||||
run: 'nix develop --command dotnet exec ./WoofWare.NUnitTestRunner/bin/Release/net8.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
|
||||||
|
uses: NasAmin/trx-parser@v0.6.0
|
||||||
|
if: always()
|
||||||
|
id: trx-parser
|
||||||
|
with:
|
||||||
|
TRX_PATH: ${{ github.workspace }}/TrxOut
|
||||||
|
REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
selftest-net6:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: none
|
||||||
|
checks: write
|
||||||
|
contents: read
|
||||||
|
deployments: none
|
||||||
|
id-token: none
|
||||||
|
issues: none
|
||||||
|
discussions: none
|
||||||
|
packages: none
|
||||||
|
pages: none
|
||||||
|
pull-requests: read
|
||||||
|
repository-projects: none
|
||||||
|
security-events: none
|
||||||
|
statuses: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
|
||||||
|
- name: Install Nix
|
||||||
|
uses: cachix/install-nix-action@V27
|
||||||
|
with:
|
||||||
|
extra_nix_config: |
|
||||||
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Restore dependencies
|
||||||
|
run: nix develop --command dotnet restore
|
||||||
|
- name: Build runner
|
||||||
|
run: 'nix develop --command dotnet build WoofWare.NUnitTestRunner --configuration Release'
|
||||||
|
- name: Build target
|
||||||
|
run: 'nix develop --command dotnet build Consumer --configuration Release'
|
||||||
|
- name: Test using self
|
||||||
|
run: 'nix develop .#net6 --command ./WoofWare.NUnitTestRunner/bin/Release/net6.0/WoofWare.NUnitTestRunner ./Consumer/bin/Release/net6.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.6.0
|
||||||
if: always()
|
if: always()
|
||||||
@@ -255,7 +297,7 @@ jobs:
|
|||||||
name: nuget-package-lib
|
name: nuget-package-lib
|
||||||
path: packed
|
path: packed
|
||||||
- name: Attest Build Provenance
|
- name: Attest Build Provenance
|
||||||
uses: actions/attest-build-provenance@897ed5eab6ed058a474202017ada7f40bfa52940 # v1.0.0
|
uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2
|
||||||
with:
|
with:
|
||||||
subject-path: "packed/*.nupkg"
|
subject-path: "packed/*.nupkg"
|
||||||
|
|
||||||
@@ -274,7 +316,7 @@ jobs:
|
|||||||
name: nuget-package-tool
|
name: nuget-package-tool
|
||||||
path: packed
|
path: packed
|
||||||
- name: Attest Build Provenance
|
- name: Attest Build Provenance
|
||||||
uses: actions/attest-build-provenance@897ed5eab6ed058a474202017ada7f40bfa52940 # v1.0.0
|
uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2
|
||||||
with:
|
with:
|
||||||
subject-path: "packed/*.nupkg"
|
subject-path: "packed/*.nupkg"
|
||||||
|
|
||||||
@@ -316,7 +358,7 @@ jobs:
|
|||||||
run: 'bash ./.github/workflows/assert-contents.sh'
|
run: 'bash ./.github/workflows/assert-contents.sh'
|
||||||
- name: Attest Build Provenance
|
- name: Attest Build Provenance
|
||||||
if: steps.publish-success.outputs.result == 'published'
|
if: steps.publish-success.outputs.result == 'published'
|
||||||
uses: actions/attest-build-provenance@897ed5eab6ed058a474202017ada7f40bfa52940 # v1.0.0
|
uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2
|
||||||
with:
|
with:
|
||||||
subject-path: "from-nuget.nupkg"
|
subject-path: "from-nuget.nupkg"
|
||||||
|
|
||||||
@@ -358,7 +400,7 @@ jobs:
|
|||||||
run: 'bash ./.github/workflows/assert-contents.sh'
|
run: 'bash ./.github/workflows/assert-contents.sh'
|
||||||
- name: Attest Build Provenance
|
- name: Attest Build Provenance
|
||||||
if: steps.publish-success.outputs.result == 'published'
|
if: steps.publish-success.outputs.result == 'published'
|
||||||
uses: actions/attest-build-provenance@897ed5eab6ed058a474202017ada7f40bfa52940 # v1.0.0
|
uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2
|
||||||
with:
|
with:
|
||||||
subject-path: "from-nuget.nupkg"
|
subject-path: "from-nuget.nupkg"
|
||||||
|
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<IsTestProject>true</IsTestProject>
|
<IsTestProject>true</IsTestProject>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -10,6 +9,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="NoAttribute.fs" />
|
<Compile Include="NoAttribute.fs" />
|
||||||
<Compile Include="Inconclusive.fs" />
|
<Compile Include="Inconclusive.fs" />
|
||||||
|
<Compile Include="RunSubProcess.fs" />
|
||||||
<Compile Include="TestNonParallel.fs" />
|
<Compile Include="TestNonParallel.fs" />
|
||||||
<Compile Include="TestParallel.fs" />
|
<Compile Include="TestParallel.fs" />
|
||||||
<Compile Include="TestStdout.fs" />
|
<Compile Include="TestStdout.fs" />
|
||||||
@@ -26,9 +26,9 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FsUnit" Version="6.0.0" />
|
<PackageReference Include="FsUnit" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0"/>
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0"/>
|
||||||
<PackageReference Include="NUnit" Version="4.1.0"/>
|
<PackageReference Include="NUnit" Version="4.1.0"/>
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
45
Consumer/RunSubProcess.fs
Normal file
45
Consumer/RunSubProcess.fs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
namespace Consumer
|
||||||
|
|
||||||
|
open System
|
||||||
|
open System.Diagnostics
|
||||||
|
open System.IO
|
||||||
|
open System.IO.Compression
|
||||||
|
open System.Text
|
||||||
|
open NUnit.Framework
|
||||||
|
open FsUnitTyped
|
||||||
|
|
||||||
|
[<TestFixture>]
|
||||||
|
module RunSubProcess =
|
||||||
|
[<Test>]
|
||||||
|
let ``Run a subprocess`` () =
|
||||||
|
let exe = "/bin/bash"
|
||||||
|
let args = [ "-c" ; "echo hi >&2 && echo bye" ]
|
||||||
|
let workingDir = None
|
||||||
|
|
||||||
|
let psi =
|
||||||
|
ProcessStartInfo (
|
||||||
|
exe,
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
WorkingDirectory = Option.toObj workingDir
|
||||||
|
)
|
||||||
|
|
||||||
|
for arg in args do
|
||||||
|
psi.ArgumentList.Add arg
|
||||||
|
|
||||||
|
psi.EnvironmentVariables.Add ("THING", Path.Combine (AppDomain.CurrentDomain.BaseDirectory, "hi"))
|
||||||
|
let stderr = StringBuilder ()
|
||||||
|
use proc = new Process (StartInfo = psi)
|
||||||
|
proc.OutputDataReceived.Add (fun e -> printfn $"%s{e.Data}")
|
||||||
|
|
||||||
|
proc.ErrorDataReceived.Add (fun e ->
|
||||||
|
eprintfn $"%s{e.Data}"
|
||||||
|
stderr.AppendLine e.Data |> ignore
|
||||||
|
)
|
||||||
|
|
||||||
|
proc.Start () |> shouldEqual true
|
||||||
|
proc.BeginOutputReadLine ()
|
||||||
|
proc.BeginErrorReadLine ()
|
||||||
|
|
||||||
|
proc.WaitForExit ()
|
99
WoofWare.NUnitTestRunner.Lib/Args.fs
Normal file
99
WoofWare.NUnitTestRunner.Lib/Args.fs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
namespace WoofWare.NUnitTestRunner
|
||||||
|
|
||||||
|
open System
|
||||||
|
open System.IO
|
||||||
|
|
||||||
|
[<AutoOpen>]
|
||||||
|
module internal Patterns =
|
||||||
|
let (|Key|_|) (start : string) (s : string) : string option =
|
||||||
|
if s.StartsWith (start + "=", StringComparison.Ordinal) then
|
||||||
|
s.Substring (start.Length + 1) |> Some
|
||||||
|
else
|
||||||
|
None
|
||||||
|
|
||||||
|
/// Represents how verbose the test runner's logging should be.
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
type LogLevel =
|
||||||
|
/// Don't log any information about the test run.
|
||||||
|
| Nothing
|
||||||
|
/// Log as much information as is available about the test run.
|
||||||
|
| Verbose
|
||||||
|
|
||||||
|
/// Arguments controlling the test runner itself (not the tests therein).
|
||||||
|
type Args =
|
||||||
|
{
|
||||||
|
/// The DLL containing the tests we'll reflectively discover and invoke.
|
||||||
|
Dll : FileInfo
|
||||||
|
/// If set, the output file into which we will write a TRX report. (We'll create parent directories as necessary.)
|
||||||
|
Trx : FileInfo option
|
||||||
|
/// Also contains the original string which specified the filter.
|
||||||
|
Filter : (string * Filter) option
|
||||||
|
/// How verbose to be with the test runner's own logging.
|
||||||
|
Logging : LogLevel
|
||||||
|
/// Maximum number of tests which can run concurrently. This setting overrides any LevelOfParallelism reflectively
|
||||||
|
/// extracted from the assembly under test.
|
||||||
|
LevelOfParallelism : int option
|
||||||
|
/// Abort if the test runner is running for longer than this timeout.
|
||||||
|
Timeout : TimeSpan option
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse `argv` into a structured Args.
|
||||||
|
static member Parse (args : string list) : Args =
|
||||||
|
match args with
|
||||||
|
| [] -> failwith "The first arg must be a positional arg, the DLL to test."
|
||||||
|
| dll :: args ->
|
||||||
|
|
||||||
|
let rec go
|
||||||
|
(trx : FileInfo option)
|
||||||
|
(filter : (string * Filter) option)
|
||||||
|
(logging : LogLevel option)
|
||||||
|
(par : int option)
|
||||||
|
(timeout : TimeSpan option)
|
||||||
|
(args : string list)
|
||||||
|
=
|
||||||
|
match args with
|
||||||
|
| [] ->
|
||||||
|
{
|
||||||
|
Dll = FileInfo dll
|
||||||
|
Trx = trx
|
||||||
|
Filter = filter
|
||||||
|
Logging = logging |> Option.defaultValue LogLevel.Nothing
|
||||||
|
LevelOfParallelism = par
|
||||||
|
Timeout = timeout
|
||||||
|
}
|
||||||
|
| Key "--filter" filterStr :: rest
|
||||||
|
| "--filter" :: filterStr :: rest ->
|
||||||
|
match filter with
|
||||||
|
| Some _ -> failwith "Two conflicting filters; you can only specify --filter once"
|
||||||
|
| None -> go trx (Some (filterStr, Filter.parse filterStr)) logging par timeout rest
|
||||||
|
| Key "--trx" trxStr :: rest
|
||||||
|
| "--trx" :: trxStr :: rest ->
|
||||||
|
match trx with
|
||||||
|
| Some _ -> failwith "Two conflicting TRX outputs; you can only specify --trx once"
|
||||||
|
| None -> go (Some (FileInfo trxStr)) filter logging par timeout rest
|
||||||
|
| Key "--verbose" verboseStr :: rest
|
||||||
|
| "--verbose" :: verboseStr :: rest ->
|
||||||
|
match logging with
|
||||||
|
| Some _ -> failwith "Two conflicting --verbose outputs; you can only specify --verbose once"
|
||||||
|
| None ->
|
||||||
|
let verbose =
|
||||||
|
if Boolean.Parse verboseStr then
|
||||||
|
LogLevel.Verbose
|
||||||
|
else
|
||||||
|
LogLevel.Nothing
|
||||||
|
|
||||||
|
go trx filter (Some verbose) par timeout rest
|
||||||
|
| Key "--parallelism" parStr :: rest
|
||||||
|
| "--parallelism" :: parStr :: rest ->
|
||||||
|
match par with
|
||||||
|
| Some _ -> failwith "Two conflicting --parallelism outputs; you can only specify --parallelism once"
|
||||||
|
| None -> go trx filter logging (Some (Int32.Parse parStr)) timeout rest
|
||||||
|
| Key "--timeout-seconds" timeoutStr :: rest
|
||||||
|
| "--timeout-seconds" :: timeoutStr :: rest ->
|
||||||
|
match timeout with
|
||||||
|
| Some _ ->
|
||||||
|
failwith "Two conflicting --timeout-seconds outputs; you can only specify --timeout-seconds once"
|
||||||
|
| None -> go trx filter logging par (Some (TimeSpan.FromSeconds (Int32.Parse timeoutStr |> float))) rest
|
||||||
|
| k :: _rest -> failwith $"Unrecognised arg %s{k}"
|
||||||
|
|
||||||
|
go None None None None None args
|
58
WoofWare.NUnitTestRunner.Lib/AssemblyLevelAttributes.fs
Normal file
58
WoofWare.NUnitTestRunner.Lib/AssemblyLevelAttributes.fs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
namespace WoofWare.NUnitTestRunner
|
||||||
|
|
||||||
|
open System.Reflection
|
||||||
|
|
||||||
|
/// Attributes at the assembly level which control the behaviour of NUnit.
|
||||||
|
type AssemblyLevelAttributes =
|
||||||
|
{
|
||||||
|
/// How many tests can be running at once, if anything's running in parallel.
|
||||||
|
Parallelism : int option
|
||||||
|
/// Whether the tests in this assembly can be parallelised at all.
|
||||||
|
Parallelizable : Parallelizable<AssemblyParallelScope> option
|
||||||
|
}
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module AssemblyLevelAttributes =
|
||||||
|
|
||||||
|
/// Reflectively obtain the values of any relevant assembly attributes.
|
||||||
|
let get (assy : Assembly) : AssemblyLevelAttributes =
|
||||||
|
((None, None), assy.CustomAttributes)
|
||||||
|
||> Seq.fold (fun (levelPar, par) attr ->
|
||||||
|
match attr.AttributeType.FullName with
|
||||||
|
| "NUnit.Framework.LevelOfParallelismAttribute" ->
|
||||||
|
let arg = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<int>
|
||||||
|
|
||||||
|
match levelPar with
|
||||||
|
| None -> (Some arg, par)
|
||||||
|
| Some existing ->
|
||||||
|
failwith $"Assembly %s{assy.Location} declares parallelism %i{arg} and also %i{existing}"
|
||||||
|
| "NUnit.Framework.NonParallelizableAttribute" ->
|
||||||
|
match levelPar with
|
||||||
|
| None -> (Some 1, par)
|
||||||
|
| Some existing ->
|
||||||
|
failwith
|
||||||
|
$"Assembly %s{assy.Location} declares non-parallelizable and also parallelism %i{existing}"
|
||||||
|
| "NUnit.Framework.ParallelizableAttribute" ->
|
||||||
|
match par with
|
||||||
|
| Some _ -> failwith "Got multiple Parallelize attributes in assembly"
|
||||||
|
| None ->
|
||||||
|
match attr.ConstructorArguments |> Seq.toList with
|
||||||
|
| [] -> levelPar, Some (Parallelizable.Yes AssemblyParallelScope.Fixtures)
|
||||||
|
| [ v ] ->
|
||||||
|
match v.Value with
|
||||||
|
| :? int as v ->
|
||||||
|
match v with
|
||||||
|
| 512 -> levelPar, Some (Parallelizable.Yes AssemblyParallelScope.Fixtures)
|
||||||
|
| 256 -> levelPar, Some (Parallelizable.Yes AssemblyParallelScope.Children)
|
||||||
|
| 257 -> failwith "ParallelScope.All is invalid on assemblies; only Fixtures or Children"
|
||||||
|
| 1 -> failwith "ParallelScope.Self is invalid on assemblies; only Fixtures or Children"
|
||||||
|
| v -> failwith $"Could not recognise value %i{v} of parallel scope on assembly"
|
||||||
|
| v -> failwith $"Unexpectedly non-int value %O{v} of parallel scope on assembly"
|
||||||
|
| _ -> failwith "unexpectedly got multiple args to Parallelizable on assembly"
|
||||||
|
| _ -> levelPar, par
|
||||||
|
)
|
||||||
|
|> fun (par, canPar) ->
|
||||||
|
{
|
||||||
|
Parallelizable = canPar
|
||||||
|
Parallelism = par
|
||||||
|
}
|
@@ -89,8 +89,13 @@ type TestContexts =
|
|||||||
AsyncLocal = local
|
AsyncLocal = local
|
||||||
}
|
}
|
||||||
|
|
||||||
member internal this.Stdout : TextWriter = this.StdOutWriter
|
/// An output stream which will identify the ExecutionContext it's being written to from,
|
||||||
member internal this.Stderr : TextWriter = this.StdErrWriter
|
/// and will separate that output into its own stream internally.
|
||||||
|
member this.Stdout : TextWriter = this.StdOutWriter
|
||||||
|
|
||||||
|
/// An output stream which will identify the ExecutionContext it's being written to from,
|
||||||
|
/// and will separate that output into its own stream internally.
|
||||||
|
member this.Stderr : TextWriter = this.StdErrWriter
|
||||||
|
|
||||||
member internal this.DumpStdout (id : OutputStreamId) : string =
|
member internal this.DumpStdout (id : OutputStreamId) : string =
|
||||||
lock
|
lock
|
||||||
|
243
WoofWare.NUnitTestRunner.Lib/CreateTrxReport.fs
Normal file
243
WoofWare.NUnitTestRunner.Lib/CreateTrxReport.fs
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
namespace WoofWare.NUnitTestRunner
|
||||||
|
|
||||||
|
open System
|
||||||
|
open System.Reflection
|
||||||
|
|
||||||
|
/// Methods for constructing TRX reports.
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module BuildTrxReport =
|
||||||
|
|
||||||
|
/// Build a TRX report from the given results.
|
||||||
|
let build
|
||||||
|
(assy : Assembly)
|
||||||
|
(creationTime : DateTimeOffset)
|
||||||
|
(startTime : DateTimeOffset)
|
||||||
|
(results : FixtureRunResults list)
|
||||||
|
: TrxReport
|
||||||
|
=
|
||||||
|
let finishTime = DateTimeOffset.Now
|
||||||
|
let finishTimeHumanReadable = finishTime.ToString @"yyyy-MM-dd HH:mm:ss"
|
||||||
|
let nowMachine = finishTime.ToString @"yyyy-MM-dd_HH_mm_ss"
|
||||||
|
|
||||||
|
let testListId = Guid.NewGuid ()
|
||||||
|
|
||||||
|
let testDefinitions, testEntries =
|
||||||
|
results
|
||||||
|
|> List.collect (fun results -> results.IndividualTestRunMetadata)
|
||||||
|
|> List.map (fun (data, _) ->
|
||||||
|
let defn =
|
||||||
|
{
|
||||||
|
Name = data.TestName
|
||||||
|
Storage = assy.Location.ToLowerInvariant ()
|
||||||
|
Id = data.TestId
|
||||||
|
Execution =
|
||||||
|
{
|
||||||
|
Id = data.ExecutionId
|
||||||
|
}
|
||||||
|
TestMethod =
|
||||||
|
{
|
||||||
|
CodeBase = assy.Location
|
||||||
|
AdapterTypeName = Uri "executor://woofware/"
|
||||||
|
ClassName = data.ClassName
|
||||||
|
Name = data.TestName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry : TrxTestEntry =
|
||||||
|
{
|
||||||
|
TestListId = testListId
|
||||||
|
ExecutionId = data.ExecutionId
|
||||||
|
TestId = data.TestId
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
defn, entry
|
||||||
|
)
|
||||||
|
|> List.unzip
|
||||||
|
|
||||||
|
let hostname = Environment.MachineName
|
||||||
|
|
||||||
|
let settings =
|
||||||
|
{
|
||||||
|
Name = "default"
|
||||||
|
Id = Guid.NewGuid ()
|
||||||
|
Deployment =
|
||||||
|
{
|
||||||
|
RunDeploymentRoot = $"_%s{hostname}_%s{nowMachine}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let testList : TrxTestListEntry =
|
||||||
|
{
|
||||||
|
Id = testListId
|
||||||
|
Name = "All"
|
||||||
|
}
|
||||||
|
|
||||||
|
let counters =
|
||||||
|
(TrxCounters.Zero, results)
|
||||||
|
// TODO: this is woefully inefficient
|
||||||
|
||> List.fold (fun counters results ->
|
||||||
|
let counters =
|
||||||
|
(counters, results.Failed)
|
||||||
|
||> List.fold (fun counters (_, _) ->
|
||||||
|
// TODO: the counters can be more specific about the failure mode
|
||||||
|
counters.AddFailed ()
|
||||||
|
)
|
||||||
|
|
||||||
|
let counters =
|
||||||
|
(counters, results.OtherFailures)
|
||||||
|
||> List.fold (fun counters _ ->
|
||||||
|
// TODO: the counters can be more specific about the failure mode
|
||||||
|
counters.AddFailed ()
|
||||||
|
)
|
||||||
|
|
||||||
|
(counters, results.Success)
|
||||||
|
||> List.fold (fun counters (_, success, _) ->
|
||||||
|
match success with
|
||||||
|
| TestMemberSuccess.Ok -> counters.AddPassed ()
|
||||||
|
| TestMemberSuccess.Ignored _
|
||||||
|
| TestMemberSuccess.Explicit _ -> counters.AddNotExecuted ()
|
||||||
|
| TestMemberSuccess.Inconclusive _ -> counters.AddInconclusive ()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: I'm sure we can do better than this; there's a whole range of possible
|
||||||
|
// states!
|
||||||
|
let outcome =
|
||||||
|
if counters.Failed > 0u then
|
||||||
|
TrxOutcome.Failed
|
||||||
|
else
|
||||||
|
TrxOutcome.Completed
|
||||||
|
|
||||||
|
let resultSummary : TrxResultsSummary =
|
||||||
|
{
|
||||||
|
Outcome = outcome
|
||||||
|
Counters = counters
|
||||||
|
Output =
|
||||||
|
{
|
||||||
|
StdOut = None
|
||||||
|
StdErr = None
|
||||||
|
ErrorInfo = None
|
||||||
|
}
|
||||||
|
RunInfos =
|
||||||
|
[
|
||||||
|
// TODO: capture stdout
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
let times : TrxReportTimes =
|
||||||
|
{
|
||||||
|
Creation = creationTime
|
||||||
|
Queuing = startTime
|
||||||
|
Start = startTime
|
||||||
|
Finish = finishTime
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let magicGuid = Guid.Parse "13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b"
|
||||||
|
|
||||||
|
let results =
|
||||||
|
results
|
||||||
|
|> List.collect (fun results -> results.IndividualTestRunMetadata)
|
||||||
|
|> List.map (fun (i, cause) ->
|
||||||
|
let exc =
|
||||||
|
match cause with
|
||||||
|
| Choice2Of3 _ -> None
|
||||||
|
| Choice1Of3 (TestMemberFailure.Malformed reasons) ->
|
||||||
|
{
|
||||||
|
StackTrace = None
|
||||||
|
Message = reasons |> String.concat "\n" |> Some
|
||||||
|
}
|
||||||
|
|> Some
|
||||||
|
| Choice1Of3 (TestMemberFailure.Failed fail)
|
||||||
|
| Choice1Of3 (TestMemberFailure.Failed fail)
|
||||||
|
| Choice1Of3 (TestMemberFailure.Failed fail) ->
|
||||||
|
((None, None), fail)
|
||||||
|
||> List.fold (fun (stackTrace, message) tf ->
|
||||||
|
match tf with
|
||||||
|
| TestFailure.TestFailed (UserMethodFailure.Threw (_, exc))
|
||||||
|
| TestFailure.SetUpFailed (UserMethodFailure.Threw (_, exc))
|
||||||
|
| TestFailure.TearDownFailed (UserMethodFailure.Threw (_, exc)) ->
|
||||||
|
let stackTrace =
|
||||||
|
match stackTrace with
|
||||||
|
| None -> (exc : Exception).ToString ()
|
||||||
|
| Some s -> s
|
||||||
|
|
||||||
|
(Some stackTrace, message)
|
||||||
|
| TestFailure.TestFailed (UserMethodFailure.ReturnedNonUnit (_, ret))
|
||||||
|
| TestFailure.SetUpFailed (UserMethodFailure.ReturnedNonUnit (_, ret))
|
||||||
|
| TestFailure.TearDownFailed (UserMethodFailure.ReturnedNonUnit (_, ret)) ->
|
||||||
|
let newMessage = $"returned non-unit value %O{ret}"
|
||||||
|
|
||||||
|
let message =
|
||||||
|
match message with
|
||||||
|
| None -> newMessage
|
||||||
|
| Some message -> $"%s{message}\n%s{newMessage}"
|
||||||
|
|
||||||
|
(stackTrace, Some message)
|
||||||
|
)
|
||||||
|
|> fun (stackTrace, message) ->
|
||||||
|
{
|
||||||
|
StackTrace = stackTrace
|
||||||
|
Message = message
|
||||||
|
}
|
||||||
|
|> Some
|
||||||
|
| Choice3Of3 (UserMethodFailure.Threw (_, exc)) ->
|
||||||
|
{
|
||||||
|
StackTrace = (exc : Exception).ToString () |> Some
|
||||||
|
Message = None
|
||||||
|
}
|
||||||
|
|> Some
|
||||||
|
| Choice3Of3 (UserMethodFailure.ReturnedNonUnit (_, ret)) ->
|
||||||
|
{
|
||||||
|
Message = $"returned non-unit value %O{ret}" |> Some
|
||||||
|
StackTrace = None
|
||||||
|
}
|
||||||
|
|> Some
|
||||||
|
|
||||||
|
let outcome =
|
||||||
|
match cause with
|
||||||
|
| Choice1Of3 _ -> TrxTestOutcome.Failed
|
||||||
|
| Choice2Of3 TestMemberSuccess.Ok -> TrxTestOutcome.Passed
|
||||||
|
| Choice2Of3 (TestMemberSuccess.Inconclusive _) -> TrxTestOutcome.Inconclusive
|
||||||
|
| Choice2Of3 (TestMemberSuccess.Ignored _)
|
||||||
|
| Choice2Of3 (TestMemberSuccess.Explicit _) -> TrxTestOutcome.NotExecuted
|
||||||
|
// TODO: we can totally do better here, more fine-grained classification
|
||||||
|
| Choice3Of3 _ -> TrxTestOutcome.Failed
|
||||||
|
|
||||||
|
{
|
||||||
|
ExecutionId = i.ExecutionId
|
||||||
|
TestId = i.TestId
|
||||||
|
TestName = i.TestName
|
||||||
|
ComputerName = i.ComputerName
|
||||||
|
Duration = i.End - i.Start
|
||||||
|
StartTime = i.Start
|
||||||
|
EndTime = i.End
|
||||||
|
TestType = magicGuid
|
||||||
|
Outcome = outcome
|
||||||
|
TestListId = testListId
|
||||||
|
RelativeResultsDirectory = i.ExecutionId.ToString () // for some reason
|
||||||
|
Output =
|
||||||
|
match i.StdOut, i.StdErr, exc with
|
||||||
|
| None, None, None -> None
|
||||||
|
| stdout, stderr, exc ->
|
||||||
|
Some
|
||||||
|
{
|
||||||
|
TrxOutput.StdOut = stdout
|
||||||
|
StdErr = stderr
|
||||||
|
ErrorInfo = exc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid ()
|
||||||
|
Name = $"@%s{hostname} %s{finishTimeHumanReadable}"
|
||||||
|
Times = times
|
||||||
|
Settings = settings
|
||||||
|
Results = results
|
||||||
|
TestDefinitions = testDefinitions
|
||||||
|
TestEntries = testEntries
|
||||||
|
TestLists = [ testList ]
|
||||||
|
ResultsSummary = resultSummary
|
||||||
|
}
|
@@ -1,3 +1,26 @@
|
|||||||
|
WoofWare.NUnitTestRunner.Args inherit obj, implements WoofWare.NUnitTestRunner.Args System.IEquatable, System.Collections.IStructuralEquatable
|
||||||
|
WoofWare.NUnitTestRunner.Args..ctor [constructor]: (System.IO.FileInfo, System.IO.FileInfo option, (string * WoofWare.NUnitTestRunner.Filter) option, WoofWare.NUnitTestRunner.LogLevel, int option, System.TimeSpan option)
|
||||||
|
WoofWare.NUnitTestRunner.Args.Dll [property]: [read-only] System.IO.FileInfo
|
||||||
|
WoofWare.NUnitTestRunner.Args.Filter [property]: [read-only] (string * WoofWare.NUnitTestRunner.Filter) option
|
||||||
|
WoofWare.NUnitTestRunner.Args.get_Dll [method]: unit -> System.IO.FileInfo
|
||||||
|
WoofWare.NUnitTestRunner.Args.get_Filter [method]: unit -> (string * WoofWare.NUnitTestRunner.Filter) option
|
||||||
|
WoofWare.NUnitTestRunner.Args.get_LevelOfParallelism [method]: unit -> int option
|
||||||
|
WoofWare.NUnitTestRunner.Args.get_Logging [method]: unit -> WoofWare.NUnitTestRunner.LogLevel
|
||||||
|
WoofWare.NUnitTestRunner.Args.get_Timeout [method]: unit -> System.TimeSpan option
|
||||||
|
WoofWare.NUnitTestRunner.Args.get_Trx [method]: unit -> System.IO.FileInfo option
|
||||||
|
WoofWare.NUnitTestRunner.Args.LevelOfParallelism [property]: [read-only] int option
|
||||||
|
WoofWare.NUnitTestRunner.Args.Logging [property]: [read-only] WoofWare.NUnitTestRunner.LogLevel
|
||||||
|
WoofWare.NUnitTestRunner.Args.Parse [static method]: string list -> WoofWare.NUnitTestRunner.Args
|
||||||
|
WoofWare.NUnitTestRunner.Args.Timeout [property]: [read-only] System.TimeSpan option
|
||||||
|
WoofWare.NUnitTestRunner.Args.Trx [property]: [read-only] System.IO.FileInfo option
|
||||||
|
WoofWare.NUnitTestRunner.AssemblyLevelAttributes inherit obj, implements WoofWare.NUnitTestRunner.AssemblyLevelAttributes System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.AssemblyLevelAttributes System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
||||||
|
WoofWare.NUnitTestRunner.AssemblyLevelAttributes..ctor [constructor]: (int option, WoofWare.NUnitTestRunner.AssemblyParallelScope WoofWare.NUnitTestRunner.Parallelizable option)
|
||||||
|
WoofWare.NUnitTestRunner.AssemblyLevelAttributes.get_Parallelism [method]: unit -> int option
|
||||||
|
WoofWare.NUnitTestRunner.AssemblyLevelAttributes.get_Parallelizable [method]: unit -> WoofWare.NUnitTestRunner.AssemblyParallelScope WoofWare.NUnitTestRunner.Parallelizable option
|
||||||
|
WoofWare.NUnitTestRunner.AssemblyLevelAttributes.Parallelism [property]: [read-only] int option
|
||||||
|
WoofWare.NUnitTestRunner.AssemblyLevelAttributes.Parallelizable [property]: [read-only] WoofWare.NUnitTestRunner.AssemblyParallelScope WoofWare.NUnitTestRunner.Parallelizable option
|
||||||
|
WoofWare.NUnitTestRunner.AssemblyLevelAttributesModule inherit obj
|
||||||
|
WoofWare.NUnitTestRunner.AssemblyLevelAttributesModule.get [static method]: System.Reflection.Assembly -> WoofWare.NUnitTestRunner.AssemblyLevelAttributes
|
||||||
WoofWare.NUnitTestRunner.AssemblyParallelScope inherit obj, implements WoofWare.NUnitTestRunner.AssemblyParallelScope System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.AssemblyParallelScope System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 2 cases
|
WoofWare.NUnitTestRunner.AssemblyParallelScope inherit obj, implements WoofWare.NUnitTestRunner.AssemblyParallelScope System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.AssemblyParallelScope System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 2 cases
|
||||||
WoofWare.NUnitTestRunner.AssemblyParallelScope+Tags inherit obj
|
WoofWare.NUnitTestRunner.AssemblyParallelScope+Tags inherit obj
|
||||||
WoofWare.NUnitTestRunner.AssemblyParallelScope+Tags.Children [static field]: int = 0
|
WoofWare.NUnitTestRunner.AssemblyParallelScope+Tags.Children [static field]: int = 0
|
||||||
@@ -12,6 +35,8 @@ WoofWare.NUnitTestRunner.AssemblyParallelScope.get_Tag [method]: unit -> int
|
|||||||
WoofWare.NUnitTestRunner.AssemblyParallelScope.IsChildren [property]: [read-only] bool
|
WoofWare.NUnitTestRunner.AssemblyParallelScope.IsChildren [property]: [read-only] bool
|
||||||
WoofWare.NUnitTestRunner.AssemblyParallelScope.IsFixtures [property]: [read-only] bool
|
WoofWare.NUnitTestRunner.AssemblyParallelScope.IsFixtures [property]: [read-only] bool
|
||||||
WoofWare.NUnitTestRunner.AssemblyParallelScope.Tag [property]: [read-only] int
|
WoofWare.NUnitTestRunner.AssemblyParallelScope.Tag [property]: [read-only] int
|
||||||
|
WoofWare.NUnitTestRunner.BuildTrxReport inherit obj
|
||||||
|
WoofWare.NUnitTestRunner.BuildTrxReport.build [static method]: System.Reflection.Assembly -> System.DateTimeOffset -> System.DateTimeOffset -> WoofWare.NUnitTestRunner.FixtureRunResults list -> WoofWare.NUnitTestRunner.TrxReport
|
||||||
WoofWare.NUnitTestRunner.ClassParallelScope inherit obj, implements WoofWare.NUnitTestRunner.ClassParallelScope System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.ClassParallelScope System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 4 cases
|
WoofWare.NUnitTestRunner.ClassParallelScope inherit obj, implements WoofWare.NUnitTestRunner.ClassParallelScope System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.ClassParallelScope System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 4 cases
|
||||||
WoofWare.NUnitTestRunner.ClassParallelScope+Tags inherit obj
|
WoofWare.NUnitTestRunner.ClassParallelScope+Tags inherit obj
|
||||||
WoofWare.NUnitTestRunner.ClassParallelScope+Tags.All [static field]: int = 3
|
WoofWare.NUnitTestRunner.ClassParallelScope+Tags.All [static field]: int = 3
|
||||||
@@ -145,6 +170,20 @@ WoofWare.NUnitTestRunner.ITestProgress.OnTestMemberSkipped [method]: string -> u
|
|||||||
WoofWare.NUnitTestRunner.ITestProgress.OnTestMemberStart [method]: string -> unit
|
WoofWare.NUnitTestRunner.ITestProgress.OnTestMemberStart [method]: string -> unit
|
||||||
WoofWare.NUnitTestRunner.LoadContext inherit System.Runtime.Loader.AssemblyLoadContext
|
WoofWare.NUnitTestRunner.LoadContext inherit System.Runtime.Loader.AssemblyLoadContext
|
||||||
WoofWare.NUnitTestRunner.LoadContext..ctor [constructor]: (System.IO.FileInfo, System.IO.DirectoryInfo list, WoofWare.NUnitTestRunner.TestContexts)
|
WoofWare.NUnitTestRunner.LoadContext..ctor [constructor]: (System.IO.FileInfo, System.IO.DirectoryInfo list, WoofWare.NUnitTestRunner.TestContexts)
|
||||||
|
WoofWare.NUnitTestRunner.LogLevel inherit obj, implements WoofWare.NUnitTestRunner.LogLevel System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.LogLevel System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 2 cases
|
||||||
|
WoofWare.NUnitTestRunner.LogLevel+Tags inherit obj
|
||||||
|
WoofWare.NUnitTestRunner.LogLevel+Tags.Nothing [static field]: int = 0
|
||||||
|
WoofWare.NUnitTestRunner.LogLevel+Tags.Verbose [static field]: int = 1
|
||||||
|
WoofWare.NUnitTestRunner.LogLevel.get_IsNothing [method]: unit -> bool
|
||||||
|
WoofWare.NUnitTestRunner.LogLevel.get_IsVerbose [method]: unit -> bool
|
||||||
|
WoofWare.NUnitTestRunner.LogLevel.get_Nothing [static method]: unit -> WoofWare.NUnitTestRunner.LogLevel
|
||||||
|
WoofWare.NUnitTestRunner.LogLevel.get_Tag [method]: unit -> int
|
||||||
|
WoofWare.NUnitTestRunner.LogLevel.get_Verbose [static method]: unit -> WoofWare.NUnitTestRunner.LogLevel
|
||||||
|
WoofWare.NUnitTestRunner.LogLevel.IsNothing [property]: [read-only] bool
|
||||||
|
WoofWare.NUnitTestRunner.LogLevel.IsVerbose [property]: [read-only] bool
|
||||||
|
WoofWare.NUnitTestRunner.LogLevel.Nothing [static property]: [read-only] WoofWare.NUnitTestRunner.LogLevel
|
||||||
|
WoofWare.NUnitTestRunner.LogLevel.Tag [property]: [read-only] int
|
||||||
|
WoofWare.NUnitTestRunner.LogLevel.Verbose [static property]: [read-only] WoofWare.NUnitTestRunner.LogLevel
|
||||||
WoofWare.NUnitTestRunner.Match inherit obj, implements WoofWare.NUnitTestRunner.Match System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.Match System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 2 cases
|
WoofWare.NUnitTestRunner.Match inherit obj, implements WoofWare.NUnitTestRunner.Match System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.Match System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 2 cases
|
||||||
WoofWare.NUnitTestRunner.Match+Contains inherit WoofWare.NUnitTestRunner.Match
|
WoofWare.NUnitTestRunner.Match+Contains inherit WoofWare.NUnitTestRunner.Match
|
||||||
WoofWare.NUnitTestRunner.Match+Contains.get_Item [method]: unit -> string
|
WoofWare.NUnitTestRunner.Match+Contains.get_Item [method]: unit -> string
|
||||||
@@ -226,6 +265,10 @@ WoofWare.NUnitTestRunner.SingleTestMethodModule inherit obj
|
|||||||
WoofWare.NUnitTestRunner.SingleTestMethodModule.parse [static method]: string list -> System.Reflection.MethodInfo -> System.Reflection.CustomAttributeData list -> (WoofWare.NUnitTestRunner.SingleTestMethod option * System.Reflection.CustomAttributeData list)
|
WoofWare.NUnitTestRunner.SingleTestMethodModule.parse [static method]: string list -> System.Reflection.MethodInfo -> System.Reflection.CustomAttributeData list -> (WoofWare.NUnitTestRunner.SingleTestMethod option * System.Reflection.CustomAttributeData list)
|
||||||
WoofWare.NUnitTestRunner.TestContexts inherit obj, implements WoofWare.NUnitTestRunner.TestContexts System.IEquatable, System.Collections.IStructuralEquatable, IDisposable
|
WoofWare.NUnitTestRunner.TestContexts inherit obj, implements WoofWare.NUnitTestRunner.TestContexts System.IEquatable, System.Collections.IStructuralEquatable, IDisposable
|
||||||
WoofWare.NUnitTestRunner.TestContexts.Empty [static method]: unit -> WoofWare.NUnitTestRunner.TestContexts
|
WoofWare.NUnitTestRunner.TestContexts.Empty [static method]: unit -> WoofWare.NUnitTestRunner.TestContexts
|
||||||
|
WoofWare.NUnitTestRunner.TestContexts.get_Stderr [method]: unit -> System.IO.TextWriter
|
||||||
|
WoofWare.NUnitTestRunner.TestContexts.get_Stdout [method]: unit -> System.IO.TextWriter
|
||||||
|
WoofWare.NUnitTestRunner.TestContexts.Stderr [property]: [read-only] System.IO.TextWriter
|
||||||
|
WoofWare.NUnitTestRunner.TestContexts.Stdout [property]: [read-only] System.IO.TextWriter
|
||||||
WoofWare.NUnitTestRunner.TestFailure inherit obj, implements WoofWare.NUnitTestRunner.TestFailure System.IEquatable, System.Collections.IStructuralEquatable - union type with 3 cases
|
WoofWare.NUnitTestRunner.TestFailure inherit obj, implements WoofWare.NUnitTestRunner.TestFailure System.IEquatable, System.Collections.IStructuralEquatable - union type with 3 cases
|
||||||
WoofWare.NUnitTestRunner.TestFailure+SetUpFailed inherit WoofWare.NUnitTestRunner.TestFailure
|
WoofWare.NUnitTestRunner.TestFailure+SetUpFailed inherit WoofWare.NUnitTestRunner.TestFailure
|
||||||
WoofWare.NUnitTestRunner.TestFailure+SetUpFailed.get_Item [method]: unit -> WoofWare.NUnitTestRunner.UserMethodFailure
|
WoofWare.NUnitTestRunner.TestFailure+SetUpFailed.get_Item [method]: unit -> WoofWare.NUnitTestRunner.UserMethodFailure
|
||||||
@@ -356,6 +399,7 @@ WoofWare.NUnitTestRunner.TestMemberSuccess.Ok [static property]: [read-only] Woo
|
|||||||
WoofWare.NUnitTestRunner.TestMemberSuccess.Tag [property]: [read-only] int
|
WoofWare.NUnitTestRunner.TestMemberSuccess.Tag [property]: [read-only] int
|
||||||
WoofWare.NUnitTestRunner.TestProgress inherit obj
|
WoofWare.NUnitTestRunner.TestProgress inherit obj
|
||||||
WoofWare.NUnitTestRunner.TestProgress.toStderr [static method]: unit -> WoofWare.NUnitTestRunner.ITestProgress
|
WoofWare.NUnitTestRunner.TestProgress.toStderr [static method]: unit -> WoofWare.NUnitTestRunner.ITestProgress
|
||||||
|
WoofWare.NUnitTestRunner.TestProgress.toWriter [static method]: System.IO.TextWriter -> WoofWare.NUnitTestRunner.ITestProgress
|
||||||
WoofWare.NUnitTestRunner.TrxCounters inherit obj, implements WoofWare.NUnitTestRunner.TrxCounters System.IEquatable, System.Collections.IStructuralEquatable
|
WoofWare.NUnitTestRunner.TrxCounters inherit obj, implements WoofWare.NUnitTestRunner.TrxCounters System.IEquatable, System.Collections.IStructuralEquatable
|
||||||
WoofWare.NUnitTestRunner.TrxCounters..ctor [constructor]: (System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32)
|
WoofWare.NUnitTestRunner.TrxCounters..ctor [constructor]: (System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32)
|
||||||
WoofWare.NUnitTestRunner.TrxCounters.Aborted [property]: [read-only] System.UInt32
|
WoofWare.NUnitTestRunner.TrxCounters.Aborted [property]: [read-only] System.UInt32
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
namespace WoofWare.NUnitTestRunner
|
namespace WoofWare.NUnitTestRunner
|
||||||
|
|
||||||
open System
|
open System
|
||||||
|
open System.IO
|
||||||
|
|
||||||
/// Represents something which knows how to report progress through a test suite.
|
/// Represents something which knows how to report progress through a test suite.
|
||||||
/// Note that we don't guarantee anything about parallelism; you must make sure
|
/// Note that we don't guarantee anything about parallelism; you must make sure
|
||||||
@@ -24,22 +25,25 @@ type ITestProgress =
|
|||||||
/// Methods for constructing specific ITestProgress objects.
|
/// Methods for constructing specific ITestProgress objects.
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module TestProgress =
|
module TestProgress =
|
||||||
/// An ITestProgress which logs to stderr.
|
/// An ITestProgress which logs to the given writer.
|
||||||
let toStderr () : ITestProgress =
|
let toWriter (writer : TextWriter) : ITestProgress =
|
||||||
{ new ITestProgress with
|
{ new ITestProgress with
|
||||||
member _.OnTestFixtureStart name testCount =
|
member _.OnTestFixtureStart name testCount =
|
||||||
let plural = if testCount = 1 then "" else "s"
|
let plural = if testCount = 1 then "" else "s"
|
||||||
Console.Error.WriteLine $"Running test fixture: %s{name} (%i{testCount} test%s{plural} to run)"
|
writer.WriteLine $"Running test fixture: %s{name} (%i{testCount} test%s{plural} to run)"
|
||||||
|
|
||||||
member _.OnTestMemberStart name =
|
member _.OnTestMemberStart name =
|
||||||
Console.Error.WriteLine $"Running test: %s{name}"
|
writer.WriteLine $"Running test: %s{name}"
|
||||||
|
|
||||||
member _.OnTestFailed name failure =
|
member _.OnTestFailed name failure =
|
||||||
Console.Error.WriteLine $"Test failed: %O{failure}"
|
writer.WriteLine $"Test failed: %O{failure}"
|
||||||
|
|
||||||
member _.OnTestMemberFinished name =
|
member _.OnTestMemberFinished name =
|
||||||
Console.Error.WriteLine $"Finished test %s{name}"
|
writer.WriteLine $"Finished test %s{name}"
|
||||||
|
|
||||||
member _.OnTestMemberSkipped name =
|
member _.OnTestMemberSkipped name =
|
||||||
Console.Error.WriteLine $"Skipping test due to filter: %s{name}"
|
writer.WriteLine $"Skipping test due to filter: %s{name}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An ITestProgress which logs to stderr.
|
||||||
|
let toStderr () : ITestProgress = toWriter Console.Error
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
<PackageId>WoofWare.NUnitTestRunner.Lib</PackageId>
|
<PackageId>WoofWare.NUnitTestRunner.Lib</PackageId>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
<WarnOn>FS3559</WarnOn>
|
<WarnOn>FS3559</WarnOn>
|
||||||
<WoofWareMyriadPluginVersion>2.1.45</WoofWareMyriadPluginVersion>
|
<WoofWareMyriadPluginVersion>2.1.51</WoofWareMyriadPluginVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
<Compile Include="Result.fs" />
|
<Compile Include="Result.fs" />
|
||||||
<Compile Include="Domain.fs" />
|
<Compile Include="Domain.fs" />
|
||||||
<Compile Include="Filter.fs" />
|
<Compile Include="Filter.fs" />
|
||||||
|
<Compile Include="Args.fs" />
|
||||||
<Compile Include="ParallelQueue.fs" />
|
<Compile Include="ParallelQueue.fs" />
|
||||||
<Compile Include="SingleTestMethod.fs" />
|
<Compile Include="SingleTestMethod.fs" />
|
||||||
<Compile Include="TestProgress.fs" />
|
<Compile Include="TestProgress.fs" />
|
||||||
@@ -36,6 +37,8 @@
|
|||||||
<Compile Include="TestFixture.fs" />
|
<Compile Include="TestFixture.fs" />
|
||||||
<Compile Include="Xml.fs" />
|
<Compile Include="Xml.fs" />
|
||||||
<Compile Include="TrxReport.fs" />
|
<Compile Include="TrxReport.fs" />
|
||||||
|
<Compile Include="CreateTrxReport.fs" />
|
||||||
|
<Compile Include="AssemblyLevelAttributes.fs" />
|
||||||
<None Include="..\README.md">
|
<None Include="..\README.md">
|
||||||
<Pack>True</Pack>
|
<Pack>True</Pack>
|
||||||
<PackagePath>\</PackagePath>
|
<PackagePath>\</PackagePath>
|
||||||
@@ -44,14 +47,14 @@
|
|||||||
<EmbeddedResource Include="version.json" />
|
<EmbeddedResource Include="version.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="WoofWare.PrattParser" Version="0.2.1" />
|
<PackageReference Include="WoofWare.PrattParser" Version="0.2.2" />
|
||||||
<PackageReference Update="FSharp.Core" Version="6.0.0" />
|
<PackageReference Update="FSharp.Core" Version="6.0.0" />
|
||||||
<PackageReference Include="WoofWare.DotnetRuntimeLocator" Version="0.1.9" />
|
<PackageReference Include="WoofWare.DotnetRuntimeLocator" Version="0.1.9" />
|
||||||
<PackageReference Include="WoofWare.Myriad.Plugins.Attributes" Version="3.1.7" />
|
<PackageReference Include="WoofWare.Myriad.Plugins.Attributes" Version="3.1.7" />
|
||||||
<PackageReference Include="Myriad.SDK" Version="0.8.3" />
|
<PackageReference Include="Myriad.SDK" Version="0.8.3" PrivateAssets="all" />
|
||||||
<PackageReference Include="WoofWare.Myriad.Plugins" Version="$(WoofWareMyriadPluginVersion)" PrivateAssets="all" />
|
<PackageReference Include="WoofWare.Myriad.Plugins" Version="$(WoofWareMyriadPluginVersion)" PrivateAssets="all" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<MyriadSdkGenerator Include="$(NuGetPackageRoot)/woofware.myriad.plugins/$(WoofWareMyriadPluginVersion)/lib/net6.0/WoofWare.Myriad.Plugins.dll" />
|
<MyriadSdkGenerator Include="$(NuGetPackageRoot)/woofware.myriad.plugins/$(WoofWareMyriadPluginVersion)/lib/net6.0/WoofWare.Myriad.Plugins.dll" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "0.13",
|
"version": "0.16",
|
||||||
"publicReleaseRefSpec": [
|
"publicReleaseRefSpec": [
|
||||||
"^refs/heads/main$"
|
"^refs/heads/main$"
|
||||||
],
|
],
|
||||||
@@ -8,4 +8,4 @@
|
|||||||
":/Directory.Build.props",
|
":/Directory.Build.props",
|
||||||
":/README.md"
|
":/README.md"
|
||||||
]
|
]
|
||||||
}
|
}
|
35
WoofWare.NUnitTestRunner.StartupHook/StartupHook.cs
Normal file
35
WoofWare.NUnitTestRunner.StartupHook/StartupHook.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Loader;
|
||||||
|
using WoofWare.NUnitTestRunner.StartupHook;
|
||||||
|
|
||||||
|
namespace WoofWare.NUnitTestRunner.StartupHook
|
||||||
|
{
|
||||||
|
internal class StartupAssemblyLoadContext : AssemblyLoadContext
|
||||||
|
{
|
||||||
|
private readonly AssemblyDependencyResolver _resolver;
|
||||||
|
|
||||||
|
public StartupAssemblyLoadContext()
|
||||||
|
{
|
||||||
|
_resolver = new AssemblyDependencyResolver(Assembly.GetExecutingAssembly().Location);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Assembly Load(AssemblyName assemblyName)
|
||||||
|
{
|
||||||
|
var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
|
||||||
|
|
||||||
|
return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be internal and called `StartupHook`
|
||||||
|
internal class StartupHook
|
||||||
|
{
|
||||||
|
public static void Initialize()
|
||||||
|
{
|
||||||
|
var loadContext = new StartupAssemblyLoadContext();
|
||||||
|
var assembly = loadContext.LoadFromAssemblyName(new AssemblyName("WoofWare.NUnitTestRunner.StartupHookLogic"));
|
||||||
|
assembly.DefinedTypes.First(x => x.Name == "StartupHookLogic").GetDeclaredMethod("DoIt")!.Invoke(null, null);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\WoofWare.NUnitTestRunner.StartupHookLogic\WoofWare.NUnitTestRunner.StartupHookLogic.csproj" />
|
||||||
|
<ProjectReference Include="..\WoofWare.NUnitTestRunner.Lib\WoofWare.NUnitTestRunner.Lib.fsproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="StartupHook.cs"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
102
WoofWare.NUnitTestRunner.StartupHookLogic/StartupHookLogic.cs
Normal file
102
WoofWare.NUnitTestRunner.StartupHookLogic/StartupHookLogic.cs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.FSharp.Collections;
|
||||||
|
using Microsoft.FSharp.Core;
|
||||||
|
|
||||||
|
namespace WoofWare.NUnitTestRunner.StartupHookLogic;
|
||||||
|
|
||||||
|
public class StartupHookLogic
|
||||||
|
{
|
||||||
|
private static void DoIt()
|
||||||
|
{
|
||||||
|
// Load test runner lib
|
||||||
|
var nunitLib = Environment.GetEnvironmentVariable("WOOFWARE_NUNIT_LIB");
|
||||||
|
if (string.IsNullOrEmpty(nunitLib))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("WoofWare.NUnitTestRunner hook expects to run with WOOFWARE_NUNIT_LIB set to WoofWare.NUnitTestRunner.Lib.dll");
|
||||||
|
}
|
||||||
|
Assembly.LoadFrom(nunitLib);
|
||||||
|
|
||||||
|
var startTime = DateTimeOffset.Now;
|
||||||
|
|
||||||
|
// Arg parsing
|
||||||
|
|
||||||
|
var filterVar = Environment.GetEnvironmentVariable("WOOFWARE_NUNIT_FILTER");
|
||||||
|
static bool NoFilter(TestFixture f, SingleTestMethod g)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
FSharpFunc<TestFixture, FSharpFunc<SingleTestMethod, bool>> filter;
|
||||||
|
if (string.IsNullOrEmpty(filterVar))
|
||||||
|
{
|
||||||
|
filter = FuncConvert.FromFunc<TestFixture, SingleTestMethod, bool>(NoFilter);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
filter = FilterModule.shouldRun(FilterModule.parse(filterVar));
|
||||||
|
}
|
||||||
|
|
||||||
|
var assy = Assembly.GetEntryAssembly()!;
|
||||||
|
|
||||||
|
var attrs = AssemblyLevelAttributesModule.get(assy);
|
||||||
|
|
||||||
|
FSharpOption<int> levelOfParallelism;
|
||||||
|
var parallelismVar = Environment.GetEnvironmentVariable("WOOFWARE_NUNIT_PARALLELISM_LEVEL");
|
||||||
|
if (string.IsNullOrEmpty(parallelismVar))
|
||||||
|
{
|
||||||
|
levelOfParallelism = attrs.Parallelism;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
levelOfParallelism = FSharpOption<int>.Some(Int32.Parse(parallelismVar));
|
||||||
|
}
|
||||||
|
|
||||||
|
var testFixtures = assy.ExportedTypes.Select(TestFixtureModule.parse);
|
||||||
|
using var par =
|
||||||
|
new ParallelQueue(levelOfParallelism, attrs.Parallelizable, FSharpOption<CancellationToken>.None);
|
||||||
|
var creationTime = DateTimeOffset.Now;
|
||||||
|
|
||||||
|
var timeoutVar = Environment.GetEnvironmentVariable("WOOFWARE_NUNIT_TIMEOUT_SECS");
|
||||||
|
TimeSpan timeout;
|
||||||
|
if (string.IsNullOrEmpty(timeoutVar))
|
||||||
|
{
|
||||||
|
timeout = TimeSpan.FromHours(2.0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timeout = TimeSpan.FromSeconds(Int32.Parse(timeoutVar));
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalErr = Console.Error;
|
||||||
|
using var contexts = TestContexts.Empty();
|
||||||
|
Console.SetOut(contexts.Stdout);
|
||||||
|
Console.SetError(contexts.Stderr);
|
||||||
|
var results =
|
||||||
|
Task.WhenAll(testFixtures.Select(x =>
|
||||||
|
TestFixtureModule.run(contexts, par, TestProgress.toWriter(normalErr), filter, x)));
|
||||||
|
|
||||||
|
if (!results.Wait(timeout))
|
||||||
|
{
|
||||||
|
throw new Exception($"Tests failed to terminate within timeout of {timeout}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var sorted =
|
||||||
|
results.Result.SelectMany(x => x);
|
||||||
|
var report = BuildTrxReport.build(assy, creationTime, startTime, ListModule.OfSeq(sorted));
|
||||||
|
|
||||||
|
var trxFile = Environment.GetEnvironmentVariable("WOOFWARE_NUNIT_GENERATE_TRX");
|
||||||
|
if (!string.IsNullOrEmpty(trxFile))
|
||||||
|
{
|
||||||
|
var contents = TrxReportModule.toXml(report).OuterXml;
|
||||||
|
var trxPath = new FileInfo(trxFile);
|
||||||
|
trxPath.Directory!.Create();
|
||||||
|
File.WriteAllText(trxPath.FullName, contents);
|
||||||
|
normalErr.WriteLine($"Written TRX file: {trxPath.FullName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (report.ResultsSummary.Outcome.Equals(TrxOutcome.Completed))
|
||||||
|
Environment.Exit(0);
|
||||||
|
else
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,18 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\WoofWare.NUnitTestRunner.Lib\WoofWare.NUnitTestRunner.Lib.fsproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="StartupHookLogic.cs"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@@ -8,6 +8,10 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.NUnitTestRunner.Li
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.NUnitTestRunner.Test", "WoofWare.NUnitTestRunner\WoofWare.NUnitTestRunner.Test\WoofWare.NUnitTestRunner.Test.fsproj", "{443B01B3-2A8C-45CF-96D6-1D890EEA0533}"
|
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.NUnitTestRunner.Test", "WoofWare.NUnitTestRunner\WoofWare.NUnitTestRunner.Test\WoofWare.NUnitTestRunner.Test.fsproj", "{443B01B3-2A8C-45CF-96D6-1D890EEA0533}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WoofWare.NUnitTestRunner.StartupHook", "WoofWare.NUnitTestRunner.StartupHook\WoofWare.NUnitTestRunner.StartupHook.csproj", "{E2C73D96-570C-4E3C-B997-707AF8BB0E6A}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WoofWare.NUnitTestRunner.StartupHookLogic", "WoofWare.NUnitTestRunner.StartupHookLogic\WoofWare.NUnitTestRunner.StartupHookLogic.csproj", "{A70627C8-9D19-42C2-AFEB-CFBDDDCE045D}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -30,5 +34,13 @@ Global
|
|||||||
{443B01B3-2A8C-45CF-96D6-1D890EEA0533}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{443B01B3-2A8C-45CF-96D6-1D890EEA0533}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{443B01B3-2A8C-45CF-96D6-1D890EEA0533}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{443B01B3-2A8C-45CF-96D6-1D890EEA0533}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{443B01B3-2A8C-45CF-96D6-1D890EEA0533}.Release|Any CPU.Build.0 = Release|Any CPU
|
{443B01B3-2A8C-45CF-96D6-1D890EEA0533}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E2C73D96-570C-4E3C-B997-707AF8BB0E6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{E2C73D96-570C-4E3C-B997-707AF8BB0E6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E2C73D96-570C-4E3C-B997-707AF8BB0E6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E2C73D96-570C-4E3C-B997-707AF8BB0E6A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A70627C8-9D19-42C2-AFEB-CFBDDDCE045D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A70627C8-9D19-42C2-AFEB-CFBDDDCE045D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A70627C8-9D19-42C2-AFEB-CFBDDDCE045D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A70627C8-9D19-42C2-AFEB-CFBDDDCE045D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
@@ -1,113 +1,22 @@
|
|||||||
namespace WoofWare.NUnitTestRunner
|
namespace WoofWare.NUnitTestRunner
|
||||||
|
|
||||||
open System
|
open System
|
||||||
|
open System.Diagnostics
|
||||||
open System.IO
|
open System.IO
|
||||||
|
open System.Reflection
|
||||||
open System.Threading.Tasks
|
open System.Threading.Tasks
|
||||||
open Spectre.Console
|
open Spectre.Console
|
||||||
|
|
||||||
// Fix for https://github.com/Smaug123/unofficial-nunit-runner/issues/8
|
|
||||||
// Set AppContext.BaseDirectory to where the test DLL is.
|
|
||||||
// (This tells the DLL loader to look next to the test DLL for dependencies.)
|
|
||||||
type SetBaseDir (testDll : FileInfo) =
|
|
||||||
let oldBaseDir = AppContext.BaseDirectory
|
|
||||||
do AppContext.SetData ("APP_CONTEXT_BASE_DIRECTORY", testDll.Directory.FullName)
|
|
||||||
|
|
||||||
interface IDisposable with
|
|
||||||
member _.Dispose () =
|
|
||||||
AppContext.SetData ("APP_CONTEXT_BASE_DIRECTORY", oldBaseDir)
|
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
|
||||||
type LogLevel =
|
|
||||||
| Nothing
|
|
||||||
| Verbose
|
|
||||||
|
|
||||||
[<AutoOpen>]
|
|
||||||
module Patterns =
|
|
||||||
let (|Key|_|) (start : string) (s : string) : string option =
|
|
||||||
if s.StartsWith (start + "=", StringComparison.Ordinal) then
|
|
||||||
s.Substring (start.Length + 1) |> Some
|
|
||||||
else
|
|
||||||
None
|
|
||||||
|
|
||||||
type Args =
|
|
||||||
{
|
|
||||||
Dll : FileInfo
|
|
||||||
Trx : FileInfo option
|
|
||||||
Filter : Filter option
|
|
||||||
Logging : LogLevel
|
|
||||||
LevelOfParallelism : int option
|
|
||||||
Timeout : TimeSpan option
|
|
||||||
}
|
|
||||||
|
|
||||||
static member Parse (args : string list) : Args =
|
|
||||||
match args with
|
|
||||||
| [] -> failwith "The first arg must be a positional arg, the DLL to test."
|
|
||||||
| dll :: args ->
|
|
||||||
|
|
||||||
let rec go
|
|
||||||
(trx : FileInfo option)
|
|
||||||
(filter : Filter option)
|
|
||||||
(logging : LogLevel option)
|
|
||||||
(par : int option)
|
|
||||||
(timeout : TimeSpan option)
|
|
||||||
(args : string list)
|
|
||||||
=
|
|
||||||
match args with
|
|
||||||
| [] ->
|
|
||||||
{
|
|
||||||
Dll = FileInfo dll
|
|
||||||
Trx = trx
|
|
||||||
Filter = filter
|
|
||||||
Logging = logging |> Option.defaultValue LogLevel.Nothing
|
|
||||||
LevelOfParallelism = par
|
|
||||||
Timeout = timeout
|
|
||||||
}
|
|
||||||
| Key "--filter" filterStr :: rest
|
|
||||||
| "--filter" :: filterStr :: rest ->
|
|
||||||
match filter with
|
|
||||||
| Some _ -> failwith "Two conflicting filters; you can only specify --filter once"
|
|
||||||
| None -> go trx (Some (Filter.parse filterStr)) logging par timeout rest
|
|
||||||
| Key "--trx" trxStr :: rest
|
|
||||||
| "--trx" :: trxStr :: rest ->
|
|
||||||
match trx with
|
|
||||||
| Some _ -> failwith "Two conflicting TRX outputs; you can only specify --trx once"
|
|
||||||
| None -> go (Some (FileInfo trxStr)) filter logging par timeout rest
|
|
||||||
| Key "--verbose" verboseStr :: rest
|
|
||||||
| "--verbose" :: verboseStr :: rest ->
|
|
||||||
match logging with
|
|
||||||
| Some _ -> failwith "Two conflicting --verbose outputs; you can only specify --verbose once"
|
|
||||||
| None ->
|
|
||||||
let verbose =
|
|
||||||
if Boolean.Parse verboseStr then
|
|
||||||
LogLevel.Verbose
|
|
||||||
else
|
|
||||||
LogLevel.Nothing
|
|
||||||
|
|
||||||
go trx filter (Some verbose) par timeout rest
|
|
||||||
| Key "--parallelism" parStr :: rest
|
|
||||||
| "--parallelism" :: parStr :: rest ->
|
|
||||||
match par with
|
|
||||||
| Some _ -> failwith "Two conflicting --parallelism outputs; you can only specify --parallelism once"
|
|
||||||
| None -> go trx filter logging (Some (Int32.Parse parStr)) timeout rest
|
|
||||||
| Key "--timeout-seconds" timeoutStr :: rest
|
|
||||||
| "--timeout-seconds" :: timeoutStr :: rest ->
|
|
||||||
match timeout with
|
|
||||||
| Some _ ->
|
|
||||||
failwith "Two conflicting --timeout-seconds outputs; you can only specify --timeout-seconds once"
|
|
||||||
| None -> go trx filter logging par (Some (TimeSpan.FromSeconds (Int32.Parse timeoutStr |> float))) rest
|
|
||||||
| k :: _rest -> failwith $"Unrecognised arg %s{k}"
|
|
||||||
|
|
||||||
go None None None None None args
|
|
||||||
|
|
||||||
module Program =
|
module Program =
|
||||||
let main argv =
|
// This is actually transcribed into C# in WoofWare.NUnitTestRunner.StartupHookLogic.
|
||||||
|
let execute argv =
|
||||||
let startTime = DateTimeOffset.Now
|
let startTime = DateTimeOffset.Now
|
||||||
|
|
||||||
let args = argv |> List.ofArray |> Args.Parse
|
let args = argv |> List.ofArray |> Args.Parse
|
||||||
|
|
||||||
let filter =
|
let filter =
|
||||||
match args.Filter with
|
match args.Filter with
|
||||||
| Some filter -> Filter.shouldRun filter
|
| Some (_, filter) -> Filter.shouldRun filter
|
||||||
| None -> fun _ _ -> true
|
| None -> fun _ _ -> true
|
||||||
|
|
||||||
let stderr =
|
let stderr =
|
||||||
@@ -125,54 +34,15 @@ module Program =
|
|||||||
for d in runtime do
|
for d in runtime do
|
||||||
stderr.WriteLine $".NET runtime directory: %s{d.FullName}"
|
stderr.WriteLine $".NET runtime directory: %s{d.FullName}"
|
||||||
|
|
||||||
use _ = new SetBaseDir (args.Dll)
|
|
||||||
|
|
||||||
use contexts = TestContexts.Empty ()
|
use contexts = TestContexts.Empty ()
|
||||||
|
|
||||||
let ctx = LoadContext (args.Dll, runtime, contexts)
|
let ctx = LoadContext (args.Dll, runtime, contexts)
|
||||||
let assy = ctx.LoadFromAssemblyPath args.Dll.FullName
|
let assy = ctx.LoadFromAssemblyPath args.Dll.FullName
|
||||||
|
|
||||||
let levelOfParallelism, par =
|
let attrs = AssemblyLevelAttributes.get assy
|
||||||
((None, None), assy.CustomAttributes)
|
|
||||||
||> Seq.fold (fun (levelPar, par) attr ->
|
|
||||||
match attr.AttributeType.FullName with
|
|
||||||
| "NUnit.Framework.LevelOfParallelismAttribute" ->
|
|
||||||
let arg = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<int>
|
|
||||||
|
|
||||||
match levelPar with
|
|
||||||
| None -> (Some arg, par)
|
|
||||||
| Some existing ->
|
|
||||||
failwith $"Assembly %s{assy.Location} declares parallelism %i{arg} and also %i{existing}"
|
|
||||||
| "NUnit.Framework.NonParallelizableAttribute" ->
|
|
||||||
match levelPar with
|
|
||||||
| None -> (Some 1, par)
|
|
||||||
| Some existing ->
|
|
||||||
failwith
|
|
||||||
$"Assembly %s{assy.Location} declares non-parallelizable and also parallelism %i{existing}"
|
|
||||||
| "NUnit.Framework.ParallelizableAttribute" ->
|
|
||||||
match par with
|
|
||||||
| Some _ -> failwith "Got multiple Parallelize attributes in assembly"
|
|
||||||
| None ->
|
|
||||||
match attr.ConstructorArguments |> Seq.toList with
|
|
||||||
| [] -> levelPar, Some (Parallelizable.Yes AssemblyParallelScope.Fixtures)
|
|
||||||
| [ v ] ->
|
|
||||||
match v.Value with
|
|
||||||
| :? int as v ->
|
|
||||||
match v with
|
|
||||||
| 512 -> levelPar, Some (Parallelizable.Yes AssemblyParallelScope.Fixtures)
|
|
||||||
| 256 -> levelPar, Some (Parallelizable.Yes AssemblyParallelScope.Children)
|
|
||||||
| 257 ->
|
|
||||||
failwith "ParallelScope.All is invalid on assemblies; only Fixtures or Children"
|
|
||||||
| 1 ->
|
|
||||||
failwith "ParallelScope.Self is invalid on assemblies; only Fixtures or Children"
|
|
||||||
| v -> failwith $"Could not recognise value %i{v} of parallel scope on assembly"
|
|
||||||
| v -> failwith $"Unexpectedly non-int value %O{v} of parallel scope on assembly"
|
|
||||||
| _ -> failwith "unexpectedly got multiple args to Parallelizable on assembly"
|
|
||||||
| _ -> levelPar, par
|
|
||||||
)
|
|
||||||
|
|
||||||
let levelOfParallelism =
|
let levelOfParallelism =
|
||||||
match args.LevelOfParallelism, levelOfParallelism with
|
match args.LevelOfParallelism, attrs.Parallelism with
|
||||||
| None, None -> None
|
| None, None -> None
|
||||||
| Some taken, Some ignored ->
|
| Some taken, Some ignored ->
|
||||||
match args.Logging with
|
match args.Logging with
|
||||||
@@ -187,7 +57,7 @@ module Program =
|
|||||||
|
|
||||||
let testFixtures = assy.ExportedTypes |> Seq.map TestFixture.parse |> Seq.toList
|
let testFixtures = assy.ExportedTypes |> Seq.map TestFixture.parse |> Seq.toList
|
||||||
|
|
||||||
use par = new ParallelQueue (levelOfParallelism, par)
|
use par = new ParallelQueue (levelOfParallelism, attrs.Parallelizable)
|
||||||
|
|
||||||
let creationTime = DateTimeOffset.Now
|
let creationTime = DateTimeOffset.Now
|
||||||
|
|
||||||
@@ -206,233 +76,7 @@ module Program =
|
|||||||
|
|
||||||
let results = results.Result |> Seq.concat |> List.ofSeq
|
let results = results.Result |> Seq.concat |> List.ofSeq
|
||||||
|
|
||||||
let finishTime = DateTimeOffset.Now
|
let report = BuildTrxReport.build assy creationTime startTime results
|
||||||
let finishTimeHumanReadable = finishTime.ToString @"yyyy-MM-dd HH:mm:ss"
|
|
||||||
let nowMachine = finishTime.ToString @"yyyy-MM-dd_HH_mm_ss"
|
|
||||||
|
|
||||||
let testListId = Guid.NewGuid ()
|
|
||||||
|
|
||||||
let testDefinitions, testEntries =
|
|
||||||
results
|
|
||||||
|> List.collect (fun results -> results.IndividualTestRunMetadata)
|
|
||||||
|> List.map (fun (data, _) ->
|
|
||||||
let defn =
|
|
||||||
{
|
|
||||||
Name = data.TestName
|
|
||||||
Storage = assy.Location.ToLowerInvariant ()
|
|
||||||
Id = data.TestId
|
|
||||||
Execution =
|
|
||||||
{
|
|
||||||
Id = data.ExecutionId
|
|
||||||
}
|
|
||||||
TestMethod =
|
|
||||||
{
|
|
||||||
CodeBase = assy.Location
|
|
||||||
AdapterTypeName = Uri "executor://woofware/"
|
|
||||||
ClassName = data.ClassName
|
|
||||||
Name = data.TestName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let entry : TrxTestEntry =
|
|
||||||
{
|
|
||||||
TestListId = testListId
|
|
||||||
ExecutionId = data.ExecutionId
|
|
||||||
TestId = data.TestId
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
defn, entry
|
|
||||||
)
|
|
||||||
|> List.unzip
|
|
||||||
|
|
||||||
let hostname = Environment.MachineName
|
|
||||||
|
|
||||||
let settings =
|
|
||||||
{
|
|
||||||
Name = "default"
|
|
||||||
Id = Guid.NewGuid ()
|
|
||||||
Deployment =
|
|
||||||
{
|
|
||||||
RunDeploymentRoot = $"_%s{hostname}_%s{nowMachine}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let testList : TrxTestListEntry =
|
|
||||||
{
|
|
||||||
Id = testListId
|
|
||||||
Name = "All"
|
|
||||||
}
|
|
||||||
|
|
||||||
let counters =
|
|
||||||
(TrxCounters.Zero, results)
|
|
||||||
// TODO: this is woefully inefficient
|
|
||||||
||> List.fold (fun counters results ->
|
|
||||||
let counters =
|
|
||||||
(counters, results.Failed)
|
|
||||||
||> List.fold (fun counters (_, _) ->
|
|
||||||
// TODO: the counters can be more specific about the failure mode
|
|
||||||
counters.AddFailed ()
|
|
||||||
)
|
|
||||||
|
|
||||||
let counters =
|
|
||||||
(counters, results.OtherFailures)
|
|
||||||
||> List.fold (fun counters _ ->
|
|
||||||
// TODO: the counters can be more specific about the failure mode
|
|
||||||
counters.AddFailed ()
|
|
||||||
)
|
|
||||||
|
|
||||||
(counters, results.Success)
|
|
||||||
||> List.fold (fun counters (_, success, _) ->
|
|
||||||
match success with
|
|
||||||
| TestMemberSuccess.Ok -> counters.AddPassed ()
|
|
||||||
| TestMemberSuccess.Ignored _
|
|
||||||
| TestMemberSuccess.Explicit _ -> counters.AddNotExecuted ()
|
|
||||||
| TestMemberSuccess.Inconclusive _ -> counters.AddInconclusive ()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: I'm sure we can do better than this; there's a whole range of possible
|
|
||||||
// states!
|
|
||||||
let outcome =
|
|
||||||
if counters.Failed > 0u then
|
|
||||||
TrxOutcome.Failed
|
|
||||||
else
|
|
||||||
TrxOutcome.Completed
|
|
||||||
|
|
||||||
let resultSummary : TrxResultsSummary =
|
|
||||||
{
|
|
||||||
Outcome = outcome
|
|
||||||
Counters = counters
|
|
||||||
Output =
|
|
||||||
{
|
|
||||||
StdOut = None
|
|
||||||
StdErr = None
|
|
||||||
ErrorInfo = None
|
|
||||||
}
|
|
||||||
RunInfos =
|
|
||||||
[
|
|
||||||
// TODO: capture stdout
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
let times : TrxReportTimes =
|
|
||||||
{
|
|
||||||
Creation = creationTime
|
|
||||||
Queuing = startTime
|
|
||||||
Start = startTime
|
|
||||||
Finish = finishTime
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
let magicGuid = Guid.Parse "13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b"
|
|
||||||
|
|
||||||
let results =
|
|
||||||
results
|
|
||||||
|> List.collect (fun results -> results.IndividualTestRunMetadata)
|
|
||||||
|> List.map (fun (i, cause) ->
|
|
||||||
let exc =
|
|
||||||
match cause with
|
|
||||||
| Choice2Of3 _ -> None
|
|
||||||
| Choice1Of3 (TestMemberFailure.Malformed reasons) ->
|
|
||||||
{
|
|
||||||
StackTrace = None
|
|
||||||
Message = reasons |> String.concat "\n" |> Some
|
|
||||||
}
|
|
||||||
|> Some
|
|
||||||
| Choice1Of3 (TestMemberFailure.Failed fail)
|
|
||||||
| Choice1Of3 (TestMemberFailure.Failed fail)
|
|
||||||
| Choice1Of3 (TestMemberFailure.Failed fail) ->
|
|
||||||
((None, None), fail)
|
|
||||||
||> List.fold (fun (stackTrace, message) tf ->
|
|
||||||
match tf with
|
|
||||||
| TestFailure.TestFailed (UserMethodFailure.Threw (_, exc))
|
|
||||||
| TestFailure.SetUpFailed (UserMethodFailure.Threw (_, exc))
|
|
||||||
| TestFailure.TearDownFailed (UserMethodFailure.Threw (_, exc)) ->
|
|
||||||
let stackTrace =
|
|
||||||
match stackTrace with
|
|
||||||
| None -> (exc : Exception).ToString ()
|
|
||||||
| Some s -> s
|
|
||||||
|
|
||||||
(Some stackTrace, message)
|
|
||||||
| TestFailure.TestFailed (UserMethodFailure.ReturnedNonUnit (_, ret))
|
|
||||||
| TestFailure.SetUpFailed (UserMethodFailure.ReturnedNonUnit (_, ret))
|
|
||||||
| TestFailure.TearDownFailed (UserMethodFailure.ReturnedNonUnit (_, ret)) ->
|
|
||||||
let newMessage = $"returned non-unit value %O{ret}"
|
|
||||||
|
|
||||||
let message =
|
|
||||||
match message with
|
|
||||||
| None -> newMessage
|
|
||||||
| Some message -> $"%s{message}\n%s{newMessage}"
|
|
||||||
|
|
||||||
(stackTrace, Some message)
|
|
||||||
)
|
|
||||||
|> fun (stackTrace, message) ->
|
|
||||||
{
|
|
||||||
StackTrace = stackTrace
|
|
||||||
Message = message
|
|
||||||
}
|
|
||||||
|> Some
|
|
||||||
| Choice3Of3 (UserMethodFailure.Threw (_, exc)) ->
|
|
||||||
{
|
|
||||||
StackTrace = (exc : Exception).ToString () |> Some
|
|
||||||
Message = None
|
|
||||||
}
|
|
||||||
|> Some
|
|
||||||
| Choice3Of3 (UserMethodFailure.ReturnedNonUnit (_, ret)) ->
|
|
||||||
{
|
|
||||||
Message = $"returned non-unit value %O{ret}" |> Some
|
|
||||||
StackTrace = None
|
|
||||||
}
|
|
||||||
|> Some
|
|
||||||
|
|
||||||
let outcome =
|
|
||||||
match cause with
|
|
||||||
| Choice1Of3 _ -> TrxTestOutcome.Failed
|
|
||||||
| Choice2Of3 TestMemberSuccess.Ok -> TrxTestOutcome.Passed
|
|
||||||
| Choice2Of3 (TestMemberSuccess.Inconclusive _) -> TrxTestOutcome.Inconclusive
|
|
||||||
| Choice2Of3 (TestMemberSuccess.Ignored _)
|
|
||||||
| Choice2Of3 (TestMemberSuccess.Explicit _) -> TrxTestOutcome.NotExecuted
|
|
||||||
// TODO: we can totally do better here, more fine-grained classification
|
|
||||||
| Choice3Of3 _ -> TrxTestOutcome.Failed
|
|
||||||
|
|
||||||
{
|
|
||||||
ExecutionId = i.ExecutionId
|
|
||||||
TestId = i.TestId
|
|
||||||
TestName = i.TestName
|
|
||||||
ComputerName = i.ComputerName
|
|
||||||
Duration = i.End - i.Start
|
|
||||||
StartTime = i.Start
|
|
||||||
EndTime = i.End
|
|
||||||
TestType = magicGuid
|
|
||||||
Outcome = outcome
|
|
||||||
TestListId = testListId
|
|
||||||
RelativeResultsDirectory = i.ExecutionId.ToString () // for some reason
|
|
||||||
Output =
|
|
||||||
match i.StdOut, i.StdErr, exc with
|
|
||||||
| None, None, None -> None
|
|
||||||
| stdout, stderr, exc ->
|
|
||||||
Some
|
|
||||||
{
|
|
||||||
TrxOutput.StdOut = stdout
|
|
||||||
StdErr = stderr
|
|
||||||
ErrorInfo = exc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
let report : TrxReport =
|
|
||||||
{
|
|
||||||
Id = Guid.NewGuid ()
|
|
||||||
Name = $"@%s{hostname} %s{finishTimeHumanReadable}"
|
|
||||||
Times = times
|
|
||||||
Settings = settings
|
|
||||||
Results = results
|
|
||||||
TestDefinitions = testDefinitions
|
|
||||||
TestEntries = testEntries
|
|
||||||
TestLists = [ testList ]
|
|
||||||
ResultsSummary = resultSummary
|
|
||||||
}
|
|
||||||
|
|
||||||
match args.Trx with
|
match args.Trx with
|
||||||
| Some trxPath ->
|
| Some trxPath ->
|
||||||
@@ -442,10 +86,56 @@ module Program =
|
|||||||
Console.Error.WriteLine $"Written TRX file: %s{trxPath.FullName}"
|
Console.Error.WriteLine $"Written TRX file: %s{trxPath.FullName}"
|
||||||
| None -> ()
|
| None -> ()
|
||||||
|
|
||||||
match outcome with
|
match report.ResultsSummary.Outcome with
|
||||||
| TrxOutcome.Completed -> 0
|
| TrxOutcome.Completed -> 0
|
||||||
| _ -> 1
|
| _ -> 1
|
||||||
|
|
||||||
|
let main argv =
|
||||||
|
let args = argv |> List.ofArray |> Args.Parse
|
||||||
|
|
||||||
|
let psi = ProcessStartInfo "dotnet"
|
||||||
|
|
||||||
|
match args.Trx with
|
||||||
|
| None -> ()
|
||||||
|
| Some trx -> psi.EnvironmentVariables.Add ("WOOFWARE_NUNIT_GENERATE_TRX", trx.FullName)
|
||||||
|
|
||||||
|
match args.LevelOfParallelism with
|
||||||
|
| None -> ()
|
||||||
|
| Some par -> psi.EnvironmentVariables.Add ("WOOFWARE_NUNIT_PARALLELISM_LEVEL", string<int> par)
|
||||||
|
|
||||||
|
match args.Filter with
|
||||||
|
| None -> ()
|
||||||
|
| Some (filter, _) -> psi.EnvironmentVariables.Add ("WOOFWARE_NUNIT_FILTER", filter)
|
||||||
|
|
||||||
|
match args.Timeout with
|
||||||
|
| None -> ()
|
||||||
|
| Some timeout ->
|
||||||
|
psi.EnvironmentVariables.Add ("WOOFWARE_NUNIT_TIMEOUT_SECS", string<int> (int<float> timeout.TotalSeconds))
|
||||||
|
|
||||||
|
psi.ArgumentList.Add "exec"
|
||||||
|
psi.ArgumentList.Add args.Dll.FullName
|
||||||
|
|
||||||
|
let us = Assembly.GetExecutingAssembly().Location |> FileInfo
|
||||||
|
|
||||||
|
let startupHook =
|
||||||
|
Path.Combine (us.Directory.FullName, "WoofWare.NUnitTestRunner.StartupHook.dll")
|
||||||
|
|
||||||
|
psi.EnvironmentVariables.Add ("DOTNET_STARTUP_HOOKS", startupHook)
|
||||||
|
|
||||||
|
psi.EnvironmentVariables.Add (
|
||||||
|
"WOOFWARE_NUNIT_LIB",
|
||||||
|
Path.Combine (us.Directory.FullName, "WoofWare.NUnitTestRunner.Lib.dll")
|
||||||
|
)
|
||||||
|
|
||||||
|
use proc = new Process ()
|
||||||
|
proc.StartInfo <- psi
|
||||||
|
|
||||||
|
if not (proc.Start ()) then
|
||||||
|
failwith "Failed to start dotnet"
|
||||||
|
|
||||||
|
proc.WaitForExit ()
|
||||||
|
proc.ExitCode
|
||||||
|
|
||||||
[<EntryPoint>]
|
[<EntryPoint>]
|
||||||
let reallyMain argv =
|
let reallyMain argv =
|
||||||
// Hack to make sure `finally`s get run.
|
// Hack to make sure `finally`s get run.
|
||||||
|
@@ -1,22 +0,0 @@
|
|||||||
namespace WoofWare.NUnitTestRunner
|
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
|
||||||
module internal Seq =
|
|
||||||
|
|
||||||
let tryMinBy (f : 'a -> 'b) (s : 'a seq) : 'a option =
|
|
||||||
use enum = s.GetEnumerator ()
|
|
||||||
|
|
||||||
if not (enum.MoveNext ()) then
|
|
||||||
None
|
|
||||||
else
|
|
||||||
|
|
||||||
let mutable answer = enum.Current
|
|
||||||
let mutable fAnswer = f enum.Current
|
|
||||||
|
|
||||||
while enum.MoveNext () do
|
|
||||||
let fNext = f enum.Current
|
|
||||||
|
|
||||||
if fNext < fAnswer then
|
|
||||||
answer <- enum.Current
|
|
||||||
|
|
||||||
Some answer
|
|
@@ -16,7 +16,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ApiSurface" Version="4.0.41" />
|
<PackageReference Include="ApiSurface" Version="4.0.42" />
|
||||||
<PackageReference Include="FsCheck" Version="3.0.0-rc3" />
|
<PackageReference Include="FsCheck" Version="3.0.0-rc3" />
|
||||||
<PackageReference Include="FsUnit" Version="6.0.0" />
|
<PackageReference Include="FsUnit" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||||
|
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<RollForward>Major</RollForward>
|
||||||
<PackAsTool>true</PackAsTool>
|
<PackAsTool>true</PackAsTool>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<Authors>Patrick Stevens</Authors>
|
<Authors>Patrick Stevens</Authors>
|
||||||
@@ -19,7 +20,6 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Seq.fs" />
|
|
||||||
<Compile Include="Progress.fs" />
|
<Compile Include="Progress.fs" />
|
||||||
<Compile Include="Program.fs" />
|
<Compile Include="Program.fs" />
|
||||||
<None Include="..\README.md">
|
<None Include="..\README.md">
|
||||||
@@ -30,6 +30,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\WoofWare.NUnitTestRunner.Lib\WoofWare.NUnitTestRunner.Lib.fsproj" />
|
<ProjectReference Include="..\WoofWare.NUnitTestRunner.Lib\WoofWare.NUnitTestRunner.Lib.fsproj" />
|
||||||
|
<ProjectReference Include="..\WoofWare.NUnitTestRunner.StartupHookLogic\WoofWare.NUnitTestRunner.StartupHookLogic.csproj" />
|
||||||
|
<ProjectReference Include="..\WoofWare.NUnitTestRunner.StartupHook\WoofWare.NUnitTestRunner.StartupHook.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
21
flake.nix
21
flake.nix
@@ -56,13 +56,20 @@
|
|||||||
doCheck = true;
|
doCheck = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
devShell = pkgs.mkShell {
|
devShells = {
|
||||||
buildInputs = [dotnet-sdk];
|
default = pkgs.mkShell {
|
||||||
packages = [
|
packages = [
|
||||||
pkgs.alejandra
|
dotnet-sdk
|
||||||
pkgs.nodePackages.markdown-link-check
|
pkgs.alejandra
|
||||||
pkgs.shellcheck
|
pkgs.nodePackages.markdown-link-check
|
||||||
];
|
pkgs.shellcheck
|
||||||
|
];
|
||||||
|
};
|
||||||
|
net6 = pkgs.mkShell {
|
||||||
|
packages = [
|
||||||
|
pkgs.dotnetCorePackages.runtime_6_0
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
32
nix/deps.nix
32
nix/deps.nix
@@ -3,8 +3,8 @@
|
|||||||
{fetchNuGet}: [
|
{fetchNuGet}: [
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "ApiSurface";
|
pname = "ApiSurface";
|
||||||
version = "4.0.41";
|
version = "4.0.42";
|
||||||
sha256 = "03kfa5ngmgkik9lc58sp8s9rrh9g40hhgjnrv662ks0d0y2i9i89";
|
sha256 = "0azjv64bbbhc4rndbjhcmqxxg1bkf1v3ym3x34zmsbz0lr1hy6pv";
|
||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "fantomas";
|
pname = "fantomas";
|
||||||
@@ -153,23 +153,23 @@
|
|||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "NuGet.Common";
|
pname = "NuGet.Common";
|
||||||
version = "6.10.0";
|
version = "6.10.1";
|
||||||
sha256 = "0nizrnilmlcqbm945293h8q3wfqfchb4xi8g50x4kjn0rbpd1kbh";
|
sha256 = "1z69k0j727jcwrxzmvnixdac84lb9706iabqs8mrns8j7kbmw1ns";
|
||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "NuGet.Configuration";
|
pname = "NuGet.Configuration";
|
||||||
version = "6.10.0";
|
version = "6.10.1";
|
||||||
sha256 = "1aqaknaawnqx4mnvx9qw73wvj48jjzv0d78dzwl7m9zjlrl9myhz";
|
sha256 = "0qy2bdi3dz6fdw7qbv77fg956idm9d9733j8b1pcrcj9pfayys26";
|
||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "NuGet.Frameworks";
|
pname = "NuGet.Frameworks";
|
||||||
version = "6.10.0";
|
version = "6.10.1";
|
||||||
sha256 = "0hrd8y31zx9a0wps49czw0qgbrakb49zn3abfgylc9xrq990zkqk";
|
sha256 = "1p8d701fhbqv2r8vqmj948af9xvz2fd3273803cdrjy3a2wykmq1";
|
||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "NuGet.Packaging";
|
pname = "NuGet.Packaging";
|
||||||
version = "6.10.0";
|
version = "6.10.1";
|
||||||
sha256 = "18s53cvrf51lihmaqqdf48p2qi6ky1l48jv0hvbp76cxwdg7rba4";
|
sha256 = "0zl8xfzvd1yij2ln6iwy6cz8qfwlbyyqlin872ab5y58ws61a2x2";
|
||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "NuGet.Protocol";
|
pname = "NuGet.Protocol";
|
||||||
@@ -178,8 +178,8 @@
|
|||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "NuGet.Versioning";
|
pname = "NuGet.Versioning";
|
||||||
version = "6.10.0";
|
version = "6.10.1";
|
||||||
sha256 = "1x19njx4x0sw9fz8y5fibi15xfsrw5avir0cx0599yd7p3ykik5g";
|
sha256 = "0lji7g6abnpmhzlgvni8wlb7l62n4180v3sphp4494wi0gn7ds4c";
|
||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "NUnit";
|
pname = "NUnit";
|
||||||
@@ -253,8 +253,8 @@
|
|||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "WoofWare.Myriad.Plugins";
|
pname = "WoofWare.Myriad.Plugins";
|
||||||
version = "2.1.45";
|
version = "2.1.51";
|
||||||
sha256 = "1i9s9aq8dqnxyn01sa10dd24y9i7cgv2d0rshmrkvbvbjkcnz9vs";
|
sha256 = "1pdzdnjvq0a2gnbbz5gy9fzvxkyg6nl6x1k68xkm2a6gdkhkf68z";
|
||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "WoofWare.Myriad.Plugins.Attributes";
|
pname = "WoofWare.Myriad.Plugins.Attributes";
|
||||||
@@ -263,7 +263,7 @@
|
|||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "WoofWare.PrattParser";
|
pname = "WoofWare.PrattParser";
|
||||||
version = "0.2.1";
|
version = "0.2.2";
|
||||||
sha256 = "1cb9496fbbrdc40dirjmc7ax02ghr27ahqq5hpk96rdzyaang9hg";
|
sha256 = "0cgrmd1kc3k224lsjhy5npalwg6kpqd8nx78szi1yq67kyb0farq";
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
Reference in New Issue
Block a user