From 87f450e81f75febaa7022f9e084c89bb1ee3fdd8 Mon Sep 17 00:00:00 2001 From: Johannes Sandgren Date: Wed, 28 May 2025 17:14:36 +0200 Subject: [PATCH 1/8] Implement xUnit testcases for xUnit.v3 Add simple tests --- Directory.Packages.props | 2 + FsCheck.sln | 12 + src/FsCheck.Xunit.v3/AssemblyInfo.fs | 23 ++ src/FsCheck.Xunit.v3/CheckExtensions.fs | 62 ++++ src/FsCheck.Xunit.v3/FsCheck.Xunit.v3.fsproj | 28 ++ src/FsCheck.Xunit.v3/PropertyAttribute.fs | 264 ++++++++++++++++++ src/FsCheck.Xunit.v3/Runner.fs | 20 ++ src/FsCheck.Xunit.v3/paket.template | 32 +++ tests/FsCheck.Test.v3/App.config | 6 + tests/FsCheck.Test.v3/FsCheck.Test.v3.fsproj | 28 ++ .../Fscheck.XUnit/PropertyAttributeTests.fs | 94 +++++++ 11 files changed, 571 insertions(+) create mode 100644 src/FsCheck.Xunit.v3/AssemblyInfo.fs create mode 100644 src/FsCheck.Xunit.v3/CheckExtensions.fs create mode 100644 src/FsCheck.Xunit.v3/FsCheck.Xunit.v3.fsproj create mode 100644 src/FsCheck.Xunit.v3/PropertyAttribute.fs create mode 100644 src/FsCheck.Xunit.v3/Runner.fs create mode 100644 src/FsCheck.Xunit.v3/paket.template create mode 100644 tests/FsCheck.Test.v3/App.config create mode 100644 tests/FsCheck.Test.v3/FsCheck.Test.v3.fsproj create mode 100644 tests/FsCheck.Test.v3/Fscheck.XUnit/PropertyAttributeTests.fs diff --git a/Directory.Packages.props b/Directory.Packages.props index 543f6d23..3f2903ed 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -37,6 +37,8 @@ + + \ No newline at end of file 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/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..684c32e8 --- /dev/null +++ b/src/FsCheck.Xunit.v3/PropertyAttribute.fs @@ -0,0 +1,264 @@ +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) = + { inherit XunitTestCase(testMethod, testCaseDisplayName, uniqueID, explicit, skipException, skipReason) } + + 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) + + result.Add(PropertyTestCase(testMethod, testCaseDisplayName, uniqueID, explicit, skipExceptions, skipReason)) + 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 From 5609f60e3e8e3f0be40b116259f81ca8d59d4308 Mon Sep 17 00:00:00 2001 From: Kurt Schelfthout Date: Tue, 3 Jun 2025 22:06:52 +0100 Subject: [PATCH 2/8] New release. --- FsCheck Release Notes.md | 4 ++++ build.fsx | 9 ++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/FsCheck Release Notes.md b/FsCheck Release Notes.md index a258d3f5..e084c218 100644 --- a/FsCheck Release Notes.md +++ b/FsCheck Release Notes.md @@ -1,3 +1,7 @@ +### 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/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 From ef50745db81b78ef8e145bf96409bbe390886f08 Mon Sep 17 00:00:00 2001 From: steve-isaac Date: Tue, 8 Jul 2025 11:35:43 +0100 Subject: [PATCH 3/8] Discover traits for FsCheck.Xunit.v3 Property tests --- src/FsCheck.Xunit.v3/PropertyAttribute.fs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/FsCheck.Xunit.v3/PropertyAttribute.fs b/src/FsCheck.Xunit.v3/PropertyAttribute.fs index 684c32e8..2801768c 100644 --- a/src/FsCheck.Xunit.v3/PropertyAttribute.fs +++ b/src/FsCheck.Xunit.v3/PropertyAttribute.fs @@ -175,6 +175,19 @@ type PropertyTestCase = new(testMethod, testCaseDisplayName, uniqueID, explicit, skipException, skipReason) = { inherit XunitTestCase(testMethod, testCaseDisplayName, uniqueID, explicit, skipException, skipReason) } + new(testMethod, testCaseDisplayName, uniqueID, explicit, ?skipException, ?skipReason, ?skipType, ?skipUnless, ?skipWhen, ?traits, ?testMethodArguments, ?sourceFilePath, ?sourceLineNumber, ?timeout) = + let skipException = Option.toObj skipException + let skipReason = Option.toObj skipReason + let skipType = Option.toObj skipType + let skipUnless = Option.toObj skipUnless + let skipWhen = Option.toObj skipWhen + let traits = Option.toObj traits + let testMethodArguments = Option.toObj testMethodArguments + let sourceFilePath = Option.toObj sourceFilePath + let sourceLineNumber = Option.toNullable sourceLineNumber + let timeout = Option.toNullable timeout + { 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 @@ -258,7 +271,7 @@ type PropertyDiscoverer(messageSink:IMessageSink) = let result = ResizeArray() let struct (testCaseDisplayName, explicit, skipExceptions, skipReason, _, _, _, _, uniqueID, testMethod) = TestIntrospectionHelper.GetTestCaseDetails(discoveryOptions, testMethod, attr) - - result.Add(PropertyTestCase(testMethod, testCaseDisplayName, uniqueID, explicit, skipExceptions, skipReason)) + let traits = TestIntrospectionHelper.GetTraits(testMethod, null) + result.Add(PropertyTestCase(testMethod, testCaseDisplayName, uniqueID, explicit, skipExceptions, skipReason, traits=traits)) ValueTask<_>(result :> IReadOnlyCollection) From ea205380a40b683f3f86e24b56c4c17ca0de074a Mon Sep 17 00:00:00 2001 From: steveisaac Date: Tue, 8 Jul 2025 13:24:48 +0100 Subject: [PATCH 4/8] Replace old constructor --- src/FsCheck.Xunit.v3/PropertyAttribute.fs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/FsCheck.Xunit.v3/PropertyAttribute.fs b/src/FsCheck.Xunit.v3/PropertyAttribute.fs index 2801768c..6f87486d 100644 --- a/src/FsCheck.Xunit.v3/PropertyAttribute.fs +++ b/src/FsCheck.Xunit.v3/PropertyAttribute.fs @@ -172,9 +172,6 @@ type PropertyTestCase = [] new() = { inherit XunitTestCase() } - new(testMethod, testCaseDisplayName, uniqueID, explicit, skipException, skipReason) = - { inherit XunitTestCase(testMethod, testCaseDisplayName, uniqueID, explicit, skipException, skipReason) } - new(testMethod, testCaseDisplayName, uniqueID, explicit, ?skipException, ?skipReason, ?skipType, ?skipUnless, ?skipWhen, ?traits, ?testMethodArguments, ?sourceFilePath, ?sourceLineNumber, ?timeout) = let skipException = Option.toObj skipException let skipReason = Option.toObj skipReason From 556a11d2eb7123171355723cd3819e95a1cd24e9 Mon Sep 17 00:00:00 2001 From: steveisaac Date: Tue, 8 Jul 2025 13:33:18 +0100 Subject: [PATCH 5/8] Formatting --- src/FsCheck.Xunit.v3/PropertyAttribute.fs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/FsCheck.Xunit.v3/PropertyAttribute.fs b/src/FsCheck.Xunit.v3/PropertyAttribute.fs index 6f87486d..0cea072e 100644 --- a/src/FsCheck.Xunit.v3/PropertyAttribute.fs +++ b/src/FsCheck.Xunit.v3/PropertyAttribute.fs @@ -173,17 +173,17 @@ type PropertyTestCase = new() = { inherit XunitTestCase() } new(testMethod, testCaseDisplayName, uniqueID, explicit, ?skipException, ?skipReason, ?skipType, ?skipUnless, ?skipWhen, ?traits, ?testMethodArguments, ?sourceFilePath, ?sourceLineNumber, ?timeout) = - let skipException = Option.toObj skipException - let skipReason = Option.toObj skipReason - let skipType = Option.toObj skipType - let skipUnless = Option.toObj skipUnless - let skipWhen = Option.toObj skipWhen - let traits = Option.toObj traits - let testMethodArguments = Option.toObj testMethodArguments - let sourceFilePath = Option.toObj sourceFilePath - let sourceLineNumber = Option.toNullable sourceLineNumber - let timeout = Option.toNullable timeout - { inherit XunitTestCase(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 From 76899cfa044456ca21a40154f81c162df37115ca Mon Sep 17 00:00:00 2001 From: Hyogeol Lee Date: Thu, 21 Aug 2025 17:25:04 +0900 Subject: [PATCH 6/8] - Update xunit.v3.extensibility.core to v3.0.1 - Fix build error --- Directory.Packages.props | 4 ++-- src/FsCheck.Xunit.v3/PropertyAttribute.fs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 3f2903ed..cba68949 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -37,8 +37,8 @@ - - + + \ No newline at end of file diff --git a/src/FsCheck.Xunit.v3/PropertyAttribute.fs b/src/FsCheck.Xunit.v3/PropertyAttribute.fs index 0cea072e..cf101169 100644 --- a/src/FsCheck.Xunit.v3/PropertyAttribute.fs +++ b/src/FsCheck.Xunit.v3/PropertyAttribute.fs @@ -266,8 +266,8 @@ type PropertyDiscoverer(messageSink:IMessageSink) = 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) + 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) From f442f0691ce4a1ae429a1a8afc3410160cc9c8f6 Mon Sep 17 00:00:00 2001 From: Kurt Schelfthout Date: Sun, 24 Aug 2025 16:55:23 +0100 Subject: [PATCH 7/8] Update release notes. --- FsCheck Release Notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FsCheck Release Notes.md b/FsCheck Release Notes.md index e084c218..ad18154a 100644 --- a/FsCheck Release Notes.md +++ b/FsCheck Release Notes.md @@ -1,3 +1,7 @@ +### 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) From 529af43740359435c4ac38c059f85eab4561dd6e Mon Sep 17 00:00:00 2001 From: Kurt Schelfthout Date: Sun, 24 Aug 2025 16:57:06 +0100 Subject: [PATCH 8/8] Bump version to 3.3.1