mirror of
https://github.com/Smaug123/unofficial-nunit-runner
synced 2025-10-07 18:18:39 +00:00
Compare commits
1 Commits
WoofWare.N
...
WoofWare.N
Author | SHA1 | Date | |
---|---|---|---|
|
9f5f22c644 |
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
|
||||||
|
}
|
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,11 @@
|
|||||||
|
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 +20,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
|
||||||
|
@@ -36,6 +36,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>
|
||||||
@@ -51,7 +53,7 @@
|
|||||||
<PackageReference Include="Myriad.SDK" Version="0.8.3" />
|
<PackageReference Include="Myriad.SDK" Version="0.8.3" />
|
||||||
<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.14",
|
||||||
"publicReleaseRefSpec": [
|
"publicReleaseRefSpec": [
|
||||||
"^refs/heads/main$"
|
"^refs/heads/main$"
|
||||||
],
|
],
|
||||||
@@ -8,4 +8,4 @@
|
|||||||
":/Directory.Build.props",
|
":/Directory.Build.props",
|
||||||
":/README.md"
|
":/README.md"
|
||||||
]
|
]
|
||||||
}
|
}
|
@@ -132,47 +132,10 @@ module Program =
|
|||||||
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 +150,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 +169,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,7 +179,7 @@ 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
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user