diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..80720f6
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "dotnet.defaultSolution": "Hedgehog.Xunit.sln"
+}
\ No newline at end of file
diff --git a/Hedgehog.Xunit.sln b/Hedgehog.Xunit.sln
index 41aa603..9df59b0 100644
--- a/Hedgehog.Xunit.sln
+++ b/Hedgehog.Xunit.sln
@@ -10,10 +10,13 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E5FCE96F-A6AE-434F-B901-19359F39B504}"
ProjectSection(SolutionItems) = preProject
CHANGELOG.md = CHANGELOG.md
+ documentation\readme-cSharp.md = documentation\readme-cSharp.md
readme.md = readme.md
EndProjectSection
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "csharp-attribute-based-parameters-comparision", "examples\csharp-attribute-based-parameters-comparision\csharp-attribute-based-parameters-comparision.csproj", "{2048061B-0561-4297-A02C-A12A263177A5}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "csharp-examples", "examples\csharp-examples\csharp-examples.csproj", "{601273CF-7FBF-4263-9EC4-8206CFDF6DC8}"
+EndProject
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "fsharp-examples", "examples\fsharp-examples\fsharp-examples.fsproj", "{7F2616F0-500D-488F-A2BC-B4D6D4833DE1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -29,10 +32,14 @@ Global
{63A8D184-519E-4061-8A74-F1EACAF3B0D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{63A8D184-519E-4061-8A74-F1EACAF3B0D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{63A8D184-519E-4061-8A74-F1EACAF3B0D5}.Release|Any CPU.Build.0 = Release|Any CPU
- {2048061B-0561-4297-A02C-A12A263177A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2048061B-0561-4297-A02C-A12A263177A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2048061B-0561-4297-A02C-A12A263177A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2048061B-0561-4297-A02C-A12A263177A5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {601273CF-7FBF-4263-9EC4-8206CFDF6DC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {601273CF-7FBF-4263-9EC4-8206CFDF6DC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {601273CF-7FBF-4263-9EC4-8206CFDF6DC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {601273CF-7FBF-4263-9EC4-8206CFDF6DC8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7F2616F0-500D-488F-A2BC-B4D6D4833DE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7F2616F0-500D-488F-A2BC-B4D6D4833DE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7F2616F0-500D-488F-A2BC-B4D6D4833DE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7F2616F0-500D-488F-A2BC-B4D6D4833DE1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/documentation/readme-cSharp.md b/documentation/readme-cSharp.md
new file mode 100644
index 0000000..4377695
--- /dev/null
+++ b/documentation/readme-cSharp.md
@@ -0,0 +1,378 @@
+# fsharp-hedgehog-xunit
+
+[![][nuget-shield]][nuget] [![][workflow-shield]][workflow] [![Coverage Status](https://coveralls.io/repos/github/dharmaturtle/fsharp-hedgehog-xunit/badge.svg?branch=main)](https://coveralls.io/github/dharmaturtle/fsharp-hedgehog-xunit?branch=main)
+
+[Hedgehog][hedgehog] with convenience attributes for [xUnit.net][xunit].
+
+
+
+## Features
+
+- Test method arguments generated by the customizable [`GenX.auto`](https://github.com/hedgehogqa/fsharp-hedgehog-experimental/#auto-generation).
+- `Property.check` called for each test.
+
+
+## Getting Started
+
+Install the _Hedgehog.Xunit_ [package][nuget] from Visual Studio's Package Manager Console:
+
+```powershell
+PM> Install-Package Hedgehog.Xunit
+```
+
+Suppose you have a test that uses [Hedgehog.Experimental](https://github.com/hedgehogqa/fsharp-hedgehog-experimental) and looks similar to the following:
+
+```CSharp
+using Hedgehog;
+using Hedgehog.Linq;
+using Hedgehog.Xunit;
+using Property = Hedgehog.Linq.Property;
+
+public class DocumentationSamples
+{
+ [Fact]
+ public void Reversing_a_list_twice_yields_the_original_list()
+ {
+ var gen = GenX.auto>();
+ var prop = from data in Property.ForAll(gen)
+ let testList = Enumerable.Reverse(data).Reverse().ToList()
+ select Assert.Equivalent(data, testList, true);
+ prop.Check();
+ }
+}
+```
+
+Then using Hedgehog.Xunit, you can simplify the above test to
+
+```CSharp
+[Property]
+public void Reversing_a_list_twice_yields_the_original_list_with_xunit(List xs)
+{
+ var testList = Enumerable.Reverse(xs).ToList();
+ Assert.Equivalent(xs, testList, true);
+}
+```
+
+
+
+## Documentation
+
+`Hedgehog.Xunit` provides the `Property`, `Properties`, and `Recheck` attributes.
+
+### `Property` attribute
+
+Methods with the `Property` attribute have their arguments generated by [`GenX.auto`](https://github.com/hedgehogqa/fsharp-hedgehog-experimental/#auto-generation).
+
+
+F# Example showing data generation
+
+```f#
+type ``class with a test`` (output: Xunit.Abstractions.ITestOutputHelper) =
+ []
+ let ``Can generate an int`` (i: int) =
+ output.WriteLine $"Test input: {i}"
+```
+
+
+
+C# Examples showing data generation
+
+```CSharp
+ private readonly ITestOutputHelper _output;
+
+ public DocumentationSamples(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ [Property]
+ public void Can_generate_an_int(
+ int i)
+ {
+ _output.WriteLine($"Test input: {i}");
+ }
+```
+
+
+
+```
+=== Output ===
+Test input: 0
+Test input: -1
+Test input: 1
+...
+Test input: 522317518
+Test input: 404306656
+Test input: 1550509078
+```
+
+`Property.check` is also run.
+
+```f#
+[]
+let ``This test fails`` (b: bool) =
+ b
+
+=== Output ===
+Hedgehog.FailedException: *** Failed! Falsifiable (after 2 tests):
+(false)
+```
+
+If the test returns `Async<_>` or `Task<_>`, then `Async.RunSynchronously` is called, _which blocks the thread._ This may have significant performance implications as tests run 100 times by default.
+
+```f#
+[]
+let ``Async with exception shrinks`` (i: int) = async {
+ do! Async.Sleep 100
+ if i > 10 then
+ failwith "whoops!"
+ }
+
+=== Output ===
+Hedgehog.FailedException: *** Failed! Falsifiable (after 12 tests):
+(11)
+```
+A test returning a `Result` in an `Error` state will be treated as a failure.
+
+```f#
+[]
+let ``Result with Error shrinks`` (i: int) =
+ if i > 10 then
+ Error ()
+ else
+ Ok ()
+
+=== Output ===
+Hedgehog.FailedException: *** Failed! Falsifiable (after 13 tests and 2 shrinks):
+[11]
+```
+
+Tests returning `Async>` or `Task>` are run synchronously and are expected to be in the `Ok` state.
+
+Tests returning a `Property` or `Property` will have `Property.check` automatically called:
+
+```f#
+[]
+let ``returning a failing property with an external number gen fails and shrinks`` i = property {
+ let! _50 = Gen.constant 50
+ return i <= _50
+}
+
+=== Output ===
+System.Exception: *** Failed! Falsifiable (after 23 tests and 5 shrinks):
+[51]
+50
+```
+
+The `Property` attribute's constructor may take several arguments: `AutoGenConfig`, `AutoGenConfigArgs`, `Tests` (count), `Shrinks` (count), and `Size`. Since the `Property` attribute extends `Xunit.FactAttribute`, it may also take `DisplayName`, `Skip`, and `Timeout`.
+
+#### `AutoGenConfig` and `AutoGenConfigArgs`
+
+* Default: `GenX.defaults`
+
+Create a class with a single static property or method that returns an instance of `AutoGenConfig`. Then provide the type of this class as an argument to the `Property` attribute. This works around the constraint that [`Attribute` parameters must be a constant.](https://stackoverflow.com/a/33007272)
+
+```f#
+type AutoGenConfigContainer =
+ static member __ =
+ GenX.defaults |> AutoGenConfig.addGenerator (Gen.constant 13)
+
+[)>]
+let ``This test passes`` (i: int) =
+ i = 13
+```
+
+If the method takes arguments, you must provide them using `AutoGenConfigArgs`.
+
+```f#
+type ConfigWithArgs =
+ static member __ a b =
+ GenX.defaults
+ |> AutoGenConfig.addGenerator (Gen.constant a)
+ |> AutoGenConfig.addGenerator (Gen.constant b)
+
+[, AutoGenConfigArgs = [|"foo"; 13|])>]
+let ``This also passes`` s i =
+ s = "foo" && i = 13
+```
+
+#### `Tests` (count)
+
+Specifies the number of tests to be run, though more or less may occur due to shrinking or early failure.
+
+```f#
+[)>]
+let ``This runs 3 times`` () =
+ ()
+```
+
+#### `Shrinks` (count)
+
+Specifies the maximal number of shrinks that may run.
+
+```f#
+[)>]
+let ``No shrinks occur`` i =
+ if i > 50 then failwith "oops"
+```
+
+#### `Size`
+
+Sets the `Size` to a value for all runs.
+
+```f#
+[]
+let ``"i" mostly ranges between -1 and 1`` i =
+ printfn "%i" i
+```
+
+### `Properties` attribute
+
+This optional attribute can decorate modules or classes. It sets default arguments for `AutoGenConfig`, `AutoGenConfigArgs`, `Tests`, `Shrinks`, and `Size`. These will be overridden by any arguments provided by the `Property` attribute.
+
+```f#
+type Int13 = static member __ = GenX.defaults |> AutoGenConfig.addGenerator (Gen.constant 13)
+type Int2718 = static member __ = GenX.defaults |> AutoGenConfig.addGenerator (Gen.constant 2718)
+
+[, 1)>]
+module ``Module with tests`` =
+
+ []
+ let ``this passes and runs once`` (i: int) =
+ i = 13
+
+ [, 2)>]
+ let ``this passes and runs twice`` (i: int) =
+ i = 2718
+```
+
+### `Recheck` attribute
+
+This optional method attribute invokes `Property.recheck` with the given `Size` and `Seed`. It must be used with `Property`.
+
+```f#
+[]
+[]
+let ``this passes`` i =
+ i = 12345
+```
+
+## Tips
+
+Use named arguments to select the desired constructor overload.
+
+```f#
+[, AutoGenConfig = typeof)>]
+module __ =
+ [, Tests = 2718, Skip = "just because")>]
+ let ``Not sure why you'd do this, but okay`` () =
+ ()
+```
+
+Consider extending `PropertyAttribute` or `PropertiesAttribute` to hardcode commonly used arguments.
+
+```f#
+type Int13 = static member __ = GenX.defaults |> AutoGenConfig.addGenerator (Gen.constant 13)
+
+type PropertyInt13Attribute() = inherit PropertyAttribute(typeof)
+module __ =
+ []
+ let ``this passes`` (i: int) =
+ i = 13
+
+type PropertiesInt13Attribute() = inherit PropertiesAttribute(typeof)
+[]
+module ___ =
+ []
+ let ``this also passes`` (i: int) =
+ i = 13
+```
+
+
+ Known issue with generating a single tuple.
+
+`GenX.autoWith` can generate a tuple.
+
+```f#
+[]
+let ``This passes`` () =
+ Property.check <| property {
+ let! a, b =
+ GenX.defaults
+ |> AutoGenConfig.addGenerator (Gen.constant (1, 2))
+ |> GenX.autoWith
+ Assert.Equal(1, a)
+ Assert.Equal(2, b)
+ }
+```
+
+However, blindly converting the above test to `Hedgehog.Xunit` will fail.
+
+```f#
+type CustomTupleGen = static member __ = GenX.defaults |> AutoGenConfig.addGenerator (Gen.constant (1, 2))
+[)>]
+let ``This fails`` ((a,b) : int*int) =
+ Assert.Equal(1, a)
+ Assert.Equal(2, b)
+```
+
+This is because F# functions whose only parameter is a tuple will generate IL that un-tuples that parameter, yielding a function whose arity is the number of elements in the tuple. More concretely, this F#
+
+```f#
+let ``This fails`` ((a,b) : int*int) = ()
+```
+
+yields this IL (in debug mode)
+
+```IL
+.method public static
+ void 'This fails' (
+ valuetype [System.Private.CoreLib]System.Int32 _arg1_0,
+ valuetype [System.Private.CoreLib]System.Int32 _arg1_1
+ ) cil managed
+{
+ .maxstack 8
+ IL_0000: ret
+}
+```
+
+Due to this behavior `Hedgehog.Xunit` can't know that the original parameter was a tuple. It will therefore not use the registered tuple generator. A workaround is to pass a second (possibly unused) parameter.
+
+```f#
+type CustomTupleGen = static member __ = GenX.defaults |> AutoGenConfig.addGenerator (Gen.constant (1, 2))
+[)>]
+let ``This passes`` (((a,b) : int*int), _: bool) =
+ Assert.Equal(1, a)
+ Assert.Equal(2, b)
+```
+
+The updated F#
+
+```f#
+let ``This passes`` (((a,b) : int*int), _: bool) = ()
+```
+
+yields this IL
+
+```IL
+.method public static
+ void 'This passes' (
+ class [System.Private.CoreLib]System.Tuple`2 _arg1,
+ valuetype [System.Private.CoreLib]System.Boolean _arg2
+ ) cil managed
+{
+ .maxstack 8
+ IL_0000: ret
+}
+```
+
+[Source of IL.](https://sharplab.io/#v2:DYLgZgzgNALiCWwoBMQGoA+BbA9sgrsAKYAEAsgJ5l6FECwAUI8TCQHY4BOWAhsAGL42AYxjwcbEjxIAjEgF4pJNLMbMirAAaaAKgAt4EEmB6II2kgApLPKDICUJECXhsYAKlcxHiy/bUMLCTa+oYkAA48EBBE5ppW1rYOTi5unm72UCQA+s4yODjAPlb2QA)
+
+
+[hedgehog]: https://github.com/hedgehogqa/fsharp-hedgehog
+[xunit]: https://xunit.net/
+
+[nuget]: https://www.nuget.org/packages/Hedgehog.Xunit/
+[nuget-shield]: https://img.shields.io/nuget/v/Hedgehog.Xunit.svg
+[workflow]: https://github.com/dharmaturtle/fsharp-hedgehog-xunit/actions?query=workflow%3AMain
+[workflow-shield]: https://github.com/dharmaturtle/fsharp-hedgehog-xunit/workflows/Main/badge.svg
diff --git a/examples/csharp-examples/DocumentationSamples.cs b/examples/csharp-examples/DocumentationSamples.cs
new file mode 100644
index 0000000..3958375
--- /dev/null
+++ b/examples/csharp-examples/DocumentationSamples.cs
@@ -0,0 +1,42 @@
+using Hedgehog;
+using Hedgehog.Linq;
+using Hedgehog.Xunit;
+using Xunit.Abstractions;
+using Property = Hedgehog.Linq.Property;
+
+namespace csharp_examples;
+
+public class DocumentationSamples
+{
+ private readonly ITestOutputHelper _output;
+
+ public DocumentationSamples(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ [Property]
+ public void Can_generate_an_int(
+ int i)
+ {
+ _output.WriteLine($"Test input: {i}");
+ }
+
+
+ [Fact]
+ public void Reversing_a_list_twice_yields_the_original_list()
+ {
+ var gen = GenX.auto>();
+ var prop = from data in Property.ForAll(gen)
+ let testList = Enumerable.Reverse(data).Reverse().ToList()
+ select Assert.Equivalent(data, testList, true);
+ prop.Check();
+ }
+
+ [Property]
+ public void Reversing_a_list_twice_yields_the_original_list_with_xunit(List xs)
+ {
+ var testList = Enumerable.Reverse(xs).ToList();
+ Assert.Equivalent(xs, testList, true);
+ }
+}
diff --git a/examples/csharp-attribute-based-parameters-comparision/Usings.cs b/examples/csharp-examples/Usings.cs
similarity index 100%
rename from examples/csharp-attribute-based-parameters-comparision/Usings.cs
rename to examples/csharp-examples/Usings.cs
diff --git a/examples/csharp-attribute-based-parameters-comparision/PositiveAndNegativeGeneratorContainerTypes.cs b/examples/csharp-examples/attribute-based-parameter-comparison/PositiveAndNegativeGeneratorContainerTypes.cs
similarity index 93%
rename from examples/csharp-attribute-based-parameters-comparision/PositiveAndNegativeGeneratorContainerTypes.cs
rename to examples/csharp-examples/attribute-based-parameter-comparison/PositiveAndNegativeGeneratorContainerTypes.cs
index 1fcb2b2..c882e1d 100644
--- a/examples/csharp-attribute-based-parameters-comparision/PositiveAndNegativeGeneratorContainerTypes.cs
+++ b/examples/csharp-examples/attribute-based-parameter-comparison/PositiveAndNegativeGeneratorContainerTypes.cs
@@ -5,7 +5,7 @@
using Range = Hedgehog.Linq.Range;
-namespace csharp_attribute_based_parameters_comparision;
+namespace csharp_examples.attribute_based_parameter_comparison;
public record PositiveInt(int Value);
public record NegativeInt( int Value );
diff --git a/examples/csharp-attribute-based-parameters-comparision/PositiveAndNegativeSimpleAttribute.cs b/examples/csharp-examples/attribute-based-parameter-comparison/PositiveAndNegativeSimpleAttribute.cs
similarity index 84%
rename from examples/csharp-attribute-based-parameters-comparision/PositiveAndNegativeSimpleAttribute.cs
rename to examples/csharp-examples/attribute-based-parameter-comparison/PositiveAndNegativeSimpleAttribute.cs
index 87d2b25..76a58c4 100644
--- a/examples/csharp-attribute-based-parameters-comparision/PositiveAndNegativeSimpleAttribute.cs
+++ b/examples/csharp-examples/attribute-based-parameter-comparison/PositiveAndNegativeSimpleAttribute.cs
@@ -5,11 +5,11 @@
namespace csharp_attribute_based_parameters_comparision;
-public class Negative : ParameterGeneraterBaseType
+public class Negative : ParameterGeneratorBaseType
{
public override Gen Generator => Gen.Int32(Range.Constant(Int32.MinValue, -1));
}
-public class Positive : ParameterGeneraterBaseType
+public class Positive : ParameterGeneratorBaseType
{
public override Gen Generator => Gen.Int32(Range.Constant(1, Int32.MaxValue));
}
diff --git a/examples/csharp-attribute-based-parameters-comparision/PositiveAndNegativeUtilizingIntegerRangeAttribute.cs b/examples/csharp-examples/attribute-based-parameter-comparison/PositiveAndNegativeUtilizingIntegerRangeAttribute.cs
similarity index 91%
rename from examples/csharp-attribute-based-parameters-comparision/PositiveAndNegativeUtilizingIntegerRangeAttribute.cs
rename to examples/csharp-examples/attribute-based-parameter-comparison/PositiveAndNegativeUtilizingIntegerRangeAttribute.cs
index 8b4054b..ba125cc 100644
--- a/examples/csharp-attribute-based-parameters-comparision/PositiveAndNegativeUtilizingIntegerRangeAttribute.cs
+++ b/examples/csharp-examples/attribute-based-parameter-comparison/PositiveAndNegativeUtilizingIntegerRangeAttribute.cs
@@ -5,7 +5,7 @@
namespace csharp_attribute_based_parameters_comparision;
-public class Int32Range : ParameterGeneraterBaseType
+public class Int32Range : ParameterGeneratorBaseType
{
private readonly int _min;
private readonly int _max;
diff --git a/examples/csharp-attribute-based-parameters-comparision/csharp-attribute-based-parameters-comparision.csproj b/examples/csharp-examples/csharp-examples.csproj
similarity index 92%
rename from examples/csharp-attribute-based-parameters-comparision/csharp-attribute-based-parameters-comparision.csproj
rename to examples/csharp-examples/csharp-examples.csproj
index 34ba708..5bf33f5 100644
--- a/examples/csharp-attribute-based-parameters-comparision/csharp-attribute-based-parameters-comparision.csproj
+++ b/examples/csharp-examples/csharp-examples.csproj
@@ -2,7 +2,7 @@
net7.0
- csharp_attribute_based_parameters_comparision
+ csharp_examples
enable
enable
diff --git a/examples/fsharp-examples/Tests.fs b/examples/fsharp-examples/Tests.fs
new file mode 100644
index 0000000..9d325e5
--- /dev/null
+++ b/examples/fsharp-examples/Tests.fs
@@ -0,0 +1,69 @@
+namespace fsharp_examples
+module Tests =
+
+open Xunit
+open Hedgehog
+open Hedgehog.Xunit
+
+//Properties containing multiple parameter of the same type with different
+//generator requirements.
+
+///Initial property
+let positiveInt() = Range.constant 0 System.Int32.MaxValue |> Gen.int32
+let negativeInt() = Range.constant System.Int32.MinValue 0 |> Gen.int32
+
+[]
+let ``Positive + Negative <= Positive`` () =
+ property {
+ let! positive = positiveInt()
+ let! negative = negativeInt()
+ return positive + negative <= positive
+ } |> Property.checkBool
+
+//Using property attribute we need to create container types so that
+//the parameters of the property can be of different types.
+type PositiveInt = {value : int}
+type NegativeInt = {value : int}
+
+type AutoGenConfigContainer =
+ static member __ =
+ GenX.defaults
+ |> AutoGenConfig.addGenerator (positiveInt() |> Gen.map(fun x-> {PositiveInt.value=x}))
+ |> AutoGenConfig.addGenerator (negativeInt() |> Gen.map(fun x -> {NegativeInt.value=x}))
+
+
+[)>]
+let ``Positive + Negative <= Positive xunit`` (positive:PositiveInt) (negative:NegativeInt) =
+ positive.value + negative.value <= positive.value
+
+//Using attributes to configure what generator the property should use
+type Posint() =
+ inherit ParameterGeneratorBaseType()
+ override this.Generator = positiveInt()
+
+type NegInt() =
+ inherit ParameterGeneratorBaseType()
+ override this.Generator = negativeInt()
+
+[]
+let ``Positive + Negative <= Positive xunit attribute`` ([] positive) ([] negative) =
+ positive + negative <= positive
+
+
+
+
+
+
+type Posint() =
+ inherit ParameterGeneratorBaseType()
+ override this.Generator = positiveInt()
+
+type NegInt() =
+ inherit ParameterGeneratorBaseType()
+ override this.Generator = negativeInt()
+
+[]
+let ``Positive + Negative <= Positive xunit`` ([] positive) ([] negative) =
+ positive + negative <= positive
+
+
diff --git a/examples/fsharp-examples/fsharp-examples.fsproj b/examples/fsharp-examples/fsharp-examples.fsproj
new file mode 100644
index 0000000..637fe45
--- /dev/null
+++ b/examples/fsharp-examples/fsharp-examples.fsproj
@@ -0,0 +1,32 @@
+
+
+
+ net7.0
+ fsharp_examples
+
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/readme.md b/readme.md
index 6e186fe..25311ab 100644
--- a/readme.md
+++ b/readme.md
@@ -8,8 +8,9 @@
## Features
-- Test method arguments generated by the customizable [`GenX.auto`](https://github.com/hedgehogqa/fsharp-hedgehog-experimental/#auto-generation).
+- Test method arguments can be generated [`GenX.auto`](https://github.com/hedgehogqa/fsharp-hedgehog-experimental/#auto-generation).
- `Property.check` called for each test.
+- Attribute based generator definitions
## Getting Started
@@ -43,11 +44,18 @@ let ``Reversing a list twice yields the original list, with Hedgehog.Xunit`` (xs
## Documentation
-`Hedgehog.Xunit` provides the `Property`, `Properties`, and `Recheck` attributes.
+`Hedgehog.Xunit` provides the following attributes:
+* [Property](#properties-attribute)
+Converts xunit 'Fact' into a property, allowing you to easily configure property parameters.
+* [Properties](#properties-attribute)
+Allows the easy configuration of all properties in a test suite.
+* [Recheck](#recheck-attribute)
+Rerun a particular test case.
+* [ParameterGeneratorBaseType]() Control what generatore is used on a parameter by parameter basis. configure
### `Property` attribute
-
-Methods with the `Property` attribute have their arguments generated by [`GenX.auto`](https://github.com/hedgehogqa/fsharp-hedgehog-experimental/#auto-generation).
+---
+Methods with the `Property` attribute have their arguments automatically generated by [`GenX.auto`](https://github.com/hedgehogqa/fsharp-hedgehog-experimental/#auto-generation).
```f#
type ``class with a test`` (output: Xunit.Abstractions.ITestOutputHelper) =
@@ -65,7 +73,7 @@ Test input: 404306656
Test input: 1550509078
```
-`Property.check` is also run.
+`Property.check` is called automatically.
```f#
[]
@@ -123,40 +131,47 @@ System.Exception: *** Failed! Falsifiable (after 23 tests and 5 shrinks):
50
```
-The `Property` attribute's constructor may take several arguments: `AutoGenConfig`, `AutoGenConfigArgs`, `Tests` (count), `Shrinks` (count), and `Size`. Since the `Property` attribute extends `Xunit.FactAttribute`, it may also take `DisplayName`, `Skip`, and `Timeout`.
-
-#### `AutoGenConfig` and `AutoGenConfigArgs`
+### `Property` Configuration
+---
+The `Property` attribute's constructor may take several arguments:
+* [`AutoGenConfig` and `AutoGenConfigArgs`](#autogenconfig-and-autogenconfigargs): Allow the manual control of generators
+* [`Tests`](#tests-count): Specifies the number of tests to run
+* [`Shrinks`](#shrinks-count): Specifies the number of shrinks
-* Default: `GenX.defaults`
+The `Property` attribute extends `Xunit.FactAttribute`, so it may also take `DisplayName`, `Skip`, and `Timeout`.
-Create a class with a single static property or method that returns an instance of `AutoGenConfig`. Then provide the type of this class as an argument to the `Property` attribute. This works around the constraint that [`Attribute` parameters must be a constant.](https://stackoverflow.com/a/33007272)
+#### `AutoGenConfig` and `AutoGenConfigArgs`
+---
+The default generator used to create arguments is `GenX.defaults`. To specify different generators:
+* Create a class with a single static property or method that returns an instance of `AutoGenConfig`.
+* Provide the type of this class as an argument to the `Property` attribute. This works around the constraint that [`Attribute` parameters must be a constant.](https://stackoverflow.com/a/33007272)
+
+ ```f#
+ type AutoGenConfigContainer =
+ static member __ =
+ GenX.defaults |> AutoGenConfig.addGenerator (Gen.constant 13)
+
+ [)>]
+ let ``This test passes`` (i: int) =
+ i = 13
+ ```
-```f#
-type AutoGenConfigContainer =
- static member __ =
- GenX.defaults |> AutoGenConfig.addGenerator (Gen.constant 13)
+* If the method takes arguments, you must provide them using `AutoGenConfigArgs`.
-[)>]
-let ``This test passes`` (i: int) =
- i = 13
-```
+ ```f#
+ type ConfigWithArgs =
+ static member __ a b =
+ GenX.defaults
+ |> AutoGenConfig.addGenerator (Gen.constant a)
+ |> AutoGenConfig.addGenerator (Gen.constant b)
-If the method takes arguments, you must provide them using `AutoGenConfigArgs`.
-
-```f#
-type ConfigWithArgs =
- static member __ a b =
- GenX.defaults
- |> AutoGenConfig.addGenerator (Gen.constant a)
- |> AutoGenConfig.addGenerator (Gen.constant b)
-
-[, AutoGenConfigArgs = [|"foo"; 13|])>]
-let ``This also passes`` s i =
- s = "foo" && i = 13
-```
+ [, AutoGenConfigArgs = [|"foo"; 13|])>]
+ let ``This also passes`` s i =
+ s = "foo" && i = 13
+ ```
#### `Tests` (count)
-
+---
Specifies the number of tests to be run, though more or less may occur due to shrinking or early failure.
```f#
@@ -166,7 +181,7 @@ let ``This runs 3 times`` () =
```
#### `Shrinks` (count)
-
+---
Specifies the maximal number of shrinks that may run.
```f#
@@ -175,8 +190,8 @@ let ``No shrinks occur`` i =
if i > 50 then failwith "oops"
```
-#### `Size`
-
+##### `Size`
+---
Sets the `Size` to a value for all runs.
```f#
@@ -185,9 +200,9 @@ let ``"i" mostly ranges between -1 and 1`` i =
printfn "%i" i
```
-### `Properties` attribute
-
-This optional attribute can decorate modules or classes. It sets default arguments for `AutoGenConfig`, `AutoGenConfigArgs`, `Tests`, `Shrinks`, and `Size`. These will be overridden by any arguments provided by the `Property` attribute.
+#### Properties attribute
+---
+This optional attribute can decorate modules or classes. It sets default arguments for [`AutoGenConfig`, `AutoGenConfigArgs`](#autogenconfig-and-autogenconfigargs), [`Tests`](#tests-count), [`Shrinks`](#shrinks-count), and [`Size`](#size). These will be overridden by any arguments provided by the `Property` attribute.
```f#
type Int13 = static member __ = GenX.defaults |> AutoGenConfig.addGenerator (Gen.constant 13)
@@ -205,9 +220,9 @@ module ``Module with tests`` =
i = 2718
```
-### `Recheck` attribute
-
-This optional method attribute invokes `Property.recheck` with the given `Size` and `Seed`. It must be used with `Property`.
+### Recheck attribute
+---
+This optional method attribute invokes `Property.recheck` with the given `Size` and `Seed`, it must be used with `Property`.
```f#
[]
@@ -216,6 +231,66 @@ let ``this passes`` i =
i = 12345
```
+### ParameterGeneratorBaseType
+---
+This is the base type of an attribute that can applied property function arguments, it allows you to provide a generator on a parameter by parameter basis. Lets look at the following property:
+
+```F#
+open Xunit
+open Hedgehog
+
+let positiveInt() = Range.constant 0 System.Int32.MaxValue |> Gen.int32
+let negativeInt() = Range.constant System.Int32.MinValue 0 |> Gen.int32
+
+[]
+let ``Positive + Negative <= Positive`` () =
+ property {
+ let! positive = positiveInt()
+ let! negative = negativeInt()
+ return positive + negative <= positive
+ } |> Property.checkBool
+```
+
+Using a `Property` attribute on this turns out to be quite tricky as we require two different generators for the same type, we may end up with something like this.
+
+```F#
+type PositiveInt = {value : int}
+type NegativeInt = {value : int}
+
+let positiveInt() = positiveInt() |> Gen.map(fun x-> {PositiveInt.value=x})
+let negativeInt() = negativeInt() |> Gen.map(fun x -> {NegativeInt.value=x})
+
+type AutoGenConfigContainer =
+ static member __ =
+ GenX.defaults
+ |> AutoGenConfig.addGenerator (positiveInt())
+ |> AutoGenConfig.addGenerator (negativeInt())
+
+[)>]
+let ``Positive + Negative <= Positive`` (positive:PositiveInt) (negative:NegativeInt) =
+ positive.value + negative.value <= positive.value
+```
+
+ Using the `ParameterGeneratorBaseType` attribute is would look like this:
+
+ ```F#
+ type Posint() =
+ inherit ParameterGeneratorBaseType()
+ override this.Generator = positiveInt()
+
+type NegInt() =
+ inherit ParameterGeneratorBaseType()
+ override this.Generator = negativeInt()
+
+[]
+let ``Positive + Negative <= Positive xunit`` ([] positive) ([] negative) =
+ positive + negative <= positive
+```
+
+
+
+
+
## Tips
Use named arguments to select the desired constructor overload.
diff --git a/src/Hedgehog.Xunit/Attributes.fs b/src/Hedgehog.Xunit/Attributes.fs
index 7328cd8..ef83f40 100644
--- a/src/Hedgehog.Xunit/Attributes.fs
+++ b/src/Hedgehog.Xunit/Attributes.fs
@@ -105,7 +105,7 @@ type RecheckAttribute(recheckData) =
[]
[]
-type ParameterGeneraterBaseType<'a>() =
+type ParameterGeneratorBaseType<'a>() =
inherit Attribute()
abstract member Generator : Gen<'a>
diff --git a/src/Hedgehog.Xunit/InternalLogic.fs b/src/Hedgehog.Xunit/InternalLogic.fs
index 18bdf30..10e9e59 100644
--- a/src/Hedgehog.Xunit/InternalLogic.fs
+++ b/src/Hedgehog.Xunit/InternalLogic.fs
@@ -164,7 +164,7 @@ let report (testMethod:MethodInfo) testClass testClassInstance =
attributes
|> List.tryPick(fun x ->
let attType = x.GetType().BaseType
- if attType.IsGenericType && attType.GetGenericTypeDefinition().IsAssignableFrom(typedefof>) then
+ if attType.IsGenericType && attType.GetGenericTypeDefinition().IsAssignableFrom(typedefof>) then
let method = attType.GetMethods() |> Array.pick(fun x -> if x.Name = "Box" then Some x else None)
method.Invoke(x, null) :?> Gen |> Some
else
diff --git a/tests/Hedgehog.Xunit.Tests/PropertyTests.fs b/tests/Hedgehog.Xunit.Tests/PropertyTests.fs
index 73af2a5..39378f6 100644
--- a/tests/Hedgehog.Xunit.Tests/PropertyTests.fs
+++ b/tests/Hedgehog.Xunit.Tests/PropertyTests.fs
@@ -12,15 +12,15 @@ open Common
type Int13 = static member __ = GenX.defaults |> AutoGenConfig.addGenerator (Gen.constant 13)
type Int5() =
- inherit ParameterGeneraterBaseType()
+ inherit ParameterGeneratorBaseType()
override this.Generator = Gen.constant 5
type Int6() =
- inherit ParameterGeneraterBaseType()
+ inherit ParameterGeneratorBaseType()
override this.Generator = Gen.constant 6
type IntCRange(max:int, min:int)=
- inherit ParameterGeneraterBaseType()
+ inherit ParameterGeneratorBaseType()
override this.Generator = (Range.constant max min) |> Gen.int32