mirror of
https://github.com/Smaug123/unofficial-nunit-runner
synced 2025-10-07 10:18:38 +00:00
Properly deal with attributes (#14)
This commit is contained in:
@@ -34,28 +34,33 @@ type SingleTestMethod =
|
|||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module SingleTestMethod =
|
module SingleTestMethod =
|
||||||
let parse (parentCategories : string list) (method : MethodInfo) : SingleTestMethod option =
|
let parse
|
||||||
let isTest, hasSource, hasData, modifiers, categories, repeat, comb =
|
(parentCategories : string list)
|
||||||
((false, None, None, [], [], None, None), method.CustomAttributes)
|
(method : MethodInfo)
|
||||||
||> Seq.fold (fun (isTest, hasSource, hasData, mods, cats, repeat, comb) attr ->
|
(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 ->
|
||||||
match attr.AttributeType.FullName with
|
match attr.AttributeType.FullName with
|
||||||
| "NUnit.Framework.TestAttribute" ->
|
| "NUnit.Framework.TestAttribute" ->
|
||||||
if attr.ConstructorArguments.Count > 0 then
|
if attr.ConstructorArguments.Count > 0 then
|
||||||
failwith "Unexpectedly got arguments to the Test attribute"
|
failwith "Unexpectedly got arguments to the Test attribute"
|
||||||
|
|
||||||
(true, hasSource, hasData, mods, cats, repeat, comb)
|
(remaining, true, hasSource, hasData, mods, cats, repeat, comb)
|
||||||
| "NUnit.Framework.TestCaseAttribute" ->
|
| "NUnit.Framework.TestCaseAttribute" ->
|
||||||
let args = attr.ConstructorArguments |> Seq.map _.Value |> Seq.toList
|
let args = attr.ConstructorArguments |> Seq.map _.Value |> Seq.toList
|
||||||
|
|
||||||
match hasData with
|
match hasData with
|
||||||
| None -> (isTest, hasSource, Some [ List.ofSeq args ], mods, cats, repeat, comb)
|
| None -> (remaining, isTest, hasSource, Some [ List.ofSeq args ], mods, cats, repeat, comb)
|
||||||
| Some existing ->
|
| Some existing ->
|
||||||
(isTest, hasSource, Some ((List.ofSeq args) :: existing), mods, cats, repeat, comb)
|
(remaining, isTest, hasSource, Some ((List.ofSeq args) :: existing), mods, cats, repeat, comb)
|
||||||
| "NUnit.Framework.TestCaseSourceAttribute" ->
|
| "NUnit.Framework.TestCaseSourceAttribute" ->
|
||||||
let arg = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<string>
|
let arg = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<string>
|
||||||
|
|
||||||
match hasSource with
|
match hasSource with
|
||||||
| None -> (isTest, Some arg, hasData, mods, cats, repeat, comb)
|
| None -> (remaining, isTest, Some arg, hasData, mods, cats, repeat, comb)
|
||||||
| Some existing ->
|
| Some existing ->
|
||||||
failwith
|
failwith
|
||||||
$"Unexpectedly got multiple different sources for test %s{method.Name} (%s{existing}, %s{arg})"
|
$"Unexpectedly got multiple different sources for test %s{method.Name} (%s{existing}, %s{arg})"
|
||||||
@@ -65,78 +70,84 @@ module SingleTestMethod =
|
|||||||
|> Seq.tryHead
|
|> Seq.tryHead
|
||||||
|> Option.map (_.Value >> unbox<string>)
|
|> Option.map (_.Value >> unbox<string>)
|
||||||
|
|
||||||
(isTest, hasSource, hasData, (Modifier.Explicit reason) :: mods, cats, repeat, comb)
|
(remaining, isTest, hasSource, hasData, (Modifier.Explicit reason) :: mods, cats, repeat, comb)
|
||||||
| "NUnit.Framework.IgnoreAttribute" ->
|
| "NUnit.Framework.IgnoreAttribute" ->
|
||||||
let reason =
|
let reason =
|
||||||
attr.ConstructorArguments
|
attr.ConstructorArguments
|
||||||
|> Seq.tryHead
|
|> Seq.tryHead
|
||||||
|> Option.map (_.Value >> unbox<string>)
|
|> Option.map (_.Value >> unbox<string>)
|
||||||
|
|
||||||
(isTest, hasSource, hasData, (Modifier.Ignored reason) :: mods, cats, repeat, comb)
|
(remaining, isTest, hasSource, hasData, (Modifier.Ignored reason) :: mods, cats, repeat, comb)
|
||||||
| "NUnit.Framework.CategoryAttribute" ->
|
| "NUnit.Framework.CategoryAttribute" ->
|
||||||
let category =
|
let category =
|
||||||
attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<string>
|
attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<string>
|
||||||
|
|
||||||
(isTest, hasSource, hasData, mods, category :: cats, repeat, comb)
|
(remaining, isTest, hasSource, hasData, mods, category :: cats, repeat, comb)
|
||||||
| "NUnit.Framework.RepeatAttribute" ->
|
| "NUnit.Framework.RepeatAttribute" ->
|
||||||
match repeat with
|
match repeat with
|
||||||
| Some _ -> failwith $"Got RepeatAttribute multiple times on %s{method.Name}"
|
| Some _ -> failwith $"Got RepeatAttribute multiple times on %s{method.Name}"
|
||||||
| None ->
|
| None ->
|
||||||
|
|
||||||
let repeat = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<int>
|
let repeat = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<int>
|
||||||
(isTest, hasSource, hasData, mods, cats, Some repeat, comb)
|
(remaining, isTest, hasSource, hasData, mods, cats, Some repeat, comb)
|
||||||
| "NUnit.Framework.CombinatorialAttribute" ->
|
| "NUnit.Framework.CombinatorialAttribute" ->
|
||||||
match comb with
|
match comb with
|
||||||
| Some _ ->
|
| Some _ ->
|
||||||
failwith $"Got CombinatorialAttribute or SequentialAttribute multiple times on %s{method.Name}"
|
failwith $"Got CombinatorialAttribute or SequentialAttribute multiple times on %s{method.Name}"
|
||||||
| None -> (isTest, hasSource, hasData, mods, cats, repeat, Some Combinatorial.Combinatorial)
|
| None ->
|
||||||
|
(remaining, isTest, hasSource, hasData, mods, cats, repeat, Some Combinatorial.Combinatorial)
|
||||||
| "NUnit.Framework.SequentialAttribute" ->
|
| "NUnit.Framework.SequentialAttribute" ->
|
||||||
match comb with
|
match comb with
|
||||||
| Some _ ->
|
| Some _ ->
|
||||||
failwith $"Got CombinatorialAttribute or SequentialAttribute multiple times on %s{method.Name}"
|
failwith $"Got CombinatorialAttribute or SequentialAttribute multiple times on %s{method.Name}"
|
||||||
| None -> (isTest, hasSource, hasData, mods, cats, repeat, Some Combinatorial.Sequential)
|
| None ->
|
||||||
|
(remaining, isTest, hasSource, hasData, mods, cats, repeat, Some Combinatorial.Sequential)
|
||||||
| s when s.StartsWith ("NUnit.Framework", StringComparison.Ordinal) ->
|
| s when s.StartsWith ("NUnit.Framework", StringComparison.Ordinal) ->
|
||||||
failwith $"Unrecognised attribute on function %s{method.Name}: %s{attr.AttributeType.FullName}"
|
failwith $"Unrecognised attribute on function %s{method.Name}: %s{attr.AttributeType.FullName}"
|
||||||
| _ -> (isTest, hasSource, hasData, mods, cats, repeat, comb)
|
| _ -> (attr :: remaining, isTest, hasSource, hasData, mods, cats, repeat, comb)
|
||||||
)
|
)
|
||||||
|
|
||||||
match isTest, hasSource, hasData, modifiers, categories, repeat, comb with
|
let test =
|
||||||
| _, Some _, Some _, _, _, _, _ ->
|
match isTest, hasSource, hasData, modifiers, categories, repeat, comb with
|
||||||
failwith $"Test %s{method.Name} unexpectedly has both TestData and TestCaseSource; not currently supported"
|
| _, Some _, Some _, _, _, _, _ ->
|
||||||
| false, None, None, [], _, _, _ -> None
|
failwith
|
||||||
| _, Some source, None, mods, categories, repeat, comb ->
|
$"Test %s{method.Name} unexpectedly has both TestData and TestCaseSource; not currently supported"
|
||||||
{
|
| false, None, None, [], _, _, _ -> None
|
||||||
Kind = TestKind.Source source
|
| _, Some source, None, mods, categories, repeat, comb ->
|
||||||
Method = method
|
{
|
||||||
Modifiers = mods
|
Kind = TestKind.Source source
|
||||||
Categories = categories @ parentCategories
|
Method = method
|
||||||
Repeat = repeat
|
Modifiers = mods
|
||||||
Combinatorial = comb
|
Categories = categories @ parentCategories
|
||||||
}
|
Repeat = repeat
|
||||||
|> Some
|
Combinatorial = comb
|
||||||
| _, None, Some data, mods, categories, repeat, comb ->
|
}
|
||||||
{
|
|> Some
|
||||||
Kind = TestKind.Data data
|
| _, None, Some data, mods, categories, repeat, comb ->
|
||||||
Method = method
|
{
|
||||||
Modifiers = mods
|
Kind = TestKind.Data data
|
||||||
Categories = categories @ parentCategories
|
Method = method
|
||||||
Repeat = repeat
|
Modifiers = mods
|
||||||
Combinatorial = comb
|
Categories = categories @ parentCategories
|
||||||
}
|
Repeat = repeat
|
||||||
|> Some
|
Combinatorial = comb
|
||||||
| true, None, None, mods, categories, repeat, comb ->
|
}
|
||||||
{
|
|> Some
|
||||||
Kind = TestKind.Single
|
| true, None, None, mods, categories, repeat, comb ->
|
||||||
Method = method
|
{
|
||||||
Modifiers = mods
|
Kind = TestKind.Single
|
||||||
Categories = categories @ parentCategories
|
Method = method
|
||||||
Repeat = repeat
|
Modifiers = mods
|
||||||
Combinatorial = comb
|
Categories = categories @ parentCategories
|
||||||
}
|
Repeat = repeat
|
||||||
|> Some
|
Combinatorial = comb
|
||||||
| false, None, None, _ :: _, _, _, _ ->
|
}
|
||||||
failwith
|
|> Some
|
||||||
$"Unexpectedly got test modifiers but no test settings on '%s{method.Name}', which you probably didn't intend."
|
| false, None, None, _ :: _, _, _, _ ->
|
||||||
|
failwith
|
||||||
|
$"Unexpectedly got test modifiers but no test settings on '%s{method.Name}', which you probably didn't intend."
|
||||||
|
|
||||||
|
test, remaining
|
||||||
|
|
||||||
type TestFixture =
|
type TestFixture =
|
||||||
{
|
{
|
||||||
@@ -391,47 +402,73 @@ module TestFixture =
|
|||||||
|
|
||||||
(TestFixture.Empty parentType.Name, parentType.GetRuntimeMethods ())
|
(TestFixture.Empty parentType.Name, parentType.GetRuntimeMethods ())
|
||||||
||> Seq.fold (fun state mi ->
|
||> Seq.fold (fun state mi ->
|
||||||
if
|
((state, []), mi.CustomAttributes)
|
||||||
mi.CustomAttributes
|
||> Seq.fold (fun (state, unrecognisedAttrs) attr ->
|
||||||
|> Seq.exists (fun attr -> attr.AttributeType.FullName = "NUnit.Framework.OneTimeSetUpAttribute")
|
match attr.AttributeType.FullName with
|
||||||
then
|
| "NUnit.Framework.OneTimeSetUpAttribute" ->
|
||||||
match state.OneTimeSetUp with
|
match state.OneTimeSetUp with
|
||||||
| None ->
|
| Some _existing -> failwith "Multiple OneTimeSetUp methods found"
|
||||||
|
| None ->
|
||||||
|
{ state with
|
||||||
|
OneTimeSetUp = Some mi
|
||||||
|
},
|
||||||
|
unrecognisedAttrs
|
||||||
|
| "NUnit.Framework.OneTimeTearDownAttribute" ->
|
||||||
|
match state.OneTimeTearDown with
|
||||||
|
| Some _existing -> failwith "Multiple OneTimeTearDown methods found"
|
||||||
|
| None ->
|
||||||
|
{ state with
|
||||||
|
OneTimeTearDown = Some mi
|
||||||
|
},
|
||||||
|
unrecognisedAttrs
|
||||||
|
| "NUnit.Framework.TearDownAttribute" ->
|
||||||
{ state with
|
{ state with
|
||||||
OneTimeSetUp = Some mi
|
TearDown = mi :: state.TearDown
|
||||||
}
|
},
|
||||||
| Some _existing -> failwith "Multiple OneTimeSetUp methods found"
|
unrecognisedAttrs
|
||||||
elif
|
| "NUnit.Framework.SetUpAttribute" ->
|
||||||
mi.CustomAttributes
|
|
||||||
|> Seq.exists (fun attr -> attr.AttributeType.FullName = "NUnit.Framework.OneTimeTearDownAttribute")
|
|
||||||
then
|
|
||||||
match state.OneTimeTearDown with
|
|
||||||
| None ->
|
|
||||||
{ state with
|
{ state with
|
||||||
OneTimeTearDown = Some mi
|
SetUp = mi :: state.SetUp
|
||||||
}
|
},
|
||||||
| Some _existing -> failwith "Multiple OneTimeTearDown methods found"
|
unrecognisedAttrs
|
||||||
elif
|
| "NUnit.Framework.TestFixtureSetUpAttribute" ->
|
||||||
mi.CustomAttributes
|
failwith "TestFixtureSetUp is not supported (upstream has deprecated it; use OneTimeSetUp)"
|
||||||
|> Seq.exists (fun attr -> attr.AttributeType.FullName = "NUnit.Framework.TearDownAttribute")
|
| "NUnit.Framework.TestFixtureTearDownAttribute" ->
|
||||||
then
|
failwith "TestFixtureTearDown is not supported (upstream has deprecated it; use OneTimeTearDown)"
|
||||||
{ state with
|
| "NUnit.Framework.RetryAttribute" ->
|
||||||
TearDown = mi :: state.TearDown
|
failwith "RetryAttribute is not supported. Don't write flaky tests."
|
||||||
}
|
| "NUnit.Framework.RandomAttribute" ->
|
||||||
elif
|
failwith "RandomAttribute is not supported. Use a property-based testing framework like FsCheck."
|
||||||
mi.CustomAttributes
|
| "NUnit.Framework.AuthorAttribute"
|
||||||
|> Seq.exists (fun attr -> attr.AttributeType.FullName = "NUnit.Framework.SetUpAttribute")
|
| "NUnit.Framework.CultureAttribute"
|
||||||
then
|
| "NUnit.Framework.DescriptionAttribute" ->
|
||||||
{ state with
|
// ignoring for now: metadata only
|
||||||
SetUp = mi :: state.SetUp
|
state, unrecognisedAttrs
|
||||||
}
|
| _ -> state, attr :: unrecognisedAttrs
|
||||||
else
|
)
|
||||||
match SingleTestMethod.parse categories mi with
|
|> fun (state, unrecognised) ->
|
||||||
| Some test ->
|
let state, unrecognised =
|
||||||
{ state with
|
match SingleTestMethod.parse categories mi unrecognised with
|
||||||
Tests = test :: state.Tests
|
| Some test, unrecognised ->
|
||||||
}
|
{ state with
|
||||||
| None -> state
|
Tests = test :: state.Tests
|
||||||
|
},
|
||||||
|
unrecognised
|
||||||
|
| None, unrecognised -> state, unrecognised
|
||||||
|
|
||||||
|
unrecognised
|
||||||
|
|> List.filter (fun attr ->
|
||||||
|
attr.AttributeType.FullName.StartsWith ("NUnit.Framework.", StringComparison.Ordinal)
|
||||||
|
)
|
||||||
|
|> function
|
||||||
|
| [] -> ()
|
||||||
|
| unrecognised ->
|
||||||
|
unrecognised
|
||||||
|
|> Seq.map (fun x -> x.AttributeType.FullName)
|
||||||
|
|> String.concat ", "
|
||||||
|
|> failwithf "Unrecognised attributes: %s"
|
||||||
|
|
||||||
|
state
|
||||||
)
|
)
|
||||||
|
|
||||||
module Program =
|
module Program =
|
||||||
|
Reference in New Issue
Block a user