diff --git a/Directory.Packages.props b/Directory.Packages.props
index 543f6d23..cba68949 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -37,6 +37,8 @@
+
+
\ No newline at end of file
diff --git a/FsCheck Release Notes.md b/FsCheck Release Notes.md
index a258d3f5..ad18154a 100644
--- a/FsCheck Release Notes.md
+++ b/FsCheck Release Notes.md
@@ -1,3 +1,11 @@
+### 3.3.1 - 24 August 2025
+
+* Update xunit.v3.extensibility.core to v3.0.1 to fix a test discovery issue. (by Hyogeol Lee)
+
+### 3.3.0 - 3 June 2025
+
+* Added FsCheck.Xunit.v3. (by JohSand)
+
### 3.2.0 - 10 April 2025
* Support C# struct record generation. (by Brian Rourke Boll)
diff --git a/FsCheck.sln b/FsCheck.sln
index 97d79f45..90eedc0f 100644
--- a/FsCheck.sln
+++ b/FsCheck.sln
@@ -59,6 +59,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharp.DocSnippets", "examp
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FsCheck.Test.CSharp", "tests\FsCheck.Test.CSharp\FsCheck.Test.CSharp.csproj", "{86955D54-4D54-4D4C-A7DC-F98F4CCFE498}"
EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsCheck.Test.v3", "tests\FsCheck.Test.v3\FsCheck.Test.v3.fsproj", "{0CE5ED25-7AE1-C88B-2C70-D4C110D59BDE}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsCheck.Xunit.v3", "src\FsCheck.Xunit.v3\FsCheck.Xunit.v3.fsproj", "{097DCA94-FF3D-BB6D-69FC-405B20621DA8}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -113,6 +117,14 @@ Global
{86955D54-4D54-4D4C-A7DC-F98F4CCFE498}.Debug|Any CPU.Build.0 = Debug|Any CPU
{86955D54-4D54-4D4C-A7DC-F98F4CCFE498}.Release|Any CPU.ActiveCfg = Release|Any CPU
{86955D54-4D54-4D4C-A7DC-F98F4CCFE498}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0CE5ED25-7AE1-C88B-2C70-D4C110D59BDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0CE5ED25-7AE1-C88B-2C70-D4C110D59BDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0CE5ED25-7AE1-C88B-2C70-D4C110D59BDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0CE5ED25-7AE1-C88B-2C70-D4C110D59BDE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {097DCA94-FF3D-BB6D-69FC-405B20621DA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {097DCA94-FF3D-BB6D-69FC-405B20621DA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {097DCA94-FF3D-BB6D-69FC-405B20621DA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {097DCA94-FF3D-BB6D-69FC-405B20621DA8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/build.fsx b/build.fsx
index 4176eccf..97cfcf02 100644
--- a/build.fsx
+++ b/build.fsx
@@ -208,7 +208,10 @@ let packages =
Summary = "Integrates FsCheck with NUnit"
}
{ Name = "FsCheck.Xunit"
- Summary = "Integrates FsCheck with xUnit.NET"
+ Summary = "Integrates FsCheck with xUnit.NET v2"
+ }
+ { Name = "FsCheck.Xunit.v3"
+ Summary = "Integrates FsCheck with xUnit.NET v3"
}
]
@@ -315,7 +318,7 @@ let packNuGet (_ : HaveTested) : HavePacked =
// via `dotnet pack`. Without this next bit, FsCheck.Xunit vA.B.C depends on >= FsCheck vA.B.C, not
// on FsCheck exactly at vA.B.C.
- for package in ["FsCheck.Xunit" ; "FsCheck.NUnit"] do
+ for package in ["FsCheck.Xunit" ; "FsCheck.NUnit" ; "FsCheck.Xunit.v3"] do
let nupkg = FileInfo (Path.Combine ("bin", $"%s{package}.%s{buildVersion}.nupkg"))
let stream = nupkg.Open (FileMode.Open, FileAccess.ReadWrite)
use archive = new ZipArchive (stream, ZipArchiveMode.Update)
@@ -348,7 +351,7 @@ let pushNuGet (_ : HaveTested) (_ : HavePacked) =
["NUGET_KEY", nugetKey]
|> Map.ofList
- for package in ["FsCheck" ; "FsCheck.NUnit" ; "FsCheck.Xunit"] do
+ for { Name = package } in packages do
let package = Path.Combine ("bin", $"%s{package}.%s{buildVersion}.nupkg")
// yup, `dotnet nuget` really does have no way to pass in the critical secret secretly, so we have
// to get the shell to do it
diff --git a/src/FsCheck.Xunit.v3/AssemblyInfo.fs b/src/FsCheck.Xunit.v3/AssemblyInfo.fs
new file mode 100644
index 00000000..12b8659d
--- /dev/null
+++ b/src/FsCheck.Xunit.v3/AssemblyInfo.fs
@@ -0,0 +1,23 @@
+
+// Auto-generated; edits may be deleted at any time
+namespace System
+open System.Reflection
+open System.Runtime.CompilerServices
+
+[]
+[]
+[]
+[]
+[]
+[]
+[]
+do ()
+
+module internal AssemblyVersionInformation =
+ let [] AssemblyTitle = "FsCheck.Xunit.v3"
+ let [] AssemblyProduct = "FsCheck.Xunit.v3"
+ let [] AssemblyDescription = "Integrates FsCheck with xUnit.v3"
+ let [] AssemblyVersion = "3.2.0"
+ let [] AssemblyFileVersion = "3.2.0"
+ let [] AssemblyKeyFile = "../../FsCheckKey.snk"
+ let [] InternalsVisibleTo = "FsCheck.Test"
diff --git a/src/FsCheck.Xunit.v3/CheckExtensions.fs b/src/FsCheck.Xunit.v3/CheckExtensions.fs
new file mode 100644
index 00000000..ed8304a9
--- /dev/null
+++ b/src/FsCheck.Xunit.v3/CheckExtensions.fs
@@ -0,0 +1,62 @@
+namespace FsCheck.Xunit
+
+open System
+open System.Runtime.CompilerServices
+open Xunit
+open Xunit.v3
+
+open FsCheck
+
+module private Helper =
+ let private runner (testOutputHelper: ITestOutputHelper) =
+ { new IRunner with
+ member _.OnStartFixture t =
+ Runner.onStartFixtureToString t |> testOutputHelper.WriteLine
+ member _.OnArguments (ntest,args, every) =
+ every ntest args |> testOutputHelper.WriteLine
+ member _.OnShrink(args, everyShrink) =
+ everyShrink args |> testOutputHelper.WriteLine
+ member _.OnFinished(name,testResult) =
+ Runner.onFinishedToString name testResult |> testOutputHelper.WriteLine
+ }
+
+ let private throwingRunner (testOutputHelper: ITestOutputHelper) =
+ { new IRunner with
+ member _.OnStartFixture t =
+ testOutputHelper.WriteLine (Runner.onStartFixtureToString t)
+ member _.OnArguments (ntest,args, every) =
+ testOutputHelper.WriteLine (every ntest args)
+ member _.OnShrink(args, everyShrink) =
+ testOutputHelper.WriteLine (everyShrink args)
+ member _.OnFinished(name,testResult) =
+ match testResult with
+ | TestResult.Passed _ -> testOutputHelper.WriteLine (Runner.onFinishedToString name testResult)
+ | _ -> failwithf "%s" (Runner.onFinishedToString name testResult)
+ }
+
+ let writeToXunit (config:Config) (testOutputHelper: ITestOutputHelper) =
+ config.WithRunner(runner testOutputHelper)
+
+ let writeToXunitThrow (config:Config) (testOutputHelper: ITestOutputHelper) =
+ config.WithRunner(throwingRunner testOutputHelper)
+
+[]
+type CheckExtensions =
+ []
+ static member QuickCheck(property:Property, testOutputHelper: ITestOutputHelper) =
+ Check.One(Helper.writeToXunit Config.Quick testOutputHelper,property)
+ []
+ static member QuickCheck(property:Property, testName:string, testOutputHelper: ITestOutputHelper) =
+ Check.One(testName,Helper.writeToXunit Config.Quick testOutputHelper,property)
+ []
+ static member QuickCheckThrowOnFailure(property:Property, testOutputHelper: ITestOutputHelper) =
+ Check.One(Helper.writeToXunitThrow Config.QuickThrowOnFailure testOutputHelper,property)
+ []
+ static member VerboseCheck(property:Property, testOutputHelper: ITestOutputHelper) =
+ Check.One(Helper.writeToXunit Config.Verbose testOutputHelper, property)
+ []
+ static member VerboseCheck(property:Property, testName:string, testOutputHelper: ITestOutputHelper) =
+ Check.One(testName, Helper.writeToXunit Config.Verbose testOutputHelper, property)
+ []
+ static member VerboseCheckThrowOnFailure(property:Property, testOutputHelper: ITestOutputHelper) =
+ Check.One(Helper.writeToXunitThrow Config.VerboseThrowOnFailure testOutputHelper,property)
\ No newline at end of file
diff --git a/src/FsCheck.Xunit.v3/FsCheck.Xunit.v3.fsproj b/src/FsCheck.Xunit.v3/FsCheck.Xunit.v3.fsproj
new file mode 100644
index 00000000..d12abff3
--- /dev/null
+++ b/src/FsCheck.Xunit.v3/FsCheck.Xunit.v3.fsproj
@@ -0,0 +1,28 @@
+
+
+
+ FsCheck.Xunit.v3
+ netstandard2.0
+ true
+ false
+
+ FsCheck.Xunit.v3 integrates FsCheck with xUnit.v3.NET by adding a PropertyAttribute that runs FsCheck tests, similar to xUnit.v3.NET's FactAttribute.
+
+ All the options normally available in vanilla FsCheck via configuration can be controlled via the PropertyAttribute.
+
+ $(PackageTags);xunit.v3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/FsCheck.Xunit.v3/PropertyAttribute.fs b/src/FsCheck.Xunit.v3/PropertyAttribute.fs
new file mode 100644
index 00000000..cf101169
--- /dev/null
+++ b/src/FsCheck.Xunit.v3/PropertyAttribute.fs
@@ -0,0 +1,274 @@
+namespace rec FsCheck.Xunit
+
+open System
+open System.Diagnostics
+open System.Reflection
+open System.Threading
+open System.Threading.Tasks
+open System.Collections.Generic
+
+open Xunit
+open Xunit.Sdk
+open Xunit.v3
+
+open FsCheck
+
+type PropertyFailedException =
+ inherit Exception
+ new (testResult:FsCheck.TestResult) = {
+ inherit Exception(sprintf "%s%s" Environment.NewLine (Runner.onFinishedToString "" testResult)) }
+ new (userMessage, innerException : exn) = {
+ inherit Exception(userMessage, innerException) }
+
+//can not be an anonymous type because of let mutable.
+type XunitRunner() =
+ let mutable result = None
+ member _.Result = result.Value
+ interface IRunner with
+ override _.OnStartFixture _ = ()
+ override _.OnArguments (ntest,args, every) =
+ every ntest args |> ignore
+ override _.OnShrink(args, everyShrink) =
+ everyShrink args |> ignore
+ override _.OnFinished(_ ,testResult) =
+ result <- Some testResult
+
+type internal PropertyConfig =
+ { MaxTest : Option
+ MaxRejected : Option
+ Replay : Option
+ Parallelism : Option
+ StartSize : Option
+ EndSize : Option
+ Verbose : Option
+ QuietOnSuccess : Option
+ Arbitrary : Type[] }
+
+[]
+module internal PropertyConfig =
+ let orElse y = function
+ | Some x -> Some x
+ | None -> y
+
+ let orDefault x y = defaultArg y x
+
+ let zero =
+ { MaxTest = None
+ MaxRejected = None
+ Replay = None
+ Parallelism = None
+ StartSize = None
+ EndSize = None
+ Verbose = None
+ QuietOnSuccess = None
+ Arbitrary = [||] }
+
+ let combine extra original =
+ { MaxTest = extra.MaxTest |> orElse original.MaxTest
+ MaxRejected = extra.MaxRejected |> orElse original.MaxRejected
+ Replay = extra.Replay |> orElse original.Replay
+ Parallelism = extra.Parallelism |> orElse original.Parallelism
+ StartSize = extra.StartSize |> orElse original.StartSize
+ EndSize = extra.EndSize |> orElse original.EndSize
+ Verbose = extra.Verbose |> orElse original.Verbose
+ QuietOnSuccess = extra.QuietOnSuccess |> orElse original.QuietOnSuccess
+ Arbitrary = Array.append extra.Arbitrary original.Arbitrary }
+
+ let parseReplay (str: string) =
+ //if someone sets this, we want it to throw if it fails
+ let split = str.Trim('(',')').Split([|","|], StringSplitOptions.RemoveEmptyEntries)
+ let seed = UInt64.Parse(split.[0])
+ let gamma = UInt64.Parse(split.[1])
+ let size = if split.Length = 3 then Some <| Convert.ToInt32(UInt32.Parse(split.[2])) else None
+ { Rnd = Rnd (seed,gamma); Size = size }
+
+ let toConfig (output : TestOutputHelper) propertyConfig =
+ Config.Default
+ .WithReplay(
+ propertyConfig.Replay
+ |> Option.map parseReplay
+ |> orElse Config.Default.Replay
+ )
+ .WithParallelRunConfig(
+ propertyConfig.Parallelism
+ |> Option.map (fun i -> { MaxDegreeOfParallelism = i })
+ |> orElse Config.Default.ParallelRunConfig
+ )
+ .WithMaxTest(propertyConfig.MaxTest |> orDefault Config.Default.MaxTest)
+ .WithMaxRejected(propertyConfig.MaxRejected |> orDefault Config.Default.MaxRejected)
+ .WithStartSize(propertyConfig.StartSize |> orDefault Config.Default.StartSize)
+ .WithEndSize(propertyConfig.EndSize |> orDefault Config.Default.EndSize)
+ .WithQuietOnSuccess(propertyConfig.QuietOnSuccess |> orDefault Config.Default.QuietOnSuccess)
+ .WithArbitrary(Seq.toList propertyConfig.Arbitrary)
+ .WithRunner(XunitRunner())
+ .WithEvery(
+ if propertyConfig.Verbose |> Option.exists id then
+ fun n args -> output.WriteLine (Config.Verbose.Every n args); ""
+ else
+ Config.Quick.Every
+ )
+ .WithEveryShrink(
+ if propertyConfig.Verbose |> Option.exists id then
+ fun args -> output.WriteLine (Config.Verbose.EveryShrink args); ""
+ else
+ Config.Quick.EveryShrink
+ )
+
+///Run this method as an FsCheck test.
+[]
+[)>]
+type public PropertyAttribute() =
+ inherit FactAttribute()
+ let mutable config = PropertyConfig.zero
+ let mutable replay = null
+ let mutable parallelism = -1
+ let mutable maxTest = -1
+ let mutable maxRejected = -1
+ let mutable startSize = -1
+ let mutable endSize = -1
+ let mutable verbose = false
+ let mutable arbitrary = [||]
+ let mutable quietOnSuccess = false
+
+ ///If set, the seed to use to start testing. Allows reproduction of previous runs. You can just paste
+ ///the tuple from the output window, e.g. 12344,12312 or (123,123).
+ ///Additionally, you can also specify a start size as the third parameter, e.g. 12344,12312,10 or (123,123,10).
+ member _.Replay with get() = replay and set(v) = replay <- v; config <- {config with Replay = if String.IsNullOrEmpty v then None else Some v}
+ ///If set, run tests in parallel. Useful for Task/async related work and heavy number crunching
+ ///Environment.ProcessorCount have been found to be useful default.
+ member _.Parallelism with get() = parallelism and set(v) = parallelism <- v; config <- {config with Parallelism = Some v}
+ ///The maximum number of tests that are run.
+ member _.MaxTest with get() = maxTest and set(v) = maxTest <- v; config <- {config with MaxTest = Some v}
+ ///The maximum number of tests where values are rejected, e.g. as the result of ==>
+ member _.MaxRejected with get() = maxRejected and set(v) = maxRejected <- v; config <- {config with MaxRejected = Some v}
+ ///The size to use for the first test.
+ member _.StartSize with get() = startSize and set(v) = startSize <- v; config <- {config with StartSize = Some v}
+ ///The size to use for the last test, when all the tests are passing. The size increases linearly between Start- and EndSize.
+ member _.EndSize with get() = endSize and set(v) = endSize <- v; config <- {config with EndSize = Some v}
+ ///Output all generated arguments.
+ member _.Verbose with get() = verbose and set(v) = verbose <- v; config <- {config with Verbose = Some v}
+ ///The Arbitrary instances to use for this test method. The Arbitrary instances
+ ///are merged in back to front order i.e. instances for the same generated type
+ ///at the front of the array will override those at the back.
+ member _.Arbitrary with get() = arbitrary and set(v) = arbitrary <- v; config <- {config with Arbitrary = v}
+ ///If set, suppresses the output from the test if the test is successful. This can be useful when running tests
+ ///with TestDriven.net, because TestDriven.net pops up the Output window in Visual Studio if a test fails; thus,
+ ///when conditioned to that behaviour, it's always a bit jarring to receive output from passing tests.
+ ///The default is false, which means that FsCheck will also output test results on success, but if set to true,
+ ///FsCheck will suppress output in the case of a passing test. This setting doesn't affect the behaviour in case of
+ ///test failures.
+ member _.QuietOnSuccess with get() = quietOnSuccess and set(v) = quietOnSuccess <- v; config <- {config with QuietOnSuccess = Some v}
+
+ member internal _.Config = config
+
+///Set common configuration for all properties within this class or module
+[]
+type public PropertiesAttribute() = inherit PropertyAttribute()
+
+/// The xUnit.v3 test runner for the PropertyAttribute that executes the test via FsCheck
+type PropertyTestCase =
+ inherit XunitTestCase
+
+ []
+ new() = { inherit XunitTestCase() }
+
+ new(testMethod, testCaseDisplayName, uniqueID, explicit, ?skipException, ?skipReason, ?skipType, ?skipUnless, ?skipWhen, ?traits, ?testMethodArguments, ?sourceFilePath, ?sourceLineNumber, ?timeout) =
+ let skipException = skipException |> Option.toObj
+ let skipReason = skipReason |> Option.toObj
+ let skipType = skipType |> Option.toObj
+ let skipUnless = skipUnless |> Option.toObj
+ let skipWhen = skipWhen |> Option.toObj
+ let traits = traits |> Option.toObj
+ let testMethodArguments = testMethodArguments |> Option.toObj
+ let sourceFilePath = sourceFilePath |> Option.toObj
+ let sourceLineNumber = sourceLineNumber |> Option.toNullable
+ let timeout = timeout |> Option.toNullable
+ { inherit XunitTestCase(testMethod, testCaseDisplayName, uniqueID, explicit, skipException, skipReason, skipType, skipUnless, skipWhen, traits, testMethodArguments, sourceFilePath, sourceLineNumber, timeout) }
+
+ static let combineAttributes (configs: (PropertyConfig option) list) =
+ configs
+ |> List.choose id
+ |> List.reduce(fun higherLevelAttribute lowerLevelAttribute ->
+ PropertyConfig.combine lowerLevelAttribute higherLevelAttribute)
+
+ member this.Init(output:TestOutputHelper) =
+ let getPropertiesOnDeclaringClasses (testClass: IXunitTestClass) =
+ [ let mutable current: Type = testClass.Class
+ while not (isNull current) do
+ yield current.GetTypeInfo().GetCustomAttributes()
+ |> Seq.tryHead
+ |> Option.map (fun attr -> attr.Config)
+ current <- current.DeclaringType]
+ |> List.rev
+
+ let getConfig (attr: PropertyAttribute) =
+ attr.Config
+
+ let config = combineAttributes [
+ yield this.TestMethod.TestClass.Class.Assembly.GetCustomAttributes() |> Seq.tryHead |> Option.map getConfig
+ yield! getPropertiesOnDeclaringClasses this.TestMethod.TestClass
+ yield this.TestMethod.Method.GetCustomAttributes() |> Seq.head |> getConfig |> Some]
+
+ { config with Arbitrary = config.Arbitrary }
+ |> PropertyConfig.toConfig output
+
+ member this.MakeInvoker() =
+ let runner =
+ { new XunitTestRunner() with
+ override _.InvokeTest(ctxt: XunitTestRunnerContext, instance) =
+ let target = instance |> Option.ofObj
+ let helper = TestOutputHelper()
+ helper.Initialize(ctxt.MessageBus, ctxt.Test)
+ use _cleanup = { new IDisposable with member _.Dispose() = helper.Uninitialize() }
+ let config = this.Init(helper)
+ let xunitRunner = if config.Runner :? XunitRunner then (config.Runner :?> XunitRunner) else XunitRunner()
+
+ let sw = Stopwatch.StartNew()
+ Check.Method(config, ctxt.TestMethod, ?target=target)
+ let ts = sw.Elapsed
+ match xunitRunner.Result with
+ | TestResult.Passed _ ->
+ helper.WriteLine(Runner.onFinishedToString "" xunitRunner.Result)
+ | TestResult.Exhausted _ ->
+ raise (PropertyFailedException(xunitRunner.Result))
+ | TestResult.Failed (testdata, originalArgs, shrunkArgs, Outcome.Failed e, originalSeed, lastSeed, lastSize) ->
+ let message = sprintf "%s%s" Environment.NewLine (Runner.onFailureToString "" testdata originalArgs shrunkArgs originalSeed lastSeed lastSize)
+ raise (PropertyFailedException(message, e))
+ | TestResult.Failed _ ->
+ raise (PropertyFailedException(xunitRunner.Result))
+
+ ValueTask<_>(ts)
+ }
+ { new XunitTestCaseRunner() with
+ override _.RunTest(x, test) =
+ runner.Run(test, x.MessageBus, x.ConstructorArguments, x.ExplicitOption, x.Aggregator.Clone(), x.CancellationTokenSource, x.BeforeAfterTestAttributes)
+ }
+
+ member this.TestExec(opts: ExplicitOption, buss: IMessageBus, ctorArgs: obj array, aggregator: ExceptionAggregator, cts: CancellationTokenSource) = async {
+ let invoker = this.MakeInvoker()
+ let! tests = aggregator.RunAsync(Func<_>(this.CreateTests), []).AsTask() |> Async.AwaitTask
+ return! invoker.Run(this, tests, buss, aggregator.Clone(), cts, this.TestCaseDisplayName, this.SkipReason, opts, ctorArgs).AsTask() |> Async.AwaitTask
+ }
+
+ interface ISelfExecutingXunitTestCase with
+ member this.Run (opts: ExplicitOption, messageBus: IMessageBus, ctorArgs: obj array, aggregator: ExceptionAggregator, cts: CancellationTokenSource) =
+ ValueTask(Async.StartImmediateAsTask(this.TestExec(opts, messageBus, ctorArgs, aggregator, cts)))
+
+
+/// xUnit.v3 test case discoverer to link the method with the PropertyAttribute to the PropertyTestCase
+/// so the test can be run via FsCheck.
+type PropertyDiscoverer(messageSink:IMessageSink) =
+
+ new () = PropertyDiscoverer(null)
+
+ member _.MessageSink = messageSink
+
+ interface IXunitTestCaseDiscoverer with
+ override this.Discover(discoveryOptions: ITestFrameworkDiscoveryOptions, testMethod: IXunitTestMethod, attr: IFactAttribute)=
+ let result = ResizeArray()
+ let struct (testCaseDisplayName, explicit, skipExceptions, skipReason, _, _, _, _, _, _, uniqueID, testMethod) =
+ TestIntrospectionHelper.GetTestCaseDetails(discoveryOptions, testMethod, attr, null, Nullable(), null, null)
+ let traits = TestIntrospectionHelper.GetTraits(testMethod, null)
+ result.Add(PropertyTestCase(testMethod, testCaseDisplayName, uniqueID, explicit, skipExceptions, skipReason, traits=traits))
+ ValueTask<_>(result :> IReadOnlyCollection)
+
diff --git a/src/FsCheck.Xunit.v3/Runner.fs b/src/FsCheck.Xunit.v3/Runner.fs
new file mode 100644
index 00000000..d2913ec2
--- /dev/null
+++ b/src/FsCheck.Xunit.v3/Runner.fs
@@ -0,0 +1,20 @@
+namespace FsCheck.Xunit
+
+open Xunit
+open FsCheck
+/// A runner for FsCheck (i.e. that you can use as Config.Runner) which outputs
+/// to Xunit's given ITestOutputHelper.
+/// For example, { Config.QuickThrowOnFailure with Runner = TestOutputRunner(output) }
+type TestOutputRunner(output: ITestOutputHelper) =
+ interface IRunner with
+ member _.OnStartFixture t =
+ output.WriteLine (Runner.onStartFixtureToString t)
+ member _.OnArguments (ntest, args, every) =
+ output.WriteLine (every ntest args)
+ member _.OnShrink(args, everyShrink) =
+ output.WriteLine (everyShrink args)
+ member _.OnFinished(name,testResult) =
+ let resultText = Runner.onFinishedToString name testResult
+ match testResult with
+ | TestResult.Passed _ -> resultText |> output.WriteLine
+ | _ -> failwithf "%s" resultText
\ No newline at end of file
diff --git a/src/FsCheck.Xunit.v3/paket.template b/src/FsCheck.Xunit.v3/paket.template
new file mode 100644
index 00000000..740e4d8e
--- /dev/null
+++ b/src/FsCheck.Xunit.v3/paket.template
@@ -0,0 +1,32 @@
+type file
+id
+ FsCheck.Xunit
+authors
+ Kurt Schelfthout and contributors
+owners
+ Kurt Schelfthout and contributors
+projectUrl
+ https://github.com/fsharp/FsCheck
+iconUrl
+ https://raw.githubusercontent.com/fscheck/FsCheck/master/docs/files/img/logo.png
+licenseUrl
+ https://github.com/fsharp/FsCheck/blob/master/License.txt
+requireLicenseAcceptance
+ false
+copyright
+ Copyright 2017
+tags
+ test testing random fscheck quickcheck xunit xunit.net
+summary
+ Integrates FsCheck with xUnit.NET
+description
+ FsCheck.Xunit integrates FsCheck with xUnit.NET by adding a PropertyAttribute that runs FsCheck tests, similar to xUnit.NET's FactAttribute.
+
+ All the options normally available in vanilla FsCheck via configuration can be controlled via the PropertyAttribute.
+dependencies
+ FsCheck = CURRENTVERSION
+ xunit.extensibility.execution ~> 2.4
+files
+ bin/Release/netstandard2.0/FsCheck.Xunit.dll ==> lib/netstandard2.0
+ bin/Release/netstandard2.0/FsCheck.Xunit.xml ==> lib/netstandard2.0
+
diff --git a/tests/FsCheck.Test.v3/App.config b/tests/FsCheck.Test.v3/App.config
new file mode 100644
index 00000000..7f4e1349
--- /dev/null
+++ b/tests/FsCheck.Test.v3/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/tests/FsCheck.Test.v3/FsCheck.Test.v3.fsproj b/tests/FsCheck.Test.v3/FsCheck.Test.v3.fsproj
new file mode 100644
index 00000000..846ab5a1
--- /dev/null
+++ b/tests/FsCheck.Test.v3/FsCheck.Test.v3.fsproj
@@ -0,0 +1,28 @@
+
+
+
+ FsCheck.Test.v3
+ net8.0
+ true
+ false
+ true
+ true
+
+
+ DEBUG
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/FsCheck.Test.v3/Fscheck.XUnit/PropertyAttributeTests.fs b/tests/FsCheck.Test.v3/Fscheck.XUnit/PropertyAttributeTests.fs
new file mode 100644
index 00000000..3eecb53d
--- /dev/null
+++ b/tests/FsCheck.Test.v3/Fscheck.XUnit/PropertyAttributeTests.fs
@@ -0,0 +1,94 @@
+namespace Fscheck.Test.FsCheck.XUnit.PropertyAttribute
+
+open System.Threading.Tasks
+open FsCheck.FSharp
+open FsCheck.Xunit
+open Xunit
+
+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
+
+
+module ``when type implements IAsyncLifetime`` =
+ type Issue657() =
+
+ let mutable executed = false;
+
+ interface IAsyncLifetime with
+ member _.InitializeAsync() =
+
+ async {
+ do! Async.Sleep 300
+ executed <- true
+ return ()
+ }
+ |> Async.StartAsTask
+ :> Task
+ |> ValueTask
+
+ member _.DisposeAsync() = ValueTask()
+
+ []
+ member this.``then InitializeAsync() is invoked``() =
+ executed = true