⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="Microsoft.NETCore.Platforms" Version="5.0.2" />
<PackageVersion Include="Microsoft.NETCore.Targets" Version="5.0" />
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" />
<PackageVersion Include="Microsoft.TestPlatform.ObjectModel" Version="16.11" />
<PackageVersion Include="MSTest.TestAdapter" Version="3.7.0" />
<PackageVersion Include="MSTest.TestFramework" Version="3.7.0" />
Expand Down
16 changes: 8 additions & 8 deletions src/FsCheck.Xunit/CheckExtensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,26 @@ module private Helper =
let private runner (testOutputHelper: ITestOutputHelper) =
{ new IRunner with
member __.OnStartFixture t =
Runner.onStartFixtureToString t |> testOutputHelper.WriteLine
Runner.onStartFixtureToString t |> Helpers.safeWriteLine testOutputHelper
member __.OnArguments (ntest,args, every) =
every ntest args |> testOutputHelper.WriteLine
every ntest args |> Helpers.safeWriteLine testOutputHelper
member __.OnShrink(args, everyShrink) =
everyShrink args |> testOutputHelper.WriteLine
everyShrink args |> Helpers.safeWriteLine testOutputHelper
member __.OnFinished(name,testResult) =
Runner.onFinishedToString name testResult |> testOutputHelper.WriteLine
Runner.onFinishedToString name testResult |> Helpers.safeWriteLine testOutputHelper
}

let private throwingRunner (testOutputHelper: ITestOutputHelper) =
{ new IRunner with
member __.OnStartFixture t =
testOutputHelper.WriteLine (Runner.onStartFixtureToString t)
Helpers.safeWriteLine testOutputHelper (Runner.onStartFixtureToString t)
member __.OnArguments (ntest,args, every) =
testOutputHelper.WriteLine (every ntest args)
Helpers.safeWriteLine testOutputHelper (every ntest args)
member __.OnShrink(args, everyShrink) =
testOutputHelper.WriteLine (everyShrink args)
Helpers.safeWriteLine testOutputHelper (everyShrink args)
member __.OnFinished(name,testResult) =
match testResult with
| TestResult.Passed _ -> testOutputHelper.WriteLine (Runner.onFinishedToString name testResult)
| TestResult.Passed _ -> Helpers.safeWriteLine testOutputHelper (Runner.onFinishedToString name testResult)
| _ -> failwithf "%s" (Runner.onFinishedToString name testResult)
}

Expand Down
1 change: 1 addition & 0 deletions src/FsCheck.Xunit/FsCheck.Xunit.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="AssemblyInfo.fs"/>
<Compile Include="Helpers.fs"/>
<Compile Include="Runner.fs"/>
<Compile Include="PropertyAttribute.fs"/>
<Compile Include="CheckExtensions.fs"/>
Expand Down
19 changes: 19 additions & 0 deletions src/FsCheck.Xunit/Helpers.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace FsCheck.Xunit

open System
open Xunit.Abstractions

module internal Helpers =
/// <summary>
/// Safely writes to a TestOutputHelper, handling cases where the test may have completed
/// and the helper is no longer active. This prevents InvalidOperationException when
/// closures that capture the TestOutputHelper are called after the test lifetime ends.
/// </summary>
let safeWriteLine (output: ITestOutputHelper) (message: string) =
try
output.WriteLine(message)
with
| :? InvalidOperationException ->
// Test has completed, TestOutputHelper is no longer active
// Silently ignore as this is expected when closures outlive test lifetime
()
4 changes: 2 additions & 2 deletions src/FsCheck.Xunit/PropertyAttribute.fs
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,13 @@ module internal PropertyConfig =
.WithRunner(XunitRunner())
.WithEvery(
if propertyConfig.Verbose |> Option.exists id then
fun n args -> output.WriteLine (Config.Verbose.Every n args); ""
fun n args -> Helpers.safeWriteLine output (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); ""
fun args -> Helpers.safeWriteLine output (Config.Verbose.EveryShrink args); ""
else
Config.Quick.EveryShrink
)
Expand Down
9 changes: 5 additions & 4 deletions src/FsCheck.Xunit/Runner.fs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
namespace FsCheck.Xunit

open FsCheck
open System

/// 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: Xunit.Abstractions.ITestOutputHelper) =
interface IRunner with
member _.OnStartFixture t =
output.WriteLine (Runner.onStartFixtureToString t)
Helpers.safeWriteLine output (Runner.onStartFixtureToString t)
member _.OnArguments (ntest, args, every) =
output.WriteLine (every ntest args)
Helpers.safeWriteLine output (every ntest args)
member _.OnShrink(args, everyShrink) =
output.WriteLine (everyShrink args)
Helpers.safeWriteLine output (everyShrink args)
member _.OnFinished(name,testResult) =
let resultText = Runner.onFinishedToString name testResult
match testResult with
| TestResult.Passed _ -> resultText |> output.WriteLine
| TestResult.Passed _ -> resultText |> Helpers.safeWriteLine output
| _ -> failwithf "%s" resultText
33 changes: 33 additions & 0 deletions tests/FsCheck.Test/Fscheck.XUnit/PropertyAttributeTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,36 @@ module ``when type implements IAsyncLifetime`` =
[<Property(MaxTest = 1)>]
member this.``then InitializeAsync() is invoked``() =
executed = true

/// Reproduction test for GitHub issue: Lifetime problem with Xunit: InvalidOperationException: There is no currently active test.
/// This test class verifies that mixing Property and Fact tests with ITestOutputHelper doesn't cause lifetime issues.
module ``when mixing Property and Fact tests with ITestOutputHelper`` =
open Xunit.Abstractions

type TestOutputHelperLifetimeTests(output: ITestOutputHelper) =

[<Property>]
member _.``Property test with parameter writes to output`` (x: int) =
output.WriteLine($"Property test with parameter: {x}")
true

[<Fact>]
member _.``Fact test writes to output`` () =
output.WriteLine("Fact test")

[<Property>]
member _.``Property test with string parameter writes to output`` (s: string) =
let str = if isNull s then "null" else s
output.WriteLine($"Property test with string: {str}")
true

[<Fact>]
member _.``Another fact test writes to output`` () =
output.WriteLine("Another fact test")

/// This test specifically exercises the Every and EveryShrink callbacks by enabling Verbose mode.
/// These callbacks capture the TestOutputHelper in closures, which was the root cause of the lifetime issue.
[<Property(Verbose = true, MaxTest = 5)>]
member _.``Verbose property test exercises Every and EveryShrink callbacks`` (x: int) (y: int) =
output.WriteLine($"Verbose mode test: x={x}, y={y}")
true