From 8171db6a4e7ba60115b0cc31e9aeeb8f749f680a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 22:15:07 +0000 Subject: [PATCH 1/4] Initial plan From fb915d48db6cd7bfeafc1dfd7eaefa15b30972f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 22:20:29 +0000 Subject: [PATCH 2/4] Use TestContext.WriteLine to support NUnit custom formatters Co-authored-by: kurtschelfthout <164917+kurtschelfthout@users.noreply.github.com> --- src/FsCheck.NUnit/FsCheckPropertyAttribute.fs | 6 ++--- .../FsCheck.NUnit/PropertyAttributeTests.fs | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/FsCheck.NUnit/FsCheckPropertyAttribute.fs b/src/FsCheck.NUnit/FsCheckPropertyAttribute.fs index 8198048b..182ddc48 100644 --- a/src/FsCheck.NUnit/FsCheckPropertyAttribute.fs +++ b/src/FsCheck.NUnit/FsCheckPropertyAttribute.fs @@ -24,9 +24,9 @@ type private NunitRunner() = // doesn't look pretty in the cases where it turns out that there's truly nothing to write (e.g. // when QuietOnSuccess is true, and the Property passed. if not (String.IsNullOrWhiteSpace argsForOutput) then - printfn "%s" argsForOutput + TestContext.WriteLine("{0}", argsForOutput) override __.OnShrink(args, everyShrink) = - printfn "%s" (everyShrink args) + TestContext.WriteLine("{0}", everyShrink args) override __.OnFinished(_,testResult) = result <- Some testResult @@ -283,7 +283,7 @@ and FsCheckTestMethod(mi : IMethodInfo, parentSuite : Test) = match testRunner.Result with | TestResult.Passed _ -> if not config.QuietOnSuccess then - printfn "%s" (Runner.onFinishedToString "" testRunner.Result) + TestContext.WriteLine("{0}", Runner.onFinishedToString "" testRunner.Result) testResult.SetResult(ResultState(TestStatus.Passed)) | TestResult.Exhausted _ -> let msg = sprintf "Exhausted: %s" (Runner.onFinishedToString "" testRunner.Result) diff --git a/tests/FsCheck.Test/FsCheck.NUnit/PropertyAttributeTests.fs b/tests/FsCheck.Test/FsCheck.NUnit/PropertyAttributeTests.fs index b81c7c0e..5ce557ff 100644 --- a/tests/FsCheck.Test/FsCheck.NUnit/PropertyAttributeTests.fs +++ b/tests/FsCheck.Test/FsCheck.NUnit/PropertyAttributeTests.fs @@ -25,6 +25,30 @@ module ResultStateExceptionHandlingTest = NUnit.Framework.Assert.Pass() } +module TestContextFormatterTests = + type CustomType(value: int) = + member _.Value = value + override _.ToString() = $"CustomType({value})" + + [] + let setupFormatter() = + // Register a custom formatter for CustomType + TestContext.AddFormatter(fun (ct: obj) -> + match ct with + | :? CustomType as custom -> $"[CUSTOM:{custom.Value}]" + | _ -> ct.ToString()) + + [] + let ``should use TestContext formatter for custom types`` (value: int) = + // Create a custom type instance + let customValue = CustomType(abs value % 100) + + // Write it to TestContext - this should use the custom formatter + TestContext.WriteLine("Testing with value: {0}", customValue) + + // The property always passes, but we're testing that the formatter is used + true + module ResultOutputTests = [] module TestModule = From 0bce764128b97609761dc50d59c9310d6abc94b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 22:24:31 +0000 Subject: [PATCH 3/4] Remove unnecessary format strings and improve test documentation Co-authored-by: kurtschelfthout <164917+kurtschelfthout@users.noreply.github.com> --- src/FsCheck.NUnit/FsCheckPropertyAttribute.fs | 6 +++--- .../FsCheck.Test/FsCheck.NUnit/PropertyAttributeTests.fs | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/FsCheck.NUnit/FsCheckPropertyAttribute.fs b/src/FsCheck.NUnit/FsCheckPropertyAttribute.fs index 182ddc48..630ab251 100644 --- a/src/FsCheck.NUnit/FsCheckPropertyAttribute.fs +++ b/src/FsCheck.NUnit/FsCheckPropertyAttribute.fs @@ -24,9 +24,9 @@ type private NunitRunner() = // doesn't look pretty in the cases where it turns out that there's truly nothing to write (e.g. // when QuietOnSuccess is true, and the Property passed. if not (String.IsNullOrWhiteSpace argsForOutput) then - TestContext.WriteLine("{0}", argsForOutput) + TestContext.WriteLine(argsForOutput) override __.OnShrink(args, everyShrink) = - TestContext.WriteLine("{0}", everyShrink args) + TestContext.WriteLine(everyShrink args) override __.OnFinished(_,testResult) = result <- Some testResult @@ -283,7 +283,7 @@ and FsCheckTestMethod(mi : IMethodInfo, parentSuite : Test) = match testRunner.Result with | TestResult.Passed _ -> if not config.QuietOnSuccess then - TestContext.WriteLine("{0}", Runner.onFinishedToString "" testRunner.Result) + TestContext.WriteLine(Runner.onFinishedToString "" testRunner.Result) testResult.SetResult(ResultState(TestStatus.Passed)) | TestResult.Exhausted _ -> let msg = sprintf "Exhausted: %s" (Runner.onFinishedToString "" testRunner.Result) diff --git a/tests/FsCheck.Test/FsCheck.NUnit/PropertyAttributeTests.fs b/tests/FsCheck.Test/FsCheck.NUnit/PropertyAttributeTests.fs index 5ce557ff..f4bcb162 100644 --- a/tests/FsCheck.Test/FsCheck.NUnit/PropertyAttributeTests.fs +++ b/tests/FsCheck.Test/FsCheck.NUnit/PropertyAttributeTests.fs @@ -38,15 +38,18 @@ module TestContextFormatterTests = | :? CustomType as custom -> $"[CUSTOM:{custom.Value}]" | _ -> ct.ToString()) - [] - let ``should use TestContext formatter for custom types`` (value: int) = + [] + let ``should allow TestContext.WriteLine within property tests`` (value: int) = // Create a custom type instance let customValue = CustomType(abs value % 100) // Write it to TestContext - this should use the custom formatter + // This demonstrates that users can use TestContext.WriteLine with custom formatters + // within their FsCheck property tests TestContext.WriteLine("Testing with value: {0}", customValue) - // The property always passes, but we're testing that the formatter is used + // The property always passes, but we're testing that TestContext is accessible + // and custom formatters work when used directly true module ResultOutputTests = From a5864dfa74eecf747ada02cfb05283121602d718 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 07:59:42 +0000 Subject: [PATCH 4/4] Add ValueFormatter API for custom value formatting in FsCheck.NUnit output - Introduce public ValueFormatter class with AddFormatter method - Format test arguments and shrink values using registered custom formatters - Update test to demonstrate custom formatter usage - Custom formatters work with verbose output during test execution Co-authored-by: kurtschelfthout <164917+kurtschelfthout@users.noreply.github.com> --- src/FsCheck.NUnit/FsCheckPropertyAttribute.fs | 39 ++++++++++++++++++- .../FsCheck.NUnit/PropertyAttributeTests.fs | 29 +++++++------- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/src/FsCheck.NUnit/FsCheckPropertyAttribute.fs b/src/FsCheck.NUnit/FsCheckPropertyAttribute.fs index 630ab251..32d2f83f 100644 --- a/src/FsCheck.NUnit/FsCheckPropertyAttribute.fs +++ b/src/FsCheck.NUnit/FsCheckPropertyAttribute.fs @@ -10,9 +10,34 @@ open NUnit.Framework open NUnit.Framework.Interfaces open NUnit.Framework.Internal +//Helper type to allow users to register custom formatters for FsCheck test output +type ValueFormatter private () = + static let mutable customFormatters = System.Collections.Generic.Dictionary string>() + + /// Register a custom formatter for a specific type. The formatter will be used when outputting + /// values of that type in FsCheck property test results. + static member AddFormatter<'T>(formatter: obj -> string) = + customFormatters.[typeof<'T>] <- formatter + + static member internal TryFormat(value: obj) = + if isNull value then + Some "null" + else + let valueType = value.GetType() + match customFormatters.TryGetValue(valueType) with + | true, formatter -> Some (formatter value) + | false, _ -> None + //can not be an anonymous type because of let mutable. type private NunitRunner() = let mutable result = None + + // Helper function to format a value using registered custom formatters + let formatValue (value: obj) = + match ValueFormatter.TryFormat(value) with + | Some formatted -> formatted + | None -> value.ToString() + member __.Result = result.Value interface IRunner with override __.OnStartFixture _ = () @@ -24,9 +49,19 @@ type private NunitRunner() = // doesn't look pretty in the cases where it turns out that there's truly nothing to write (e.g. // when QuietOnSuccess is true, and the Property passed. if not (String.IsNullOrWhiteSpace argsForOutput) then - TestContext.WriteLine(argsForOutput) + // Output the test number + TestContext.WriteLine("{0}:", ntest) + // Output each argument individually using custom formatters if registered + for arg in args do + TestContext.WriteLine(formatValue arg) + TestContext.WriteLine("") override __.OnShrink(args, everyShrink) = - TestContext.WriteLine(everyShrink args) + let shrinkOutput = everyShrink args + if not (String.IsNullOrWhiteSpace shrinkOutput) then + // Output each shrunk argument individually using custom formatters if registered + for arg in args do + TestContext.WriteLine(formatValue arg) + TestContext.WriteLine("") override __.OnFinished(_,testResult) = result <- Some testResult diff --git a/tests/FsCheck.Test/FsCheck.NUnit/PropertyAttributeTests.fs b/tests/FsCheck.Test/FsCheck.NUnit/PropertyAttributeTests.fs index f4bcb162..bf414beb 100644 --- a/tests/FsCheck.Test/FsCheck.NUnit/PropertyAttributeTests.fs +++ b/tests/FsCheck.Test/FsCheck.NUnit/PropertyAttributeTests.fs @@ -30,27 +30,26 @@ module TestContextFormatterTests = member _.Value = value override _.ToString() = $"CustomType({value})" + // Arbitrary instance for CustomType + type CustomTypeArb = + static member CustomType() = + ArbMap.defaults.ArbFor() + |> Arb.convert CustomType (fun ct -> ct.Value) + [] let setupFormatter() = - // Register a custom formatter for CustomType - TestContext.AddFormatter(fun (ct: obj) -> + // Register a custom formatter for CustomType using FsCheck.NUnit.ValueFormatter + ValueFormatter.AddFormatter(fun (ct: obj) -> match ct with | :? CustomType as custom -> $"[CUSTOM:{custom.Value}]" | _ -> ct.ToString()) - [] - let ``should allow TestContext.WriteLine within property tests`` (value: int) = - // Create a custom type instance - let customValue = CustomType(abs value % 100) - - // Write it to TestContext - this should use the custom formatter - // This demonstrates that users can use TestContext.WriteLine with custom formatters - // within their FsCheck property tests - TestContext.WriteLine("Testing with value: {0}", customValue) - - // The property always passes, but we're testing that TestContext is accessible - // and custom formatters work when used directly - true + [ |])>] + let ``should use custom formatter for test arguments`` (ct: CustomType) = + // This test demonstrates that custom formatters are used for console output during verbose mode. + // When you run this test with verbose=true, the console will show [CUSTOM:x] instead of CustomType(x) + true // Pass so we can see the formatted output in verbose mode + module ResultOutputTests = []