From 1e0ad9a6909d55399c880085e49eab08f4251393 Mon Sep 17 00:00:00 2001 From: Laurence King <53393540+LaurenceJKing@users.noreply.github.com> Date: Tue, 6 Apr 2021 22:19:58 +0100 Subject: [PATCH] Allow xUnit Properties attribute to be used at assembly level (#559) --- docs/RunningTests.fsx | 24 +++++-- src/FsCheck.Xunit/PropertyAttribute.fs | 29 ++++---- tests/FsCheck.Test/FsCheck.Test.fsproj | 1 + .../Fscheck.XUnit/PropertyAttributeTests.fs | 69 +++++++++++++++++++ 4 files changed, 104 insertions(+), 19 deletions(-) create mode 100644 tests/FsCheck.Test/Fscheck.XUnit/PropertyAttributeTests.fs diff --git a/docs/RunningTests.fsx b/docs/RunningTests.fsx index f8c945af..58df8617 100644 --- a/docs/RunningTests.fsx +++ b/docs/RunningTests.fsx @@ -148,23 +148,39 @@ instance just for that test method. You can also use the `PropertiesAttribute` ( type Positive = static member Double() = Arb.Default.Float() - |> Arb.mapFilter abs (fun t -> t >= 0.0) + |> Arb.mapFilter abs (fun t -> t > 0.0) type Negative = static member Double() = Arb.Default.Float() - |> Arb.mapFilter (abs >> ((-) 0.0)) (fun t -> t <= 0.0) + |> Arb.mapFilter (abs >> ((-) 0.0)) (fun t -> t < 0.0) + +type Zero = + static member Double() = + 0.0 + |> Gen.constant + |> Arb.fromGen + +[ |])>] do() +module ModuleWithoutProperties = + [] + let ``should use Arb instances from assembly``(underTest:float) = + underTest = 0.0 + + [ |] )>] + let ``should use Arb instance on method``(underTest:float) = + underTest > 0.0 [ |] )>] module ModuleWithProperties = [] let ``should use Arb instances from enclosing module``(underTest:float) = - underTest <= 0.0 + underTest < 0.0 [ |] )>] let ``should use Arb instance on method``(underTest:float) = - underTest >= 0.0 + underTest > 0.0 (** Using `PropertiesAttribute` and `PropertyAttribute` you can set any configuration. For example in following module: diff --git a/src/FsCheck.Xunit/PropertyAttribute.fs b/src/FsCheck.Xunit/PropertyAttribute.fs index 139d1378..68e1788e 100644 --- a/src/FsCheck.Xunit/PropertyAttribute.fs +++ b/src/FsCheck.Xunit/PropertyAttribute.fs @@ -148,33 +148,32 @@ type public PropertyAttribute() = member internal __.Config = config ///Set common configuration for all properties within this class or module -[] +[] type public PropertiesAttribute() = inherit PropertyAttribute() /// The xUnit2 test runner for the PropertyAttribute that executes the test via FsCheck -type PropertyTestCase(diagnosticMessageSink:IMessageSink, defaultMethodDisplay:TestMethodDisplay, testMethod:ITestMethod, ?testMethodArguments:obj []) = +type PropertyTestCase(diagnosticMessageSink:IMessageSink, defaultMethodDisplay:TestMethodDisplay, testMethod:ITestMethod, ?testMethodArguments:obj []) = inherit XunitTestCase(diagnosticMessageSink, defaultMethodDisplay, testMethod, (match testMethodArguments with | None -> null | Some v -> v)) + let combineAttributes (attributes: (IAttributeInfo option) list) = + attributes + |> List.choose id + |> List.map(fun attr -> attr.GetNamedArgument "Config") + |> List.reduce(fun higherLevelAttribute lowerLevelAttribute -> + PropertyConfig.combine lowerLevelAttribute higherLevelAttribute) + new() = new PropertyTestCase(null, TestMethodDisplay.ClassAndMethod, null) member this.Init(output:TestOutputHelper) = - let factAttribute = this.TestMethod.Method.GetCustomAttributes(typeof) |> Seq.head let arbitrariesOnClass = this.TestMethod.TestClass.Class.GetCustomAttributes(Type.GetType("FsCheck.Xunit.ArbitraryAttribute")) |> Seq.collect (fun attr -> attr.GetNamedArgument "Arbitrary") |> Seq.toArray - let generalAttribute = - this.TestMethod.TestClass.Class.GetCustomAttributes(typeof) - |> Seq.tryFind (fun _ -> true) - - let config = - match generalAttribute with - | Some generalAttribute -> - PropertyConfig.combine - (factAttribute.GetNamedArgument "Config") - (generalAttribute.GetNamedArgument "Config") - | None -> - factAttribute.GetNamedArgument "Config" + + let config = combineAttributes [ + this.TestMethod.TestClass.Class.Assembly.GetCustomAttributes(typeof) |> Seq.tryHead + this.TestMethod.TestClass.Class.GetCustomAttributes(typeof) |> Seq.tryHead + this.TestMethod.Method.GetCustomAttributes(typeof) |> Seq.head |> Some] { config with Arbitrary = Array.append config.Arbitrary arbitrariesOnClass } |> PropertyConfig.toConfig output diff --git a/tests/FsCheck.Test/FsCheck.Test.fsproj b/tests/FsCheck.Test/FsCheck.Test.fsproj index 50949ddd..1d3bb989 100644 --- a/tests/FsCheck.Test/FsCheck.Test.fsproj +++ b/tests/FsCheck.Test/FsCheck.Test.fsproj @@ -9,6 +9,7 @@ DEBUG + diff --git a/tests/FsCheck.Test/Fscheck.XUnit/PropertyAttributeTests.fs b/tests/FsCheck.Test/Fscheck.XUnit/PropertyAttributeTests.fs new file mode 100644 index 00000000..119388e8 --- /dev/null +++ b/tests/FsCheck.Test/Fscheck.XUnit/PropertyAttributeTests.fs @@ -0,0 +1,69 @@ +namespace Fscheck.Test.FsCheck.XUnit.PropertyAttribute +open FsCheck.Xunit +open FsCheck + +type AttributeLevel = +| Assembly +| ClassOrModule +| NestedClassOrModule +| MethodOrProperty + +type AttributeLevel_Assembly() = + static member Generator = + Assembly + |> Gen.constant + |> Arb.fromGen + +type AttributeLevel_ClassOrModule() = + static member Generator = + ClassOrModule + |> Gen.constant + |> Arb.fromGen + +type AttributeLevel_MethodOrProperty() = + static member Generator = + MethodOrProperty + |> Gen.constant + |> Arb.fromGen + +type AttributeLevel_NestedClassOrModule() = + static member Generator = + NestedClassOrModule + |> Gen.constant + |> Arb.fromGen + +[ |])>] +do() + +module ``when module does not have properties attribute``= + [] + let ``then the assembly attribute should be used`` = function + | Assembly -> true + | _ -> false + + [|])>] + let ``then the property attribute takes precient`` = function + | MethodOrProperty -> true + | _ -> false + +[|])>] +module ``when module has properties attribute`` = + + [] + let ``then the module's property takes precident`` = function + | ClassOrModule -> true + | _ -> false + + [|])>] + let ``then the property attribute takes precient`` = function + | MethodOrProperty -> true + | _ -> false + + [|])>] + module ``and there is and nested module`` = + [] + let ``then the nested module's property takes precident`` = function + | NestedClassOrModule -> true + | _ -> false + +