diff --git a/Consumer/TestCaseData.fs b/Consumer/TestCaseData.fs index 9dfaa8d..033b140 100644 --- a/Consumer/TestCaseData.fs +++ b/Consumer/TestCaseData.fs @@ -9,6 +9,27 @@ module TestCaseData = let dataSourceRaw = [ 3, "hi", [| 4.0 |] ; -10, "bye", null ] + let dataSource = dataSourceRaw |> List.map TestCaseData + + [] + let ``Consume test data`` (i : int, s : string, arr : float[]) = + lock testCasesSeen (fun () -> testCasesSeen.Add (i, s, arr)) + + let multipleSources = ResizeArray () + + let dataSource2Raw = [ 5, "egg", [| -1.3 ; 4.5 |] ; 100, "mycroft", [||] ] + let dataSource2 = dataSource2Raw |> List.map TestCaseData + + [] + [] + let ``Consume test data from multiple sources`` (i : int, s : string, arr : float[]) = + lock multipleSources (fun () -> multipleSources.Add (i, s, arr)) + + let optional = [ Some "hi" ; None ] |> List.map TestCaseData + + [] + let ``Consume options`` (s : string option) : unit = s |> shouldEqual s + [] let tearDown () = testCasesSeen @@ -16,13 +37,7 @@ module TestCaseData = |> List.sortBy (fun (a, _, _) -> a) |> shouldEqual (dataSourceRaw |> List.sortBy (fun (a, _, _) -> a)) - let dataSource = dataSourceRaw |> List.map TestCaseData - - [] - let ``Consume test data`` (i : int, s : string, arr : float[]) = - lock testCasesSeen (fun () -> testCasesSeen.Add (i, s, arr)) - - let optional = [ Some "hi" ; None ] |> List.map TestCaseData - - [] - let ``Consume options`` (s : string option) : unit = s |> shouldEqual s + multipleSources + |> Seq.toList + |> List.sortBy (fun (a, _, _) -> a) + |> shouldEqual ((dataSourceRaw @ dataSource2Raw) |> List.sortBy (fun (a, _, _) -> a)) diff --git a/TestRunner.Lib/Domain.fs b/TestRunner.Lib/Domain.fs index d44b69c..4c2be7d 100644 --- a/TestRunner.Lib/Domain.fs +++ b/TestRunner.Lib/Domain.fs @@ -10,7 +10,7 @@ type Modifier = [] type TestKind = | Single - | Source of string + | Source of string list | Data of obj list list type Combinatorial = diff --git a/TestRunner.Lib/SingleTestMethod.fs b/TestRunner.Lib/SingleTestMethod.fs index b0a3eb4..c126578 100644 --- a/TestRunner.Lib/SingleTestMethod.fs +++ b/TestRunner.Lib/SingleTestMethod.fs @@ -12,82 +12,77 @@ module SingleTestMethod = (attrs : CustomAttributeData list) : SingleTestMethod option * CustomAttributeData list = - let remaining, isTest, hasSource, hasData, modifiers, categories, repeat, comb = - (([], false, None, None, [], [], None, None), attrs) - ||> List.fold (fun (remaining, isTest, hasSource, hasData, mods, cats, repeat, comb) attr -> + 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 -> match attr.AttributeType.FullName with | "NUnit.Framework.TestAttribute" -> if attr.ConstructorArguments.Count > 0 then failwith "Unexpectedly got arguments to the Test attribute" - (remaining, true, hasSource, hasData, mods, cats, repeat, comb) + (remaining, true, sources, hasData, mods, cats, repeat, comb) | "NUnit.Framework.TestCaseAttribute" -> let args = attr.ConstructorArguments |> Seq.map _.Value |> Seq.toList match hasData with - | None -> (remaining, isTest, hasSource, Some [ List.ofSeq args ], mods, cats, repeat, comb) + | None -> (remaining, isTest, sources, Some [ List.ofSeq args ], mods, cats, repeat, comb) | Some existing -> - (remaining, isTest, hasSource, Some ((List.ofSeq args) :: existing), mods, cats, repeat, comb) + (remaining, isTest, sources, Some ((List.ofSeq args) :: existing), mods, cats, repeat, comb) | "NUnit.Framework.TestCaseSourceAttribute" -> let arg = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox - match hasSource with - | None -> (remaining, isTest, Some arg, hasData, mods, cats, repeat, comb) - | Some existing -> - failwith - $"Unexpectedly got multiple different sources for test %s{method.Name} (%s{existing}, %s{arg})" + (remaining, isTest, arg :: sources, hasData, mods, cats, repeat, comb) | "NUnit.Framework.ExplicitAttribute" -> let reason = attr.ConstructorArguments |> Seq.tryHead |> Option.map (_.Value >> unbox) - (remaining, isTest, hasSource, hasData, (Modifier.Explicit reason) :: mods, cats, repeat, comb) + (remaining, isTest, sources, hasData, (Modifier.Explicit reason) :: mods, cats, repeat, comb) | "NUnit.Framework.IgnoreAttribute" -> let reason = attr.ConstructorArguments |> Seq.tryHead |> Option.map (_.Value >> unbox) - (remaining, isTest, hasSource, hasData, (Modifier.Ignored reason) :: mods, cats, repeat, comb) + (remaining, isTest, sources, hasData, (Modifier.Ignored reason) :: mods, cats, repeat, comb) | "NUnit.Framework.CategoryAttribute" -> let category = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox - (remaining, isTest, hasSource, hasData, mods, category :: cats, repeat, comb) + (remaining, isTest, sources, hasData, mods, category :: cats, repeat, comb) | "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, hasSource, hasData, mods, cats, Some repeat, comb) + (remaining, isTest, sources, hasData, mods, cats, Some repeat, comb) | "NUnit.Framework.CombinatorialAttribute" -> match comb with | Some _ -> failwith $"Got CombinatorialAttribute or SequentialAttribute multiple times on %s{method.Name}" | None -> - (remaining, isTest, hasSource, hasData, mods, cats, repeat, Some Combinatorial.Combinatorial) + (remaining, isTest, sources, hasData, mods, cats, repeat, Some Combinatorial.Combinatorial) | "NUnit.Framework.SequentialAttribute" -> match comb with | Some _ -> failwith $"Got CombinatorialAttribute or SequentialAttribute multiple times on %s{method.Name}" - | None -> - (remaining, isTest, hasSource, hasData, mods, cats, repeat, Some Combinatorial.Sequential) + | None -> (remaining, isTest, sources, hasData, mods, cats, repeat, Some Combinatorial.Sequential) | s when s.StartsWith ("NUnit.Framework", StringComparison.Ordinal) -> failwith $"Unrecognised attribute on function %s{method.Name}: %s{attr.AttributeType.FullName}" - | _ -> (attr :: remaining, isTest, hasSource, hasData, mods, cats, repeat, comb) + | _ -> (attr :: remaining, isTest, sources, hasData, mods, cats, repeat, comb) ) let test = - match isTest, hasSource, hasData, modifiers, categories, repeat, comb with - | _, Some _, Some _, _, _, _, _ -> + match isTest, sources, hasData, modifiers, categories, repeat, comb with + | _, _ :: _, Some _, _, _, _, _ -> failwith $"Test %s{method.Name} unexpectedly has both TestData and TestCaseSource; not currently supported" - | false, None, None, [], _, _, _ -> None - | _, Some source, None, mods, categories, repeat, comb -> + | false, [], None, [], _, _, _ -> None + | _, _ :: _, None, mods, categories, repeat, comb -> { - Kind = TestKind.Source source + Kind = TestKind.Source sources Method = method Modifiers = mods Categories = categories @ parentCategories @@ -95,7 +90,7 @@ module SingleTestMethod = Combinatorial = comb } |> Some - | _, None, Some data, mods, categories, repeat, comb -> + | _, [], Some data, mods, categories, repeat, comb -> { Kind = TestKind.Data data Method = method @@ -105,7 +100,7 @@ module SingleTestMethod = Combinatorial = comb } |> Some - | true, None, None, mods, categories, repeat, comb -> + | true, [], None, mods, categories, repeat, comb -> { Kind = TestKind.Single Method = method @@ -115,7 +110,7 @@ module SingleTestMethod = Combinatorial = comb } |> Some - | false, None, 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/TestRunner.Lib/TestFixture.fs b/TestRunner.Lib/TestFixture.fs index 5fdf56a..a75b840 100644 --- a/TestRunner.Lib/TestFixture.fs +++ b/TestRunner.Lib/TestFixture.fs @@ -192,52 +192,53 @@ module TestFixture = |> TestMemberFailure.Malformed |> Error |> Seq.singleton - | TestKind.Source s, None -> - let args = - test.Method.DeclaringType.GetProperty ( - s, - BindingFlags.Public - ||| BindingFlags.NonPublic - ||| BindingFlags.Instance - ||| BindingFlags.Static - ) - + | TestKind.Source sources, None -> seq { - // Might not be an IEnumerable of a reference type. - // Concretely, `FSharpList :> IEnumerable` fails. - for arg in args.GetValue (null : obj) :?> System.Collections.IEnumerable do - yield - match arg with - | :? Tuple as (a, b) -> - runOne setUp tearDown test.Method containingObject [| a ; b |] - | :? Tuple as (a, b, c) -> - runOne setUp tearDown test.Method containingObject [| a ; b ; c |] - | :? Tuple as (a, b, c, d) -> - runOne setUp tearDown test.Method containingObject [| a ; b ; c ; d |] - | arg -> - let argTy = arg.GetType () + for source in sources do + let args = + test.Method.DeclaringType.GetProperty ( + source, + BindingFlags.Public + ||| BindingFlags.NonPublic + ||| BindingFlags.Instance + ||| BindingFlags.Static + ) - if argTy.FullName = "NUnit.Framework.TestCaseData" then - let argsMem = - argTy.GetMethod ( - "get_Arguments", - BindingFlags.Public - ||| BindingFlags.Instance - ||| BindingFlags.FlattenHierarchy - ) + // Might not be an IEnumerable of a reference type. + // Concretely, `FSharpList :> IEnumerable` fails. + for arg in args.GetValue (null : obj) :?> System.Collections.IEnumerable do + yield + match arg with + | :? Tuple as (a, b) -> + runOne setUp tearDown test.Method containingObject [| a ; b |] + | :? Tuple as (a, b, c) -> + runOne setUp tearDown test.Method containingObject [| a ; b ; c |] + | :? Tuple as (a, b, c, d) -> + runOne setUp tearDown test.Method containingObject [| a ; b ; c ; d |] + | arg -> + let argTy = arg.GetType () - if isNull argsMem then - failwith "Unexpectedly could not call `.Arguments` on TestCaseData" + if argTy.FullName = "NUnit.Framework.TestCaseData" then + let argsMem = + argTy.GetMethod ( + "get_Arguments", + BindingFlags.Public + ||| BindingFlags.Instance + ||| BindingFlags.FlattenHierarchy + ) - runOne - setUp - tearDown - test.Method - containingObject - (argsMem.Invoke (arg, [||]) |> unbox) - else - runOne setUp tearDown test.Method containingObject [| arg |] - |> normaliseError + if isNull argsMem then + failwith "Unexpectedly could not call `.Arguments` on TestCaseData" + + runOne + setUp + tearDown + test.Method + containingObject + (argsMem.Invoke (arg, [||]) |> unbox) + else + runOne setUp tearDown test.Method containingObject [| arg |] + |> normaliseError } ) |> Seq.concat