Compare commits

...

4 Commits

Author SHA1 Message Date
Patrick Stevens
df64e46079 Cope with parameterised fixtures (#70) 2024-06-11 23:28:13 +01:00
Patrick Stevens
b3bc0aa4c0 Fix param count error with TestCase (#68) 2024-06-11 22:41:43 +01:00
Patrick Stevens
d3f9ee6b02 Throw on SetUpFixture (#63) 2024-06-10 23:45:03 +01:00
Patrick Stevens
3e6fff27d6 Stop throwing on unrecognised exceptions (#62) 2024-06-10 23:28:49 +01:00
9 changed files with 127 additions and 43 deletions

View File

@@ -10,6 +10,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="NoAttribute.fs" /> <Compile Include="NoAttribute.fs" />
<Compile Include="Inconclusive.fs" /> <Compile Include="Inconclusive.fs" />
<Compile Include="TestParameterisedFixture.fs" />
<Compile Include="TestSetUp.fs" /> <Compile Include="TestSetUp.fs" />
<Compile Include="TestValues.fs" /> <Compile Include="TestValues.fs" />
<None Include="some-config.json"> <None Include="some-config.json">

View File

@@ -35,6 +35,13 @@ module TestCaseData =
[<TestCaseSource(nameof optionalRaw)>] [<TestCaseSource(nameof optionalRaw)>]
let ``Consume options, raw`` (s : string option) : unit = s |> shouldEqual s let ``Consume options, raw`` (s : string option) : unit = s |> shouldEqual s
[<TestCase(30, 15, 44, false)>]
let bug66 (i : int, j : int, k : int, l : bool) =
i |> shouldEqual 30
j |> shouldEqual 15
k |> shouldEqual 44
l |> shouldEqual false
[<OneTimeTearDown>] [<OneTimeTearDown>]
let tearDown () = let tearDown () =
testCasesSeen testCasesSeen

View File

@@ -0,0 +1,18 @@
namespace Consumer
open NUnit.Framework
open FsUnitTyped
[<TestFixture true>]
[<TestFixture false>]
type TestParameterisedFixture (v : bool) =
[<Test>]
member _.Thing () = v |> shouldEqual v
[<TestFixture(3, true)>]
[<TestFixture(6, false)>]
type TestParameterisedFixtureMultiple (i : int, v : bool) =
[<Test>]
member _.Thing () =
v |> shouldEqual v
i |> shouldEqual i

View File

@@ -1,5 +1,6 @@
namespace WoofWare.NUnitTestRunner namespace WoofWare.NUnitTestRunner
open System
open System.Reflection open System.Reflection
/// A modifier on whether a given test should be run. /// A modifier on whether a given test should be run.
@@ -62,6 +63,8 @@ type TestFixture =
/// Fully-qualified name of this fixture (e.g. MyThing.Test.Foo for `[<TestFixture>] module Foo` in the /// Fully-qualified name of this fixture (e.g. MyThing.Test.Foo for `[<TestFixture>] module Foo` in the
/// `MyThing.Test` assembly). /// `MyThing.Test` assembly).
Name : string Name : string
/// The type which is this fixture, containing the tests as members.
Type : Type
/// A method which is run once when this test fixture starts, before any other setup logic and before /// A method which is run once when this test fixture starts, before any other setup logic and before
/// any tests run. If this method fails, no tests will run and no per-test setup/teardown logic will run, /// any tests run. If this method fails, no tests will run and no per-test setup/teardown logic will run,
/// but OneTimeTearDown will run. /// but OneTimeTearDown will run.
@@ -77,19 +80,24 @@ type TestFixture =
/// Methods which are run in some arbitrary order after each individual test, even if the test or its setup /// Methods which are run in some arbitrary order after each individual test, even if the test or its setup
/// failed. If the first TearDown we run fails, we don't define whether the other TearDowns run. /// failed. If the first TearDown we run fails, we don't define whether the other TearDowns run.
TearDown : MethodInfo list TearDown : MethodInfo list
/// You might have defined e.g. `[<TestFixture true>] type Foo (v : bool) = ...`. If so, this gives the
/// various possible parameters.
Parameters : obj list list
/// The individual test methods present within this fixture. /// The individual test methods present within this fixture.
Tests : SingleTestMethod list Tests : SingleTestMethod list
} }
/// A test fixture about which we know nothing. No tests, no setup/teardown. /// A test fixture about which we know nothing. No tests, no setup/teardown.
static member Empty (containingAssembly : Assembly) (name : string) = static member Empty (ty : Type) (args : obj list list) =
{ {
ContainingAssembly = containingAssembly ContainingAssembly = ty.Assembly
Name = name Type = ty
Name = ty.Name
OneTimeSetUp = None OneTimeSetUp = None
OneTimeTearDown = None OneTimeTearDown = None
SetUp = [] SetUp = []
TearDown = [] TearDown = []
Parameters = args
Tests = [] Tests = []
} }

View File

@@ -30,6 +30,15 @@ module SingleTestMethod =
| "NUnit.Framework.TestCaseAttribute" -> | "NUnit.Framework.TestCaseAttribute" ->
let args = attr.ConstructorArguments |> Seq.map _.Value |> Seq.toList let args = attr.ConstructorArguments |> Seq.map _.Value |> Seq.toList
let args =
match args with
| [ :? System.Collections.ICollection as x ] ->
x
|> Seq.cast<CustomAttributeTypedArgument>
|> Seq.map (fun v -> v.Value)
|> Seq.toList
| _ -> args
match hasData with 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)
| Some existing -> | Some existing ->

View File

@@ -185,25 +185,29 @@ WoofWare.NUnitTestRunner.TestFailure.NewTearDownFailed [static method]: WoofWare
WoofWare.NUnitTestRunner.TestFailure.NewTestFailed [static method]: WoofWare.NUnitTestRunner.UserMethodFailure -> WoofWare.NUnitTestRunner.TestFailure WoofWare.NUnitTestRunner.TestFailure.NewTestFailed [static method]: WoofWare.NUnitTestRunner.UserMethodFailure -> WoofWare.NUnitTestRunner.TestFailure
WoofWare.NUnitTestRunner.TestFailure.Tag [property]: [read-only] int 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 inherit obj, implements WoofWare.NUnitTestRunner.TestFixture System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.NUnitTestRunner.TestFixture..ctor [constructor]: (System.Reflection.Assembly, string, System.Reflection.MethodInfo option, System.Reflection.MethodInfo option, System.Reflection.MethodInfo list, System.Reflection.MethodInfo 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.TestFixture.ContainingAssembly [property]: [read-only] System.Reflection.Assembly WoofWare.NUnitTestRunner.TestFixture.ContainingAssembly [property]: [read-only] System.Reflection.Assembly
WoofWare.NUnitTestRunner.TestFixture.Empty [static method]: System.Reflection.Assembly -> string -> WoofWare.NUnitTestRunner.TestFixture WoofWare.NUnitTestRunner.TestFixture.Empty [static method]: System.Type -> obj list list -> WoofWare.NUnitTestRunner.TestFixture
WoofWare.NUnitTestRunner.TestFixture.get_ContainingAssembly [method]: unit -> System.Reflection.Assembly WoofWare.NUnitTestRunner.TestFixture.get_ContainingAssembly [method]: unit -> System.Reflection.Assembly
WoofWare.NUnitTestRunner.TestFixture.get_Name [method]: unit -> string WoofWare.NUnitTestRunner.TestFixture.get_Name [method]: unit -> string
WoofWare.NUnitTestRunner.TestFixture.get_OneTimeSetUp [method]: unit -> System.Reflection.MethodInfo option 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_OneTimeTearDown [method]: unit -> System.Reflection.MethodInfo 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_SetUp [method]: unit -> System.Reflection.MethodInfo list
WoofWare.NUnitTestRunner.TestFixture.get_TearDown [method]: unit -> System.Reflection.MethodInfo list WoofWare.NUnitTestRunner.TestFixture.get_TearDown [method]: unit -> System.Reflection.MethodInfo list
WoofWare.NUnitTestRunner.TestFixture.get_Tests [method]: unit -> WoofWare.NUnitTestRunner.SingleTestMethod list WoofWare.NUnitTestRunner.TestFixture.get_Tests [method]: unit -> WoofWare.NUnitTestRunner.SingleTestMethod list
WoofWare.NUnitTestRunner.TestFixture.get_Type [method]: unit -> System.Type
WoofWare.NUnitTestRunner.TestFixture.Name [property]: [read-only] string WoofWare.NUnitTestRunner.TestFixture.Name [property]: [read-only] string
WoofWare.NUnitTestRunner.TestFixture.OneTimeSetUp [property]: [read-only] System.Reflection.MethodInfo option 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.OneTimeTearDown [property]: [read-only] System.Reflection.MethodInfo 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.SetUp [property]: [read-only] System.Reflection.MethodInfo list
WoofWare.NUnitTestRunner.TestFixture.TearDown [property]: [read-only] System.Reflection.MethodInfo list WoofWare.NUnitTestRunner.TestFixture.TearDown [property]: [read-only] System.Reflection.MethodInfo list
WoofWare.NUnitTestRunner.TestFixture.Tests [property]: [read-only] WoofWare.NUnitTestRunner.SingleTestMethod list WoofWare.NUnitTestRunner.TestFixture.Tests [property]: [read-only] WoofWare.NUnitTestRunner.SingleTestMethod list
WoofWare.NUnitTestRunner.TestFixture.Type [property]: [read-only] System.Type
WoofWare.NUnitTestRunner.TestFixtureModule inherit obj WoofWare.NUnitTestRunner.TestFixtureModule inherit obj
WoofWare.NUnitTestRunner.TestFixtureModule.parse [static method]: System.Type -> WoofWare.NUnitTestRunner.TestFixture WoofWare.NUnitTestRunner.TestFixtureModule.parse [static method]: System.Type -> WoofWare.NUnitTestRunner.TestFixture
WoofWare.NUnitTestRunner.TestFixtureModule.run [static method]: WoofWare.NUnitTestRunner.ITestProgress -> (WoofWare.NUnitTestRunner.TestFixture -> WoofWare.NUnitTestRunner.SingleTestMethod -> bool) -> WoofWare.NUnitTestRunner.TestFixture -> WoofWare.NUnitTestRunner.FixtureRunResults WoofWare.NUnitTestRunner.TestFixtureModule.run [static method]: WoofWare.NUnitTestRunner.ITestProgress -> (WoofWare.NUnitTestRunner.TestFixture -> WoofWare.NUnitTestRunner.SingleTestMethod -> bool) -> WoofWare.NUnitTestRunner.TestFixture -> WoofWare.NUnitTestRunner.FixtureRunResults list
WoofWare.NUnitTestRunner.TestKind inherit obj, implements WoofWare.NUnitTestRunner.TestKind System.IEquatable, System.Collections.IStructuralEquatable - union type with 3 cases WoofWare.NUnitTestRunner.TestKind inherit obj, implements WoofWare.NUnitTestRunner.TestKind System.IEquatable, System.Collections.IStructuralEquatable - union type with 3 cases
WoofWare.NUnitTestRunner.TestKind+Data inherit WoofWare.NUnitTestRunner.TestKind WoofWare.NUnitTestRunner.TestKind+Data inherit WoofWare.NUnitTestRunner.TestKind
WoofWare.NUnitTestRunner.TestKind+Data.get_Item [method]: unit -> obj list list WoofWare.NUnitTestRunner.TestKind+Data.get_Item [method]: unit -> obj list list

View File

@@ -1,6 +1,7 @@
namespace WoofWare.NUnitTestRunner namespace WoofWare.NUnitTestRunner
open System open System
open System.Collections
open System.Diagnostics open System.Diagnostics
open System.IO open System.IO
open System.Reflection open System.Reflection
@@ -168,8 +169,6 @@ module TestFixture =
| "NUnit.Framework.IgnoreException" -> Ok (Some (TestMemberSuccess.Ignored (Option.ofObj exc.Message))) | "NUnit.Framework.IgnoreException" -> Ok (Some (TestMemberSuccess.Ignored (Option.ofObj exc.Message)))
| "NUnit.Framework.InconclusiveException" -> | "NUnit.Framework.InconclusiveException" ->
Ok (Some (TestMemberSuccess.Inconclusive (Option.ofObj exc.Message))) Ok (Some (TestMemberSuccess.Inconclusive (Option.ofObj exc.Message)))
| s when s.StartsWith ("NUnit.Framework.", StringComparison.Ordinal) ->
failwith $"Unrecognised special exception: %s{s}"
| _ -> Error orig | _ -> Error orig
| Error orig -> Error orig | Error orig -> Error orig
@@ -417,36 +416,15 @@ module TestFixture =
/// Run every test (except those which fail the `filter`) in this test fixture, as well as the /// Run every test (except those which fail the `filter`) in this test fixture, as well as the
/// appropriate setup and tear-down logic. /// appropriate setup and tear-down logic.
let run let private runOneFixture
(progress : ITestProgress) (progress : ITestProgress)
(filter : TestFixture -> SingleTestMethod -> bool) (filter : TestFixture -> SingleTestMethod -> bool)
(name : string)
(containingObject : obj)
(tests : TestFixture) (tests : TestFixture)
: FixtureRunResults : FixtureRunResults
= =
progress.OnTestFixtureStart tests.Name tests.Tests.Length progress.OnTestFixtureStart name tests.Tests.Length
let containingObject =
let methods =
seq {
match tests.OneTimeSetUp with
| None -> ()
| Some t -> yield t
match tests.OneTimeTearDown with
| None -> ()
| Some t -> yield t
yield! tests.Tests |> Seq.map (fun t -> t.Method)
}
methods
|> Seq.tryPick (fun mi ->
if not mi.IsStatic then
Some (Activator.CreateInstance mi.DeclaringType)
else
None
)
|> Option.toObj
let oldWorkDir = Environment.CurrentDirectory let oldWorkDir = Environment.CurrentDirectory
Environment.CurrentDirectory <- FileInfo(tests.ContainingAssembly.Location).Directory.FullName Environment.CurrentDirectory <- FileInfo(tests.ContainingAssembly.Location).Directory.FullName
@@ -472,7 +450,7 @@ module TestFixture =
ExecutionId = Guid.NewGuid () ExecutionId = Guid.NewGuid ()
TestId = Guid.NewGuid () TestId = Guid.NewGuid ()
// This one is a bit dubious, because we don't actually have a test name at all // This one is a bit dubious, because we don't actually have a test name at all
TestName = tests.Name TestName = name
ClassName = tests.Name ClassName = tests.Name
StdOut = if String.IsNullOrEmpty stdOut then None else Some stdOut StdOut = if String.IsNullOrEmpty stdOut then None else Some stdOut
StdErr = if String.IsNullOrEmpty stdErr then None else Some stdErr StdErr = if String.IsNullOrEmpty stdErr then None else Some stdErr
@@ -542,13 +520,27 @@ module TestFixture =
/// Interpret this type as a [<TestFixture>], extracting the test members from it and annotating them with all /// Interpret this type as a [<TestFixture>], extracting the test members from it and annotating them with all
/// relevant information about how we should run them. /// relevant information about how we should run them.
let parse (parentType : Type) : TestFixture = let parse (parentType : Type) : TestFixture =
let categories = let categories, args =
parentType.CustomAttributes (([], []), parentType.CustomAttributes)
|> Seq.filter (fun attr -> attr.AttributeType.FullName = "NUnit.Framework.CategoryAttribute") ||> Seq.fold (fun (categories, args) attr ->
|> Seq.map (fun attr -> attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<string>) match attr.AttributeType.FullName with
|> Seq.toList | "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<string>
cat :: categories, args
| "NUnit.Framework.TestFixtureAttribute" ->
let newArgs =
match attr.ConstructorArguments |> Seq.map _.Value |> Seq.toList with
| [ :? ICollection as x ] ->
x |> Seq.cast<CustomAttributeTypedArgument> |> Seq.map _.Value |> Seq.toList
| xs -> xs
(TestFixture.Empty parentType.Assembly parentType.Name, parentType.GetRuntimeMethods ()) categories, newArgs :: args
| _ -> categories, args
)
(TestFixture.Empty parentType args, parentType.GetRuntimeMethods ())
||> Seq.fold (fun state mi -> ||> Seq.fold (fun state mi ->
((state, []), mi.CustomAttributes) ((state, []), mi.CustomAttributes)
||> Seq.fold (fun (state, unrecognisedAttrs) attr -> ||> Seq.fold (fun (state, unrecognisedAttrs) attr ->
@@ -618,3 +610,48 @@ module TestFixture =
state state
) )
/// Run every test (except those which fail the `filter`) in this test fixture, as well as the
/// appropriate setup and tear-down logic.
let run
(progress : ITestProgress)
(filter : TestFixture -> SingleTestMethod -> bool)
(tests : TestFixture)
: FixtureRunResults list
=
match tests.Parameters with
| [] -> [ null ]
| args -> args |> List.map List.toArray
|> List.map (fun args ->
let containingObject =
let methods =
seq {
match tests.OneTimeSetUp with
| None -> ()
| Some t -> yield t
match tests.OneTimeTearDown with
| None -> ()
| Some t -> yield t
yield! tests.Tests |> Seq.map (fun t -> t.Method)
}
methods
|> Seq.tryPick (fun mi ->
if not mi.IsStatic then
Some (Activator.CreateInstance (mi.DeclaringType, args))
else
None
)
|> Option.toObj
let name =
if isNull args then
tests.Name
else
let args = args |> Seq.map (fun o -> o.ToString ()) |> String.concat ","
$"%s{tests.Name}(%s{args})"
runOneFixture progress filter name containingObject tests
)

View File

@@ -1,5 +1,5 @@
{ {
"version": "0.8", "version": "0.9",
"publicReleaseRefSpec": [ "publicReleaseRefSpec": [
"^refs/heads/main$" "^refs/heads/main$"
], ],

View File

@@ -167,7 +167,7 @@ module Program =
let testFixtures = assy.ExportedTypes |> Seq.map TestFixture.parse |> Seq.toList let testFixtures = assy.ExportedTypes |> Seq.map TestFixture.parse |> Seq.toList
let creationTime = DateTimeOffset.Now let creationTime = DateTimeOffset.Now
let results = testFixtures |> List.map (TestFixture.run progress filter) let results = testFixtures |> List.collect (TestFixture.run progress filter)
let finishTime = DateTimeOffset.Now let finishTime = DateTimeOffset.Now
let finishTimeHumanReadable = finishTime.ToString @"yyyy-MM-dd HH:mm:ss" let finishTimeHumanReadable = finishTime.ToString @"yyyy-MM-dd HH:mm:ss"