diff --git a/Consumer/Consumer.fsproj b/Consumer/Consumer.fsproj index f482d4c..fbd572a 100644 --- a/Consumer/Consumer.fsproj +++ b/Consumer/Consumer.fsproj @@ -10,6 +10,7 @@ + diff --git a/Consumer/TestParallel.fs b/Consumer/TestParallel.fs new file mode 100644 index 0000000..48facdd --- /dev/null +++ b/Consumer/TestParallel.fs @@ -0,0 +1,49 @@ +namespace Consumer + +open NUnit.Framework +open FsUnitTyped + +[] +[] +module TestParallelDefault = + + let defaults = List.init 100 id + + [] + let ``Default thing`` (i : int) = i |> shouldEqual i + +[] +[] +module TestParallelAllScope = + + let defaults = List.init 100 id + + [] + let ``Default thing`` (i : int) = i |> shouldEqual i + +[] +[] +module TestParallelSelfScope = + + let defaults = List.init 100 id + + [] + let ``Default thing`` (i : int) = i |> shouldEqual i + +[] +[] +module TestParallelChildrenScope = + + let defaults = List.init 100 id + + [] + let ``Default thing`` (i : int) = i |> shouldEqual i + +[] +[] +module TestParallelFixturesScope = + + let defaults = List.init 100 id + + [] + let ``Default thing`` (i : int) = i |> shouldEqual i diff --git a/README.md b/README.md index 6da70d3..0e93a3c 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,9 @@ To supply special characters in a string, XML-encode them and `"quote"` the stri We support at least the [documented `dotnet test` examples](https://learn.microsoft.com/en-us/dotnet/core/testing/selective-unit-tests). However, we would recommend phrasing some of them differently, for maximum peace of mind: * `FullyQualifiedName=MyNamespace.MyTestsClass.MyTestMethod`. This would be better phrased with quotes and escaping as `FullyQualifiedName="MyNamespace.MyTestsClass<ParameterType1%2CParameterType2>.MyTestMethod"` + +## Parallelism + +WoofWare.NUnitTestRunner has *limited* support for parallelism. +By default, we run tests serially; we may or may not respect the NUnit parallelism attributes to any given extent (but we will never incorrectly run tests in parallel). +For example, as of this writing, we do not run any tests in parallel (but the internal infrastructure is set up so that we will be able to do this soon). diff --git a/WoofWare.NUnitTestRunner.Lib/Domain.fs b/WoofWare.NUnitTestRunner.Lib/Domain.fs index 612bca8..c4bcf6b 100644 --- a/WoofWare.NUnitTestRunner.Lib/Domain.fs +++ b/WoofWare.NUnitTestRunner.Lib/Domain.fs @@ -31,6 +31,36 @@ type Combinatorial = /// each", and so on. Spare slots are filled with `Unchecked.defaultof<_>`. | Sequential +/// Describes the level of parallelism permitted in some context. +[] +type ClassParallelScope = + /// "I may be run in parallel with other tests, although my children might not be able to run in parallel with each + /// other". + | Self + /// "The set of things I contain may be run in parallel with itself". + | Children + /// "Fixtures within me may be run in parallel with each other, but the tests within a given fixture might not + /// be runnable in parallel with each other". + | Fixtures + /// "All my descendents are happy to run in parallel with anything else, and also so am I". + | All + +/// Describes the level of parallelism permitted within an assembly. +[] +type AssemblyParallelScope = + /// "The set of things I contain may be run in parallel with itself". + | Children + /// "Fixtures within me may be run in parallel with each other, but the tests within a given fixture might not + /// necessarily be runnable in parallel with each other". + | Fixtures + +/// Describes whether a test can be run concurrently with other tests. +type Parallelizable<'scope> = + /// This test is happy, under some conditions (specified by the scope), to be run alongside other tests. + | Yes of 'scope + /// This test must always be run on its own. + | No + /// A single method or member which holds some tests. (Often such a member will represent only one test, but e.g. /// if it has [] then it represents multiple tests.) type SingleTestMethod = @@ -49,6 +79,8 @@ type SingleTestMethod = /// If this test has data supplied by `[]` annotations, specifies how those annotations are combined /// to produce the complete collection of args. Combinatorial : Combinatorial option + /// If this test has declared a parallelisability, that goes here. + Parallelize : Parallelizable option } /// Human-readable name of this test method. @@ -85,10 +117,12 @@ type TestFixture = Parameters : obj list list /// The individual test methods present within this fixture. Tests : SingleTestMethod list + /// If this fixture has declared a parallelisability, that goes here. + Parallelize : Parallelizable option } /// A test fixture about which we know nothing. No tests, no setup/teardown. - static member Empty (ty : Type) (args : obj list list) = + static member Empty (ty : Type) (par : Parallelizable option) (args : obj list list) = { ContainingAssembly = ty.Assembly Type = ty @@ -99,6 +133,7 @@ type TestFixture = TearDown = [] Parameters = args Tests = [] + Parallelize = par } /// User code in the unit under test has failed somehow. diff --git a/WoofWare.NUnitTestRunner.Lib/SingleTestMethod.fs b/WoofWare.NUnitTestRunner.Lib/SingleTestMethod.fs index d1e459b..1308053 100644 --- a/WoofWare.NUnitTestRunner.Lib/SingleTestMethod.fs +++ b/WoofWare.NUnitTestRunner.Lib/SingleTestMethod.fs @@ -18,15 +18,15 @@ module SingleTestMethod = (attrs : CustomAttributeData list) : SingleTestMethod option * CustomAttributeData list = - let remaining, isTest, sources, hasData, modifiers, categories, repeat, comb = - (([], false, [], None, [], [], None, None), attrs) - ||> List.fold (fun (remaining, isTest, sources, hasData, mods, cats, repeat, comb) attr -> + let remaining, isTest, sources, hasData, modifiers, categories, repeat, comb, par = + (([], false, [], None, [], [], None, None, None), attrs) + ||> List.fold (fun (remaining, isTest, sources, hasData, mods, cats, repeat, comb, par) attr -> match attr.AttributeType.FullName with | "NUnit.Framework.TestAttribute" -> if attr.ConstructorArguments.Count > 0 then failwith "Unexpectedly got arguments to the Test attribute" - (remaining, true, sources, hasData, mods, cats, repeat, comb) + (remaining, true, sources, hasData, mods, cats, repeat, comb, par) | "NUnit.Framework.TestCaseAttribute" -> let args = attr.ConstructorArguments |> Seq.map _.Value |> Seq.toList @@ -40,62 +40,73 @@ module SingleTestMethod = | _ -> args match hasData with - | None -> (remaining, isTest, sources, Some [ List.ofSeq args ], mods, cats, repeat, comb) + | None -> (remaining, isTest, sources, Some [ List.ofSeq args ], mods, cats, repeat, comb, par) | Some existing -> - (remaining, isTest, sources, Some ((List.ofSeq args) :: existing), mods, cats, repeat, comb) + let args = (List.ofSeq args) :: existing |> Some + (remaining, isTest, sources, args, mods, cats, repeat, comb, par) | "NUnit.Framework.TestCaseSourceAttribute" -> let arg = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox - (remaining, isTest, arg :: sources, hasData, mods, cats, repeat, comb) + (remaining, isTest, arg :: sources, hasData, mods, cats, repeat, comb, par) | "NUnit.Framework.ExplicitAttribute" -> let reason = attr.ConstructorArguments |> Seq.tryHead |> Option.map (_.Value >> unbox) - (remaining, isTest, sources, hasData, (Modifier.Explicit reason) :: mods, cats, repeat, comb) + (remaining, isTest, sources, hasData, (Modifier.Explicit reason) :: mods, cats, repeat, comb, par) | "NUnit.Framework.IgnoreAttribute" -> let reason = attr.ConstructorArguments |> Seq.tryHead |> Option.map (_.Value >> unbox) - (remaining, isTest, sources, hasData, (Modifier.Ignored reason) :: mods, cats, repeat, comb) + (remaining, isTest, sources, hasData, (Modifier.Ignored reason) :: mods, cats, repeat, comb, par) | "NUnit.Framework.CategoryAttribute" -> let category = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox - (remaining, isTest, sources, hasData, mods, category :: cats, repeat, comb) + (remaining, isTest, sources, hasData, mods, category :: cats, repeat, comb, par) | "NUnit.Framework.RepeatAttribute" -> match repeat with | Some _ -> failwith $"Got RepeatAttribute multiple times on %s{method.Name}" | None -> let repeat = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox - (remaining, isTest, sources, hasData, mods, cats, Some repeat, comb) + (remaining, isTest, sources, hasData, mods, cats, Some repeat, comb, par) | "NUnit.Framework.CombinatorialAttribute" -> match comb with | Some _ -> failwith $"Got CombinatorialAttribute or SequentialAttribute multiple times on %s{method.Name}" | None -> - (remaining, isTest, sources, hasData, mods, cats, repeat, Some Combinatorial.Combinatorial) + (remaining, isTest, sources, hasData, mods, cats, repeat, Some Combinatorial.Combinatorial, par) | "NUnit.Framework.SequentialAttribute" -> match comb with | Some _ -> failwith $"Got CombinatorialAttribute or SequentialAttribute multiple times on %s{method.Name}" - | None -> (remaining, isTest, sources, hasData, mods, cats, repeat, Some Combinatorial.Sequential) + | None -> + (remaining, isTest, sources, hasData, mods, cats, repeat, Some Combinatorial.Sequential, par) + | "NUnit.Framework.NonParallelizableAttribute" -> + match par with + | Some _ -> failwith $"Got a parallelization attribute multiple times on %s{method.Name}" + | None -> (remaining, isTest, sources, hasData, mods, cats, repeat, comb, Some Parallelizable.No) + | "NUnit.Framework.ParallelizableAttribute" -> + match par with + | Some _ -> failwith $"Got multiple parallelization attributes on %s{method.Name}" + | None -> + (remaining, isTest, sources, hasData, mods, cats, repeat, comb, Some (Parallelizable.Yes ())) | s when s.StartsWith ("NUnit.Framework", StringComparison.Ordinal) -> failwith $"Unrecognised attribute on function %s{method.Name}: %s{attr.AttributeType.FullName}" - | _ -> (attr :: remaining, isTest, sources, hasData, mods, cats, repeat, comb) + | _ -> (attr :: remaining, isTest, sources, hasData, mods, cats, repeat, comb, par) ) let test = - match isTest, sources, hasData, modifiers, categories, repeat, comb with - | _, _ :: _, Some _, _, _, _, _ -> + match isTest, sources, hasData, modifiers, categories, repeat, comb, par with + | _, _ :: _, Some _, _, _, _, _, _ -> failwith $"Test '%s{method.Name}' unexpectedly has both TestData and TestCaseSource; not currently supported" - | false, [], None, [], _, _, _ -> None - | _, _ :: _, None, mods, categories, repeat, comb -> + | false, [], None, [], _, _, _, _ -> None + | _, _ :: _, None, mods, categories, repeat, comb, par -> { Kind = TestKind.Source sources Method = method @@ -103,9 +114,10 @@ module SingleTestMethod = Categories = categories @ parentCategories Repeat = repeat Combinatorial = comb + Parallelize = par } |> Some - | _, [], Some data, mods, categories, repeat, comb -> + | _, [], Some data, mods, categories, repeat, comb, par -> { Kind = TestKind.Data data Method = method @@ -113,9 +125,10 @@ module SingleTestMethod = Categories = categories @ parentCategories Repeat = repeat Combinatorial = comb + Parallelize = par } |> Some - | true, [], None, mods, categories, repeat, comb -> + | true, [], None, mods, categories, repeat, comb, par -> { Kind = TestKind.Single Method = method @@ -123,9 +136,10 @@ module SingleTestMethod = Categories = categories @ parentCategories Repeat = repeat Combinatorial = comb + Parallelize = par } |> Some - | false, [], None, _ :: _, _, _, _ -> + | false, [], None, _ :: _, _, _, _, _ -> failwith $"Unexpectedly got test modifiers but no test settings on '%s{method.Name}', which you probably didn't intend." diff --git a/WoofWare.NUnitTestRunner.Lib/SurfaceBaseline.txt b/WoofWare.NUnitTestRunner.Lib/SurfaceBaseline.txt index 19db0cb..7389790 100644 --- a/WoofWare.NUnitTestRunner.Lib/SurfaceBaseline.txt +++ b/WoofWare.NUnitTestRunner.Lib/SurfaceBaseline.txt @@ -1,3 +1,41 @@ +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.Children [static field]: int = 0 +WoofWare.NUnitTestRunner.AssemblyParallelScope+Tags.Fixtures [static field]: int = 1 +WoofWare.NUnitTestRunner.AssemblyParallelScope.Children [static property]: [read-only] WoofWare.NUnitTestRunner.AssemblyParallelScope +WoofWare.NUnitTestRunner.AssemblyParallelScope.Fixtures [static property]: [read-only] WoofWare.NUnitTestRunner.AssemblyParallelScope +WoofWare.NUnitTestRunner.AssemblyParallelScope.get_Children [static method]: unit -> WoofWare.NUnitTestRunner.AssemblyParallelScope +WoofWare.NUnitTestRunner.AssemblyParallelScope.get_Fixtures [static method]: unit -> WoofWare.NUnitTestRunner.AssemblyParallelScope +WoofWare.NUnitTestRunner.AssemblyParallelScope.get_IsChildren [method]: unit -> bool +WoofWare.NUnitTestRunner.AssemblyParallelScope.get_IsFixtures [method]: unit -> bool +WoofWare.NUnitTestRunner.AssemblyParallelScope.get_Tag [method]: unit -> int +WoofWare.NUnitTestRunner.AssemblyParallelScope.IsChildren [property]: [read-only] bool +WoofWare.NUnitTestRunner.AssemblyParallelScope.IsFixtures [property]: [read-only] bool +WoofWare.NUnitTestRunner.AssemblyParallelScope.Tag [property]: [read-only] int +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.All [static field]: int = 3 +WoofWare.NUnitTestRunner.ClassParallelScope+Tags.Children [static field]: int = 1 +WoofWare.NUnitTestRunner.ClassParallelScope+Tags.Fixtures [static field]: int = 2 +WoofWare.NUnitTestRunner.ClassParallelScope+Tags.Self [static field]: int = 0 +WoofWare.NUnitTestRunner.ClassParallelScope.All [static property]: [read-only] WoofWare.NUnitTestRunner.ClassParallelScope +WoofWare.NUnitTestRunner.ClassParallelScope.Children [static property]: [read-only] WoofWare.NUnitTestRunner.ClassParallelScope +WoofWare.NUnitTestRunner.ClassParallelScope.Fixtures [static property]: [read-only] WoofWare.NUnitTestRunner.ClassParallelScope +WoofWare.NUnitTestRunner.ClassParallelScope.get_All [static method]: unit -> WoofWare.NUnitTestRunner.ClassParallelScope +WoofWare.NUnitTestRunner.ClassParallelScope.get_Children [static method]: unit -> WoofWare.NUnitTestRunner.ClassParallelScope +WoofWare.NUnitTestRunner.ClassParallelScope.get_Fixtures [static method]: unit -> WoofWare.NUnitTestRunner.ClassParallelScope +WoofWare.NUnitTestRunner.ClassParallelScope.get_IsAll [method]: unit -> bool +WoofWare.NUnitTestRunner.ClassParallelScope.get_IsChildren [method]: unit -> bool +WoofWare.NUnitTestRunner.ClassParallelScope.get_IsFixtures [method]: unit -> bool +WoofWare.NUnitTestRunner.ClassParallelScope.get_IsSelf [method]: unit -> bool +WoofWare.NUnitTestRunner.ClassParallelScope.get_Self [static method]: unit -> WoofWare.NUnitTestRunner.ClassParallelScope +WoofWare.NUnitTestRunner.ClassParallelScope.get_Tag [method]: unit -> int +WoofWare.NUnitTestRunner.ClassParallelScope.IsAll [property]: [read-only] bool +WoofWare.NUnitTestRunner.ClassParallelScope.IsChildren [property]: [read-only] bool +WoofWare.NUnitTestRunner.ClassParallelScope.IsFixtures [property]: [read-only] bool +WoofWare.NUnitTestRunner.ClassParallelScope.IsSelf [property]: [read-only] bool +WoofWare.NUnitTestRunner.ClassParallelScope.Self [static property]: [read-only] WoofWare.NUnitTestRunner.ClassParallelScope +WoofWare.NUnitTestRunner.ClassParallelScope.Tag [property]: [read-only] int WoofWare.NUnitTestRunner.Combinatorial inherit obj, implements WoofWare.NUnitTestRunner.Combinatorial System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.Combinatorial System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 2 cases WoofWare.NUnitTestRunner.Combinatorial+Tags inherit obj WoofWare.NUnitTestRunner.Combinatorial+Tags.Combinatorial [static field]: int = 0 @@ -141,8 +179,24 @@ WoofWare.NUnitTestRunner.Modifier.IsIgnored [property]: [read-only] bool WoofWare.NUnitTestRunner.Modifier.NewExplicit [static method]: string option -> WoofWare.NUnitTestRunner.Modifier WoofWare.NUnitTestRunner.Modifier.NewIgnored [static method]: string option -> WoofWare.NUnitTestRunner.Modifier WoofWare.NUnitTestRunner.Modifier.Tag [property]: [read-only] int +WoofWare.NUnitTestRunner.Parallelizable`1 inherit obj, implements 'scope WoofWare.NUnitTestRunner.Parallelizable System.IEquatable, System.Collections.IStructuralEquatable, 'scope WoofWare.NUnitTestRunner.Parallelizable System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 2 cases +WoofWare.NUnitTestRunner.Parallelizable`1+Tags inherit obj +WoofWare.NUnitTestRunner.Parallelizable`1+Tags.No [static field]: int = 1 +WoofWare.NUnitTestRunner.Parallelizable`1+Tags.Yes [static field]: int = 0 +WoofWare.NUnitTestRunner.Parallelizable`1+Yes inherit 'scope WoofWare.NUnitTestRunner.Parallelizable +WoofWare.NUnitTestRunner.Parallelizable`1+Yes.get_Item [method]: unit -> 'scope +WoofWare.NUnitTestRunner.Parallelizable`1+Yes.Item [property]: [read-only] 'scope +WoofWare.NUnitTestRunner.Parallelizable`1.get_IsNo [method]: unit -> bool +WoofWare.NUnitTestRunner.Parallelizable`1.get_IsYes [method]: unit -> bool +WoofWare.NUnitTestRunner.Parallelizable`1.get_No [static method]: unit -> 'scope WoofWare.NUnitTestRunner.Parallelizable +WoofWare.NUnitTestRunner.Parallelizable`1.get_Tag [method]: unit -> int +WoofWare.NUnitTestRunner.Parallelizable`1.IsNo [property]: [read-only] bool +WoofWare.NUnitTestRunner.Parallelizable`1.IsYes [property]: [read-only] bool +WoofWare.NUnitTestRunner.Parallelizable`1.NewYes [static method]: 'scope -> 'scope WoofWare.NUnitTestRunner.Parallelizable +WoofWare.NUnitTestRunner.Parallelizable`1.No [static property]: [read-only] 'scope WoofWare.NUnitTestRunner.Parallelizable +WoofWare.NUnitTestRunner.Parallelizable`1.Tag [property]: [read-only] int WoofWare.NUnitTestRunner.SingleTestMethod inherit obj, implements WoofWare.NUnitTestRunner.SingleTestMethod System.IEquatable, System.Collections.IStructuralEquatable -WoofWare.NUnitTestRunner.SingleTestMethod..ctor [constructor]: (System.Reflection.MethodInfo, WoofWare.NUnitTestRunner.TestKind, WoofWare.NUnitTestRunner.Modifier list, string list, int option, WoofWare.NUnitTestRunner.Combinatorial option) +WoofWare.NUnitTestRunner.SingleTestMethod..ctor [constructor]: (System.Reflection.MethodInfo, WoofWare.NUnitTestRunner.TestKind, WoofWare.NUnitTestRunner.Modifier list, string list, int option, WoofWare.NUnitTestRunner.Combinatorial option, unit WoofWare.NUnitTestRunner.Parallelizable option) WoofWare.NUnitTestRunner.SingleTestMethod.Categories [property]: [read-only] string list WoofWare.NUnitTestRunner.SingleTestMethod.Combinatorial [property]: [read-only] WoofWare.NUnitTestRunner.Combinatorial option WoofWare.NUnitTestRunner.SingleTestMethod.get_Categories [method]: unit -> string list @@ -151,11 +205,13 @@ WoofWare.NUnitTestRunner.SingleTestMethod.get_Kind [method]: unit -> WoofWare.NU WoofWare.NUnitTestRunner.SingleTestMethod.get_Method [method]: unit -> System.Reflection.MethodInfo WoofWare.NUnitTestRunner.SingleTestMethod.get_Modifiers [method]: unit -> WoofWare.NUnitTestRunner.Modifier list WoofWare.NUnitTestRunner.SingleTestMethod.get_Name [method]: unit -> string +WoofWare.NUnitTestRunner.SingleTestMethod.get_Parallelize [method]: unit -> unit WoofWare.NUnitTestRunner.Parallelizable option WoofWare.NUnitTestRunner.SingleTestMethod.get_Repeat [method]: unit -> int option WoofWare.NUnitTestRunner.SingleTestMethod.Kind [property]: [read-only] WoofWare.NUnitTestRunner.TestKind WoofWare.NUnitTestRunner.SingleTestMethod.Method [property]: [read-only] System.Reflection.MethodInfo WoofWare.NUnitTestRunner.SingleTestMethod.Modifiers [property]: [read-only] WoofWare.NUnitTestRunner.Modifier list WoofWare.NUnitTestRunner.SingleTestMethod.Name [property]: [read-only] string +WoofWare.NUnitTestRunner.SingleTestMethod.Parallelize [property]: [read-only] unit WoofWare.NUnitTestRunner.Parallelizable option WoofWare.NUnitTestRunner.SingleTestMethod.Repeat [property]: [read-only] int option 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) @@ -187,13 +243,14 @@ WoofWare.NUnitTestRunner.TestFailure.NewTearDownFailed [static method]: WoofWare WoofWare.NUnitTestRunner.TestFailure.NewTestFailed [static method]: WoofWare.NUnitTestRunner.UserMethodFailure -> WoofWare.NUnitTestRunner.TestFailure WoofWare.NUnitTestRunner.TestFailure.Tag [property]: [read-only] int WoofWare.NUnitTestRunner.TestFixture inherit obj, implements WoofWare.NUnitTestRunner.TestFixture System.IEquatable, System.Collections.IStructuralEquatable -WoofWare.NUnitTestRunner.TestFixture..ctor [constructor]: (System.Reflection.Assembly, string, System.Type, System.Reflection.MethodInfo option, System.Reflection.MethodInfo option, System.Reflection.MethodInfo list, System.Reflection.MethodInfo list, obj list list, WoofWare.NUnitTestRunner.SingleTestMethod list) +WoofWare.NUnitTestRunner.TestFixture..ctor [constructor]: (System.Reflection.Assembly, string, System.Type, System.Reflection.MethodInfo option, System.Reflection.MethodInfo option, System.Reflection.MethodInfo list, System.Reflection.MethodInfo list, obj list list, WoofWare.NUnitTestRunner.SingleTestMethod list, WoofWare.NUnitTestRunner.ClassParallelScope WoofWare.NUnitTestRunner.Parallelizable option) WoofWare.NUnitTestRunner.TestFixture.ContainingAssembly [property]: [read-only] System.Reflection.Assembly -WoofWare.NUnitTestRunner.TestFixture.Empty [static method]: System.Type -> obj list list -> WoofWare.NUnitTestRunner.TestFixture +WoofWare.NUnitTestRunner.TestFixture.Empty [static method]: System.Type -> WoofWare.NUnitTestRunner.ClassParallelScope WoofWare.NUnitTestRunner.Parallelizable option -> obj list list -> WoofWare.NUnitTestRunner.TestFixture WoofWare.NUnitTestRunner.TestFixture.get_ContainingAssembly [method]: unit -> System.Reflection.Assembly WoofWare.NUnitTestRunner.TestFixture.get_Name [method]: unit -> string WoofWare.NUnitTestRunner.TestFixture.get_OneTimeSetUp [method]: unit -> System.Reflection.MethodInfo option WoofWare.NUnitTestRunner.TestFixture.get_OneTimeTearDown [method]: unit -> System.Reflection.MethodInfo option +WoofWare.NUnitTestRunner.TestFixture.get_Parallelize [method]: unit -> WoofWare.NUnitTestRunner.ClassParallelScope WoofWare.NUnitTestRunner.Parallelizable option WoofWare.NUnitTestRunner.TestFixture.get_Parameters [method]: unit -> obj list list WoofWare.NUnitTestRunner.TestFixture.get_SetUp [method]: unit -> System.Reflection.MethodInfo list WoofWare.NUnitTestRunner.TestFixture.get_TearDown [method]: unit -> System.Reflection.MethodInfo list @@ -202,6 +259,7 @@ WoofWare.NUnitTestRunner.TestFixture.get_Type [method]: unit -> System.Type WoofWare.NUnitTestRunner.TestFixture.Name [property]: [read-only] string WoofWare.NUnitTestRunner.TestFixture.OneTimeSetUp [property]: [read-only] System.Reflection.MethodInfo option WoofWare.NUnitTestRunner.TestFixture.OneTimeTearDown [property]: [read-only] System.Reflection.MethodInfo option +WoofWare.NUnitTestRunner.TestFixture.Parallelize [property]: [read-only] WoofWare.NUnitTestRunner.ClassParallelScope WoofWare.NUnitTestRunner.Parallelizable option WoofWare.NUnitTestRunner.TestFixture.Parameters [property]: [read-only] obj list list WoofWare.NUnitTestRunner.TestFixture.SetUp [property]: [read-only] System.Reflection.MethodInfo list WoofWare.NUnitTestRunner.TestFixture.TearDown [property]: [read-only] System.Reflection.MethodInfo list diff --git a/WoofWare.NUnitTestRunner.Lib/TestFixture.fs b/WoofWare.NUnitTestRunner.Lib/TestFixture.fs index dd257b7..e508bd5 100644 --- a/WoofWare.NUnitTestRunner.Lib/TestFixture.fs +++ b/WoofWare.NUnitTestRunner.Lib/TestFixture.fs @@ -520,15 +520,15 @@ module TestFixture = /// Interpret this type as a [], extracting the test members from it and annotating them with all /// relevant information about how we should run them. let parse (parentType : Type) : TestFixture = - let categories, args = - (([], []), parentType.CustomAttributes) - ||> Seq.fold (fun (categories, args) attr -> + let categories, args, par = + (([], [], None), parentType.CustomAttributes) + ||> Seq.fold (fun (categories, args, par) attr -> match attr.AttributeType.FullName with | "NUnit.Framework.SetUpFixtureAttribute" -> failwith "This test runner does not support SetUpFixture. Please shout if you want this." | "NUnit.Framework.CategoryAttribute" -> let cat = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox - cat :: categories, args + cat :: categories, args, par | "NUnit.Framework.TestFixtureAttribute" -> let newArgs = match attr.ConstructorArguments |> Seq.map _.Value |> Seq.toList with @@ -536,11 +536,36 @@ module TestFixture = x |> Seq.cast |> Seq.map _.Value |> Seq.toList | xs -> xs - categories, newArgs :: args - | _ -> categories, args + categories, newArgs :: args, par + | "NUnit.Framework.NonParallelizableAttribute" -> + match par with + | Some _ -> failwith $"Got multiple parallelism attributes on %s{parentType.FullName}" + | None -> categories, args, Some Parallelizable.No + | "NUnit.Framework.ParallelizableAttribute" -> + match par with + | Some _ -> failwith $"Got multiple parallelism attributes on %s{parentType.FullName}" + | None -> + match attr.ConstructorArguments |> Seq.toList with + | [] -> categories, args, Some (Parallelizable.Yes ClassParallelScope.Self) + | [ v ] -> + match v.Value with + | :? int as v -> + match v with + | 512 -> categories, args, Some (Parallelizable.Yes ClassParallelScope.Fixtures) + | 256 -> categories, args, Some (Parallelizable.Yes ClassParallelScope.Children) + | 257 -> categories, args, Some (Parallelizable.Yes ClassParallelScope.All) + | 1 -> categories, args, Some (Parallelizable.Yes ClassParallelScope.Self) + | v -> + failwith + $"Could not recognise value %i{v} of parallel scope in %s{parentType.FullName}" + | v -> + failwith + $"Unexpectedly non-int value %O{v} of parallel scope in %s{parentType.FullName}" + | _ -> failwith $"unexpectedly got multiple args to Parallelizable on %s{parentType.FullName}" + | _ -> categories, args, par ) - (TestFixture.Empty parentType args, parentType.GetRuntimeMethods ()) + (TestFixture.Empty parentType par args, parentType.GetRuntimeMethods ()) ||> Seq.fold (fun state mi -> ((state, []), mi.CustomAttributes) ||> Seq.fold (fun (state, unrecognisedAttrs) attr -> diff --git a/WoofWare.NUnitTestRunner.Lib/version.json b/WoofWare.NUnitTestRunner.Lib/version.json index 16bed39..6979dc4 100644 --- a/WoofWare.NUnitTestRunner.Lib/version.json +++ b/WoofWare.NUnitTestRunner.Lib/version.json @@ -1,5 +1,5 @@ { - "version": "0.11", + "version": "0.12", "publicReleaseRefSpec": [ "^refs/heads/main$" ], diff --git a/WoofWare.NUnitTestRunner/Program.fs b/WoofWare.NUnitTestRunner/Program.fs index 99a8c15..934f5a5 100644 --- a/WoofWare.NUnitTestRunner/Program.fs +++ b/WoofWare.NUnitTestRunner/Program.fs @@ -68,6 +68,45 @@ module Program = let ctx = Ctx (testDll, DotnetRuntime.locate testDll) let assy = ctx.LoadFromAssemblyPath testDll.FullName + let levelOfParallelism, par = + ((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 + + 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 testFixtures = assy.ExportedTypes |> Seq.map TestFixture.parse |> Seq.toList let creationTime = DateTimeOffset.Now