Multiple TestCaseSources (#26)

This commit is contained in:
Patrick Stevens
2024-06-05 17:27:17 +01:00
committed by GitHub
parent 298dca94ea
commit f6e907395c
4 changed files with 91 additions and 80 deletions

View File

@@ -9,6 +9,27 @@ module TestCaseData =
let dataSourceRaw = [ 3, "hi", [| 4.0 |] ; -10, "bye", null ]
let dataSource = dataSourceRaw |> List.map TestCaseData
[<TestCaseSource(nameof dataSource)>]
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
[<TestCaseSource(nameof dataSource)>]
[<TestCaseSource(nameof dataSource2)>]
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
[<TestCaseSource(nameof optional)>]
let ``Consume options`` (s : string option) : unit = s |> shouldEqual s
[<OneTimeTearDown>]
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
[<TestCaseSource(nameof dataSource)>]
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
[<TestCaseSource(nameof optional)>]
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))

View File

@@ -10,7 +10,7 @@ type Modifier =
[<RequireQualifiedAccess>]
type TestKind =
| Single
| Source of string
| Source of string list
| Data of obj list list
type Combinatorial =

View File

@@ -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<string>
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<string>)
(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<string>)
(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<string>
(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<int>
(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."

View File

@@ -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<HttpStatusCode> :> IEnumerable<obj>` fails.
for arg in args.GetValue (null : obj) :?> System.Collections.IEnumerable do
yield
match arg with
| :? Tuple<obj, obj> as (a, b) ->
runOne setUp tearDown test.Method containingObject [| a ; b |]
| :? Tuple<obj, obj, obj> as (a, b, c) ->
runOne setUp tearDown test.Method containingObject [| a ; b ; c |]
| :? Tuple<obj, obj, obj, obj> 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<HttpStatusCode> :> IEnumerable<obj>` fails.
for arg in args.GetValue (null : obj) :?> System.Collections.IEnumerable do
yield
match arg with
| :? Tuple<obj, obj> as (a, b) ->
runOne setUp tearDown test.Method containingObject [| a ; b |]
| :? Tuple<obj, obj, obj> as (a, b, c) ->
runOne setUp tearDown test.Method containingObject [| a ; b ; c |]
| :? Tuple<obj, obj, obj, obj> 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<obj[]>)
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<obj[]>)
else
runOne setUp tearDown test.Method containingObject [| arg |]
|> normaliseError
}
)
|> Seq.concat