From f4b4df15d294a0a7aeb7230d2fb9b92afb3f9ef5 Mon Sep 17 00:00:00 2001 From: metacoder_FEFF Date: Tue, 28 Apr 2026 16:06:47 +0000 Subject: [PATCH 01/11] CI: minor fix --- .github/dependabot.yml | 6 ++++-- .github/workflows/test.yml | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ea0502d..23c9fa7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,7 +8,8 @@ updates: - package-ecosystem: "github-actions" directories: [".github/workflows", ".github/actions/**"] schedule: - interval: "daily" + # interval: "daily" + interval: "weekly" groups: github-actions: patterns: @@ -17,7 +18,8 @@ updates: - package-ecosystem: "nuget" directory: "/" # Location of solution/project files schedule: - interval: "daily" + # interval: "daily" + interval: "weekly" groups: nuget: patterns: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d15c0e8..61982d5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,9 +2,11 @@ name: Test on: push: - branches: [ main ] - pull_request: - branches: [ main ] + branches: + - '**' + # branches: [ main ] + # pull_request: + # branches: [ main ] permissions: contents: read From db3842dacf60512efe14a04d917b8e6aca4678e7 Mon Sep 17 00:00:00 2001 From: metacoder_FEFF Date: Wed, 29 Apr 2026 07:35:32 +0000 Subject: [PATCH 02/11] fx docs --- .../02.advanced/fixture-parameterization.md | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/docs/articles/02.advanced/fixture-parameterization.md b/docs/articles/02.advanced/fixture-parameterization.md index bffc074..caaf42a 100644 --- a/docs/articles/02.advanced/fixture-parameterization.md +++ b/docs/articles/02.advanced/fixture-parameterization.md @@ -1,16 +1,20 @@ # Fixture Parameterization -Fixture parameterization allows you to create fixture instances with concrete argument values by defining options types that supply configuration data. This pattern is essential when you need the same fixture to behave differently across tests based on custom parameters. - ## Overview -While standard fixtures are created with default behavior, parameterized fixtures accept configuration through a dedicated options type. This options type is itself a fixture, enabling dependency injection and test isolation. +Fixture parameterization lets you pass values to a fixture that are required but not known when the fixture is created. Instead of hardcoding configuration inside the fixture, you supply it from the outside. + +This is useful when: + +- Different tests need the same fixture to behave differently +- Configuration values (connection strings, endpoints, flags) are decided at test time, not when the fixture is written +- The same fixture is reused across test suites with different settings -The pattern consists of three components: +The pattern uses three pieces: -1. **Options Interface** - Defines the contract for configuration data -2. **Options Fixture** - Implements the interface with concrete values -3. **Parameterized Fixture** - Consumes the options fixture via constructor injection of generic type parameter +1. **Options Interface** — defines what values the fixture needs +2. **Options Fixture** — provides the actual values +3. **Parameterized Fixture** — receives the values through its constructor ## Example: TmpDatabaseNameFixture @@ -91,6 +95,25 @@ The generic type parameter `OptionsFixture` tells `TmpDatabaseNameFixture` which - **Reusability** - Same parameterized fixture works with different options - **Clarity** - Options type documents required configuration +## Selecting Configuration Method + +The library provides two distinct ways to configure fixtures. Choose based on whether the configuration varies between tests or is controlled externally. + +| Aspect | Parameterization Pattern (this article) | Options Pattern ([configuring-fixtures.md](configuring-fixtures.md)) | +|--------|----------------------------------------|---------------------------------------------------------------------| +| **Mechanism** | Generic type parameter + options fixture interface | `Microsoft.Extensions.Options` bound to configuration (e.g., environment variables) | +| **When to use** | Different tests or test suites need different behavior for the same fixture | Global or environment-specific settings (CI, debugging, local overrides) | +| **Discovery** | Compile-time via generic constraint | Runtime via `IOptions` and `IFixtureRegistrar` | +| **Flexibility** | Per-test-suite variation by swapping the options fixture type | Global change without recompiling tests | +| **Example scenario** | One test suite uses database `A`, another uses database `B`, via `TmpDatabaseNameFixture` vs `TmpDatabaseNameFixture` | Skipping temp directory cleanup on CI by setting `TmpDirectoryFixture__DisposeType=Skip` | + +### Decision Guide + +- **Use parameterization** when the fixture must behave differently across test classes or collections and the decision is made at design time. This keeps configuration type-safe and visible in test code. +- **Use the options pattern** when you need operators or CI pipelines to adjust behavior without changing source code, or when a setting applies broadly across all tests using that fixture. + +The two patterns can coexist: a fixture may accept an options fixture for structural choices (which connection strings to redirect) while also using `IOptions` for operational tuning (cleanup behavior, timeouts). + ## See Also | Resource | Description | From f26e72b7be2c9a8a67cf7b59bb74c9561f4f1415 Mon Sep 17 00:00:00 2001 From: metacoder_FEFF Date: Wed, 29 Apr 2026 08:26:05 +0000 Subject: [PATCH 03/11] add docs --- .../advanced-fixture-registration.md | 2 +- .../options-pattern.md} | 4 ++- .../parameterization-pattern.md} | 26 +++---------------- .../selecting-configuration-method.md | 26 +++++++++++++++++++ docs/articles/02.advanced/toc.yml | 10 ++++--- 5 files changed, 41 insertions(+), 27 deletions(-) rename docs/articles/02.advanced/{configuring-fixtures.md => configuring-fixtures/options-pattern.md} (95%) rename docs/articles/02.advanced/{fixture-parameterization.md => configuring-fixtures/parameterization-pattern.md} (66%) create mode 100644 docs/articles/02.advanced/configuring-fixtures/selecting-configuration-method.md diff --git a/docs/articles/02.advanced/advanced-fixture-registration.md b/docs/articles/02.advanced/advanced-fixture-registration.md index be7b19a..4cd90e2 100644 --- a/docs/articles/02.advanced/advanced-fixture-registration.md +++ b/docs/articles/02.advanced/advanced-fixture-registration.md @@ -50,7 +50,7 @@ This approach gives you full control over how the fixture and its dependencies a Fixtures that implement `IFixtureRegistrar` can expose configuration options through the `Microsoft.Extensions.Options` pattern. See the dedicated article for a complete walkthrough: -👉 [Configuring Fixtures](configuring-fixtures.md) - Learn how to add configuration support to fixtures like `TmpDirectoryFixture`. +👉 [Options Pattern](configuring-fixtures/options-pattern.md) - Learn how to add configuration support to fixtures like `TmpDirectoryFixture`. ## When to Use Advanced Registration diff --git a/docs/articles/02.advanced/configuring-fixtures.md b/docs/articles/02.advanced/configuring-fixtures/options-pattern.md similarity index 95% rename from docs/articles/02.advanced/configuring-fixtures.md rename to docs/articles/02.advanced/configuring-fixtures/options-pattern.md index 9e6b5f6..391dae2 100644 --- a/docs/articles/02.advanced/configuring-fixtures.md +++ b/docs/articles/02.advanced/configuring-fixtures/options-pattern.md @@ -1,4 +1,4 @@ -# Configuring Fixtures +# Options Pattern Fixtures that implement `IFixtureRegistrar` can expose configuration options through the standard `Microsoft.Extensions.Options` pattern. This article demonstrates how to add configuration support to a fixture using `TmpDirectoryFixture` as an example. @@ -162,3 +162,5 @@ var manager = new FixtureManagerBuilder() | [TmpDirectoryFixture.cs](https://github.com/metacoder-feff/FEFF.TestFixtures/blob/main/src/FEFF.TestFixtures/Fixtures/TmpDirectoryFixture.cs) | Complete source code example | | [TmpDirectoryFixtureTests.cs](https://github.com/metacoder-feff/FEFF.TestFixtures/blob/main/tests/FEFF.TestFixtures.Tests/Fixtures/TmpDirectoryFixtureTests.cs) | Unit tests for TmpDirectoryFixture | | [OptionsConfigurationTests.cs](https://github.com/metacoder-feff/FEFF.TestFixtures/blob/main/tests/FEFF.TestFixtures.Tests/EngineTests/OptionsConfigurationTests.cs) | Tests for options configuration patterns | +| [Parameterization Pattern](parameterization-pattern.md) | Alternative generic type-based configuration | +| [Selecting Configuration Method](selecting-configuration-method.md) | Comparison of configuration approaches | diff --git a/docs/articles/02.advanced/fixture-parameterization.md b/docs/articles/02.advanced/configuring-fixtures/parameterization-pattern.md similarity index 66% rename from docs/articles/02.advanced/fixture-parameterization.md rename to docs/articles/02.advanced/configuring-fixtures/parameterization-pattern.md index caaf42a..3ae47a8 100644 --- a/docs/articles/02.advanced/fixture-parameterization.md +++ b/docs/articles/02.advanced/configuring-fixtures/parameterization-pattern.md @@ -70,8 +70,8 @@ public class OptionsFixture : ITmpDatabaseNameFixtureOptions ``` The options fixture can: -- Return hardcoded values -- Read from environment variables + +- Return hardcoded or calculated values - Depend on other fixtures for dynamic configuration ### Step 4: Use the Parameterized Fixture @@ -95,25 +95,6 @@ The generic type parameter `OptionsFixture` tells `TmpDatabaseNameFixture` which - **Reusability** - Same parameterized fixture works with different options - **Clarity** - Options type documents required configuration -## Selecting Configuration Method - -The library provides two distinct ways to configure fixtures. Choose based on whether the configuration varies between tests or is controlled externally. - -| Aspect | Parameterization Pattern (this article) | Options Pattern ([configuring-fixtures.md](configuring-fixtures.md)) | -|--------|----------------------------------------|---------------------------------------------------------------------| -| **Mechanism** | Generic type parameter + options fixture interface | `Microsoft.Extensions.Options` bound to configuration (e.g., environment variables) | -| **When to use** | Different tests or test suites need different behavior for the same fixture | Global or environment-specific settings (CI, debugging, local overrides) | -| **Discovery** | Compile-time via generic constraint | Runtime via `IOptions` and `IFixtureRegistrar` | -| **Flexibility** | Per-test-suite variation by swapping the options fixture type | Global change without recompiling tests | -| **Example scenario** | One test suite uses database `A`, another uses database `B`, via `TmpDatabaseNameFixture` vs `TmpDatabaseNameFixture` | Skipping temp directory cleanup on CI by setting `TmpDirectoryFixture__DisposeType=Skip` | - -### Decision Guide - -- **Use parameterization** when the fixture must behave differently across test classes or collections and the decision is made at design time. This keeps configuration type-safe and visible in test code. -- **Use the options pattern** when you need operators or CI pipelines to adjust behavior without changing source code, or when a setting applies broadly across all tests using that fixture. - -The two patterns can coexist: a fixture may accept an options fixture for structural choices (which connection strings to redirect) while also using `IOptions` for operational tuning (cleanup behavior, timeouts). - ## See Also | Resource | Description | @@ -122,4 +103,5 @@ The two patterns can coexist: a fixture may accept an options fixture for struct | [ITmpDatabaseNameFixtureOptions.cs](https://github.com/metacoder-feff/FEFF.TestFixtures/blob/main/src/FEFF.TestFixtures.AspNetCore/Fixtures/TmpDatabaseNameFixture.cs) | Options interface definition | | [ApiTests.cs](https://github.com/metacoder-feff/FEFF.TestFixtures/blob/main/examples/ExampleTests.AspNetCore/ApiTests.cs) | Example usage in API tests | | [TmpDatabaseNameFixtureTests.cs](https://github.com/metacoder-feff/FEFF.TestFixtures/blob/main/tests/FEFF.TestFixtures.Tests/FixturesTests/FEFF.TestFixtures.AspNetCore/TmpDatabaseNameFixtureTests.cs) | Unit tests for parameterization | -| [Configuring Fixtures](configuring-fixtures.md) | Alternative configuration via environment variables | +| [Options Pattern](options-pattern.md) | Alternative configuration via environment variables | +| [Selecting Configuration Method](selecting-configuration-method.md) | Comparison with Options Pattern | diff --git a/docs/articles/02.advanced/configuring-fixtures/selecting-configuration-method.md b/docs/articles/02.advanced/configuring-fixtures/selecting-configuration-method.md new file mode 100644 index 0000000..7dd74c0 --- /dev/null +++ b/docs/articles/02.advanced/configuring-fixtures/selecting-configuration-method.md @@ -0,0 +1,26 @@ +# Selecting Configuration Method + +The library provides two distinct ways to configure fixtures. Choose based on whether the configuration varies between tests or is controlled externally. + +| Aspect | [Parameterization Pattern](parameterization-pattern.md) | [Options Pattern](options-pattern.md) | +|--------|----------------------------------------|---------------------------------------------------------------------| +| **When to use** | Different tests or test suites need different behavior for the same fixture | Global or environment-specific settings (CI, debugging, local overrides) | +| **Value requirement** | Fixture explicitly requires a value | Fixture should have default value | +| **Mechanism** | Generic type parameter + options fixture interface | `Microsoft.Extensions.Options` bound to configuration (e.g., environment variables) | +| **Discovery** | Compile-time via generic constraint | Runtime via `IOptions` and `IFixtureRegistrar` | +| **Flexibility** | Per-test-suite variation by swapping the options fixture type | Global change without recompiling tests | +| **Example scenario** | One test suite uses database `A`, another uses database `B`, via `TmpDatabaseNameFixture` vs `TmpDatabaseNameFixture` | Skipping temp directory cleanup on CI by setting `TmpDirectoryFixture__DisposeType=Skip` | + +## Decision Guide + +- **Use parameterization** when the fixture must behave differently across test classes or collections and the decision is made at design time. This keeps configuration type-safe and visible in test code. +- **Use the options pattern** when you need operators or CI pipelines to adjust behavior without changing source code, or when a setting applies broadly across all tests using that fixture. + +The two patterns can coexist: a fixture may accept an options fixture for structural choices (which connection strings to redirect) while also using `IOptions` for operational tuning (cleanup behavior, timeouts). + +## See Also + +| Resource | Description | +|----------|-------------| +| [Parameterization Pattern](parameterization-pattern.md) | Generic type-based configuration | +| [Options Pattern](options-pattern.md) | Environment variable-based configuration | diff --git a/docs/articles/02.advanced/toc.yml b/docs/articles/02.advanced/toc.yml index 359a1a3..d772680 100644 --- a/docs/articles/02.advanced/toc.yml +++ b/docs/articles/02.advanced/toc.yml @@ -5,6 +5,10 @@ - name: Advanced Fixture Registration href: advanced-fixture-registration.md - name: Configuring Fixtures - href: configuring-fixtures.md -- name: Fixture Parameterization - href: fixture-parameterization.md + items: + - name: Options Pattern + href: configuring-fixtures/options-pattern.md + - name: Parameterization Pattern + href: configuring-fixtures/parameterization-pattern.md + - name: Selecting Configuration Method + href: configuring-fixtures/selecting-configuration-method.md From ac4fe9b817a8c45fa80ed9bdc6f90372575aa68c Mon Sep 17 00:00:00 2001 From: metacoder_FEFF Date: Wed, 29 Apr 2026 08:40:11 +0000 Subject: [PATCH 04/11] fix docs --- .../configuring-fixtures/options-pattern.md | 93 +++++++++---------- .../parameterization-pattern.md | 7 +- docs/articles/02.advanced/toc.yml | 2 +- 3 files changed, 50 insertions(+), 52 deletions(-) diff --git a/docs/articles/02.advanced/configuring-fixtures/options-pattern.md b/docs/articles/02.advanced/configuring-fixtures/options-pattern.md index 391dae2..8e55324 100644 --- a/docs/articles/02.advanced/configuring-fixtures/options-pattern.md +++ b/docs/articles/02.advanced/configuring-fixtures/options-pattern.md @@ -18,41 +18,41 @@ The `TmpDirectoryFixture` provides a unique temporary directory for each test sc Create an `Options` class and any supporting enums: ```csharp +/// +/// Specifies the behavior when the fixture is disposed. +/// +public enum DisposeType +{ /// - /// Specifies the behavior when the fixture is disposed. + /// Deletes the temporary directory and its contents on disposal. /// - public enum DisposeType - { - /// - /// Deletes the temporary directory and its contents on disposal. - /// - Delete, - - /// - /// Skips deletion of the temporary directory on disposal. - /// - /// - /// Can be used for optimization in CI environments. - /// - Skip - } + Delete, /// - /// Configuration options for TmpDirectoryFixture. + /// Skips deletion of the temporary directory on disposal. /// - public class Options - { - /// - /// Gets or sets whether the temporary directory should be deleted on disposal. - /// Defaults to DisposeType.Delete. - /// - public DisposeType DisposeType { get; set; } = DisposeType.Delete; - - /// - /// Gets or sets the prefix for the temporary directory name. - /// - public string? Prefix { get; set; } - } + /// + /// Can be used for optimization in CI environments. + /// + Skip +} + +/// +/// Configuration options for TmpDirectoryFixture. +/// +public class Options +{ + /// + /// Gets or sets whether the temporary directory should be deleted on disposal. + /// Defaults to DisposeType.Delete. + /// + public DisposeType DisposeType { get; set; } = DisposeType.Delete; + + /// + /// Gets or sets the prefix for the temporary directory name. + /// + public string? Prefix { get; set; } +} ``` ### Step 2: Register Options with DI @@ -89,27 +89,26 @@ This enables: Inject `IOptions` into the fixture constructor: ```csharp - private readonly Options _opts; - public string Path { get; } +private readonly Options _opts; +public string Path { get; } + +public TmpDirectoryFixture(IOptions opts) +{ + _opts = opts.Value; + Path = Directory.CreateTempSubdirectory(_opts.Prefix).FullName; +} - public TmpDirectoryFixture(IOptions opts) +public void Dispose() +{ + if (_opts.DisposeType != DisposeType.Delete) + return; + + try { - _opts = opts.Value; - Path = Directory.CreateTempSubdirectory(_opts.Prefix).FullName; + Directory.Delete(Path, true); } - - public void Dispose() + catch (DirectoryNotFoundException) { - if (_opts.DisposeType != DisposeType.Delete) - return; - - try - { - Directory.Delete(Path, true); - } - catch (DirectoryNotFoundException) - { - } } } ``` diff --git a/docs/articles/02.advanced/configuring-fixtures/parameterization-pattern.md b/docs/articles/02.advanced/configuring-fixtures/parameterization-pattern.md index 3ae47a8..e918145 100644 --- a/docs/articles/02.advanced/configuring-fixtures/parameterization-pattern.md +++ b/docs/articles/02.advanced/configuring-fixtures/parameterization-pattern.md @@ -1,4 +1,4 @@ -# Fixture Parameterization +# Generic Parameterization Pattern ## Overview @@ -86,7 +86,7 @@ var fixtureInstance = TestContext.Current.GetFeffFixture [!TIP] -> Single `OptionsFixture` class can implement multiple different interfaces to parameterize multiple fixtures used in a test suite. +> A single `OptionsFixture` class can implement multiple different interfaces to parameterize multiple fixtures used in a test suite. ## Benefits of Fixture Parameterization @@ -99,8 +99,7 @@ The generic type parameter `OptionsFixture` tells `TmpDatabaseNameFixture` which | Resource | Description | |----------|-------------| -| [TmpDatabaseNameFixture.cs](https://github.com/metacoder-feff/FEFF.TestFixtures/blob/main/src/FEFF.TestFixtures.AspNetCore/Fixtures/TmpDatabaseNameFixture.cs) | Parameterized fixture implementation | -| [ITmpDatabaseNameFixtureOptions.cs](https://github.com/metacoder-feff/FEFF.TestFixtures/blob/main/src/FEFF.TestFixtures.AspNetCore/Fixtures/TmpDatabaseNameFixture.cs) | Options interface definition | +| [TmpDatabaseNameFixture.cs](https://github.com/metacoder-feff/FEFF.TestFixtures/blob/main/src/FEFF.TestFixtures.AspNetCore/Fixtures/TmpDatabaseNameFixture.cs) | Parameterized fixture and options interface | | [ApiTests.cs](https://github.com/metacoder-feff/FEFF.TestFixtures/blob/main/examples/ExampleTests.AspNetCore/ApiTests.cs) | Example usage in API tests | | [TmpDatabaseNameFixtureTests.cs](https://github.com/metacoder-feff/FEFF.TestFixtures/blob/main/tests/FEFF.TestFixtures.Tests/FixturesTests/FEFF.TestFixtures.AspNetCore/TmpDatabaseNameFixtureTests.cs) | Unit tests for parameterization | | [Options Pattern](options-pattern.md) | Alternative configuration via environment variables | diff --git a/docs/articles/02.advanced/toc.yml b/docs/articles/02.advanced/toc.yml index d772680..a7f31fb 100644 --- a/docs/articles/02.advanced/toc.yml +++ b/docs/articles/02.advanced/toc.yml @@ -8,7 +8,7 @@ items: - name: Options Pattern href: configuring-fixtures/options-pattern.md - - name: Parameterization Pattern + - name: Generic Parameterization Pattern href: configuring-fixtures/parameterization-pattern.md - name: Selecting Configuration Method href: configuring-fixtures/selecting-configuration-method.md From 7cf61b409c04d73258be4dc5f3529dc7c58fae3b Mon Sep 17 00:00:00 2001 From: metacoder_FEFF Date: Wed, 29 Apr 2026 09:53:53 +0000 Subject: [PATCH 05/11] clone TUnit to XunitV4 --- FEFF.TestFixtures.slnx | 3 + .../FEFF.TestFixtures.Abstractions.csproj | 1 + .../FEFF.TestFixtures.XunitV4.csproj | 24 ++ .../GlobalHooksExtension.cs | 165 ++++++++++ .../packages.lock.json | 302 ++++++++++++++++++ .../ApiVerificationTests.cs | 1 + ....TestFixtures.ApiVerification.Tests.csproj | 1 + .../packages.lock.json | 32 +- .../FEFF.TestFixtures.XunitV4.Tests.csproj | 21 ++ .../XunitV4IntegrationTests.cs | 71 ++++ .../packages.lock.json | 253 +++++++++++++++ ...EFF.TestFixtures.Abstractions.verified.txt | 1 + .../FEFF.TestFixtures.XunitV4.verified.txt | 29 ++ .../TUnit.TestSubject/packages.lock.json | 32 +- .../XunitV4.TestSubject/Infrastructure.cs | 26 ++ .../XunitV4.TestSubject/TestSubject.cs | 74 +++++ .../XunitV4.TestSubject.csproj | 20 ++ .../XunitV4.TestSubject/packages.lock.json | 229 +++++++++++++ 18 files changed, 1257 insertions(+), 28 deletions(-) create mode 100644 src/FEFF.TestFixtures.XunitV4/FEFF.TestFixtures.XunitV4.csproj create mode 100644 src/FEFF.TestFixtures.XunitV4/GlobalHooksExtension.cs create mode 100644 src/FEFF.TestFixtures.XunitV4/packages.lock.json create mode 100644 tests/FEFF.TestFixtures.XunitV4.Tests/FEFF.TestFixtures.XunitV4.Tests.csproj create mode 100644 tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs create mode 100644 tests/FEFF.TestFixtures.XunitV4.Tests/packages.lock.json create mode 100644 tests/Files/API/FEFF.TestFixtures.XunitV4.verified.txt create mode 100644 tests/Subjects/XunitV4.TestSubject/Infrastructure.cs create mode 100644 tests/Subjects/XunitV4.TestSubject/TestSubject.cs create mode 100644 tests/Subjects/XunitV4.TestSubject/XunitV4.TestSubject.csproj create mode 100644 tests/Subjects/XunitV4.TestSubject/packages.lock.json diff --git a/FEFF.TestFixtures.slnx b/FEFF.TestFixtures.slnx index 65eceab..fc0f88b 100644 --- a/FEFF.TestFixtures.slnx +++ b/FEFF.TestFixtures.slnx @@ -14,17 +14,20 @@ + + + \ No newline at end of file diff --git a/src/FEFF.TestFixtures.Abstractions/FEFF.TestFixtures.Abstractions.csproj b/src/FEFF.TestFixtures.Abstractions/FEFF.TestFixtures.Abstractions.csproj index cbcee0b..201b5b4 100644 --- a/src/FEFF.TestFixtures.Abstractions/FEFF.TestFixtures.Abstractions.csproj +++ b/src/FEFF.TestFixtures.Abstractions/FEFF.TestFixtures.Abstractions.csproj @@ -16,6 +16,7 @@ + diff --git a/src/FEFF.TestFixtures.XunitV4/FEFF.TestFixtures.XunitV4.csproj b/src/FEFF.TestFixtures.XunitV4/FEFF.TestFixtures.XunitV4.csproj new file mode 100644 index 0000000..318c679 --- /dev/null +++ b/src/FEFF.TestFixtures.XunitV4/FEFF.TestFixtures.XunitV4.csproj @@ -0,0 +1,24 @@ + + + + + + true + xUnit v4 extension, adds 'FEFF.TestFixtures'. + testing;fixtures;xUnit;xUnitV4;FEFF; + + + + + + + + + + + + + + + + diff --git a/src/FEFF.TestFixtures.XunitV4/GlobalHooksExtension.cs b/src/FEFF.TestFixtures.XunitV4/GlobalHooksExtension.cs new file mode 100644 index 0000000..dd3d535 --- /dev/null +++ b/src/FEFF.TestFixtures.XunitV4/GlobalHooksExtension.cs @@ -0,0 +1,165 @@ +using FEFF.Extensions; + +namespace FEFF.TestFixtures.XunitV4; + +using Engine; + +/// +/// Defines the lifetime scope for a fixture in TUnit tests. +/// +public enum FixtureScopeType +{ + /// + /// The fixture is scoped to an individual test case. + /// + TestCase, + + /// + /// The fixture is scoped to a test class. + /// + Class, + + /// + /// The fixture is scoped to a test assembly. + /// + Assembly, + + /// + /// The fixture is scoped to the entire test session. + /// + Session +} + +/// +/// Provides TUnit integration hooks for the FEFF.TestFixtures framework. +/// Automatically manages fixture lifecycles across test, class, assembly, and session scopes. +/// +public static class GlobalHooksExtension +{ + //TODO: multiple sessions??? + // => scopeId + // => dispose + +#if NET9_0_OR_GREATER + private static readonly Lock _lock = new(); +#else + private static readonly Object _lock = new(); +#endif + private static volatile FixtureManager? _manager; + + /// + /// Resolves a fixture from the specified scope within the test context. + /// + /// The type of fixture to resolve. + /// The current TUnit test context. + /// The lifetime scope for the fixture. Defaults to . + /// The resolved fixture instance. + /// Thrown when is null. + public static T GetFeffFixture(this TestContext ctx, FixtureScopeType scopeType = FixtureScopeType.TestCase) + where T : notnull + { + ArgumentNullException.ThrowIfNull(ctx); + + var m = GetManager(); + var id = GetScopeId(ctx, scopeType); + return m.GetScope(id).GetFixture(); + } + + private static string GetScopeId(TestContext ctx, FixtureScopeType scopeType) => scopeType switch + { + FixtureScopeType.TestCase => GetScopeId(ctx), + FixtureScopeType.Class => GetScopeId(ctx.ClassContext), + FixtureScopeType.Assembly => GetScopeId(ctx.ClassContext.AssemblyContext), + FixtureScopeType.Session => GetScopeId(ctx.ClassContext.AssemblyContext.TestSessionContext), + _ => + throw EnumMatchException.From(scopeType) + }; + + private static string GetScopeId(string ctxId, FixtureScopeType scopeType) => + $"{scopeType}-{ctxId}"; + + private static string GetScopeId(TestContext ctx) => + GetScopeId(ctx.Id, FixtureScopeType.TestCase); + + private static string GetScopeId(ClassHookContext ctx) => + GetScopeId($"{ctx.ClassType.FullName}_{ctx.ClassType.Assembly.FullName}", FixtureScopeType.Class); + + private static string GetScopeId(AssemblyHookContext ctx) => + GetScopeId(ctx.Assembly.FullName!, FixtureScopeType.Assembly); + + private static string GetScopeId(TestSessionContext ctx) => + GetScopeId(ctx.Id, FixtureScopeType.Session); + + private static FixtureManager GetManager() + { + // double-check: optimization + if (_manager != null) + return _manager; + + lock (_lock) + { + // double-check: guard + _manager ??= new FixtureManagerBuilder().Build(); + } + return _manager; + } + + private static ValueTask RemoveScope(FixtureManager? manager, string id) + { + if (manager == null) + return ValueTask.CompletedTask; + + return manager.RemoveScopeAsync(id); + } + + /// + /// TUnit hook executed after every test case. Disposes the test-case-scoped fixtures. + /// + /// The test context. + [AfterEvery(Test)] + public async static Task AfterT(TestContext ctx) + { + var id = GetScopeId(ctx); + await RemoveScope(_manager, id).ConfigureAwait(false); + } + + /// + /// TUnit hook executed after every test class. Disposes the class-scoped fixtures. + /// + /// The class hook context. + [AfterEvery(Class)] + public async static Task AfterC(ClassHookContext ctx) + { + var id = GetScopeId(ctx); + await RemoveScope(_manager, id).ConfigureAwait(false); + } + + /// + /// TUnit hook executed after every test assembly. Disposes the assembly-scoped fixtures. + /// + /// The assembly hook context. + [AfterEvery(Assembly)] + public async static Task AfterA(AssemblyHookContext ctx) + { + var id = GetScopeId(ctx); + await RemoveScope(_manager, id).ConfigureAwait(false); + } + + /// + /// TUnit hook executed after the test session. Disposes the session-scoped fixture + /// and the underlying . + /// + /// The test session context. + [After(TestSession)] + public async static Task AfterS(TestSessionContext ctx) + { + var m = Interlocked.Exchange(ref _manager, null); + + var id = GetScopeId(ctx); + await RemoveScope(m, id).ConfigureAwait(false); + + // dispose both: manager & assemblyFixtureScope + if (m != null) + await m.DisposeAsync().ConfigureAwait(false); + } +} diff --git a/src/FEFF.TestFixtures.XunitV4/packages.lock.json b/src/FEFF.TestFixtures.XunitV4/packages.lock.json new file mode 100644 index 0000000..471d442 --- /dev/null +++ b/src/FEFF.TestFixtures.XunitV4/packages.lock.json @@ -0,0 +1,302 @@ +{ + "version": 1, + "dependencies": { + "net10.0": { + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[10.0.203, )", + "resolved": "10.0.203", + "contentHash": "R4Tvr1oACImMS+Y5M7NM07ll9QyJSKnki3Dvz8QwG1W6FEmd+9fmZXAF6BE6UPswHF6n0v41wgMQGlaudOspqA==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "10.0.203", + "Microsoft.SourceLink.Common": "10.0.203", + "System.IO.Hashing": "10.0.7" + } + }, + "TUnit.Core": { + "type": "Direct", + "requested": "[1.40.10, )", + "resolved": "1.40.10", + "contentHash": "e09Mumq7XAnm2olh4/YzgrijNbJN5V0qMahFiyxblSrbibBv6uJFrEdaOZSn9wtds3zsE8mmcpTzCCm6MXEeXQ==" + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "10.0.203", + "contentHash": "m56WtzvIcL6t7JR3c7ogYitHizNM2QnRSo8yqxrQi+m5E/GGyDEmqymP+2p6YsFXn0j/Tzz67s4FQnrTLC7GKQ==", + "dependencies": { + "System.IO.Hashing": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "wZbGh7J8R1vXN525O6d8dlcDTxhRTnd5MyW4LdfP5S0tSnTwTCseYSrq6g0Mxh7W9xn8P/2xPuf0D/m6k2dy2w==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7", + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "t56nEgvECcyLPojZIUFWJknQQDAbgfTf9J+QMYJE1YYvVgz69vN6B/AKL8Grvj3Lcnp8kTpNqwmwFhb3YLJmtQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "8bS1qIaRivny+WX+49pmeJ6iAylbtX8C0DLEcCQWZjdxQvLqaMssXiGD9P/6pYElrHbK5/nAHmjbQ8STqdMYeg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.7", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "TWto3imA+mJMLZI+5sbgLiFFoOFNFkizQYNaC5jTuiHKn3diwm1RN7mWDOEZN9kG2bixw7IvgpvtUG5/teSRzA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.7", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "91F/o3emPV/+xY/ip3s2LqDNF14kjttlVtq0BXgg6p4MnCzeSZxnUJm+t6WRrtD3JdGo88/oX+z7OwK4y8PZuw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "Z6mfFEaFcwCfSboxJwOLfu7/31npCY9q70WUamHW/vRQhDvBKOT4Vf9YkZj5J6hLvJpb0oDEYfHunQZj0xxvKw==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "00SHUGTh2jSMvIr6x9Xwd2nE+B5/qFCO/9hDwUDhJsjYRDlADmaBZ7tqehXzBDsfjHSXJzuRHJzPYPPjphBQ7Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7", + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "IT7f+EMXZtkjatEcF+o6aOw/7OE4etRrMiDGEWH/iiTu2R3uhC4NEQJCfHiibtX45U3sIQ5Fh6tbb1qaOz3YAg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7", + "Microsoft.Extensions.Configuration.Binder": "10.0.7", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7", + "Microsoft.Extensions.Options": "10.0.7", + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "D5M0Jr551iTgwkZMN9rm0pSkgNLj5quUWQUmQPMZh7k/bnvZTnXRGfE2KuvXf1EEjt/ofD9yw9IumpgdP9QCnw==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "10.0.203", + "contentHash": "QYAnhBCOkT3ZUT/fHag11+bamwlbZ3U9Vi/WfKrD9emdUf1t3aqjWv0V2KtEGHSRSC81aBc8Oy/mvyGpEYd9Pg==" + }, + "System.IO.Hashing": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "6hsjdSr4VOXSOnhALkYplHpAxnTG1J33YN42IB6nH2fEg4QnJqrZ4Ft+qn7mkrKAOYC8pCSFYwVWw6rQbmwgLQ==" + }, + "System.Reflection.MetadataLoadContext": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "XDkKntYPUaANhLVdo7AHbrJ+QbUAY5u/T7lG5rHSx5kLxeHceoc3JcIMVAc/vZT+3rbwFYlrdnikxdi6G+2nvA==" + }, + "feff.testfixtures": { + "type": "Project", + "dependencies": { + "FEFF.TestFixtures.Abstractions": "[0.0.1, )", + "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.7, )" + } + }, + "feff.testfixtures.abstractions": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "[10.0.7, )" + } + }, + "feff.testfixtures.engine": { + "type": "Project", + "dependencies": { + "FEFF.TestFixtures.Abstractions": "[0.0.1, )", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "[10.0.7, )", + "Microsoft.Extensions.DependencyInjection": "[10.0.7, )", + "Microsoft.Extensions.Options": "[10.0.7, )", + "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.7, )", + "System.Reflection.MetadataLoadContext": "[10.0.7, )" + } + } + }, + "net8.0": { + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[10.0.203, )", + "resolved": "10.0.203", + "contentHash": "R4Tvr1oACImMS+Y5M7NM07ll9QyJSKnki3Dvz8QwG1W6FEmd+9fmZXAF6BE6UPswHF6n0v41wgMQGlaudOspqA==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "10.0.203", + "Microsoft.SourceLink.Common": "10.0.203", + "System.IO.Hashing": "10.0.7" + } + }, + "TUnit.Core": { + "type": "Direct", + "requested": "[1.40.10, )", + "resolved": "1.40.10", + "contentHash": "e09Mumq7XAnm2olh4/YzgrijNbJN5V0qMahFiyxblSrbibBv6uJFrEdaOZSn9wtds3zsE8mmcpTzCCm6MXEeXQ==" + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "10.0.203", + "contentHash": "m56WtzvIcL6t7JR3c7ogYitHizNM2QnRSo8yqxrQi+m5E/GGyDEmqymP+2p6YsFXn0j/Tzz67s4FQnrTLC7GKQ==", + "dependencies": { + "System.IO.Hashing": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "wZbGh7J8R1vXN525O6d8dlcDTxhRTnd5MyW4LdfP5S0tSnTwTCseYSrq6g0Mxh7W9xn8P/2xPuf0D/m6k2dy2w==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7", + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "t56nEgvECcyLPojZIUFWJknQQDAbgfTf9J+QMYJE1YYvVgz69vN6B/AKL8Grvj3Lcnp8kTpNqwmwFhb3YLJmtQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "8bS1qIaRivny+WX+49pmeJ6iAylbtX8C0DLEcCQWZjdxQvLqaMssXiGD9P/6pYElrHbK5/nAHmjbQ8STqdMYeg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.7", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "TWto3imA+mJMLZI+5sbgLiFFoOFNFkizQYNaC5jTuiHKn3diwm1RN7mWDOEZN9kG2bixw7IvgpvtUG5/teSRzA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.7", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "91F/o3emPV/+xY/ip3s2LqDNF14kjttlVtq0BXgg6p4MnCzeSZxnUJm+t6WRrtD3JdGo88/oX+z7OwK4y8PZuw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "Z6mfFEaFcwCfSboxJwOLfu7/31npCY9q70WUamHW/vRQhDvBKOT4Vf9YkZj5J6hLvJpb0oDEYfHunQZj0xxvKw==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "00SHUGTh2jSMvIr6x9Xwd2nE+B5/qFCO/9hDwUDhJsjYRDlADmaBZ7tqehXzBDsfjHSXJzuRHJzPYPPjphBQ7Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7", + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "IT7f+EMXZtkjatEcF+o6aOw/7OE4etRrMiDGEWH/iiTu2R3uhC4NEQJCfHiibtX45U3sIQ5Fh6tbb1qaOz3YAg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7", + "Microsoft.Extensions.Configuration.Binder": "10.0.7", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7", + "Microsoft.Extensions.Options": "10.0.7", + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "D5M0Jr551iTgwkZMN9rm0pSkgNLj5quUWQUmQPMZh7k/bnvZTnXRGfE2KuvXf1EEjt/ofD9yw9IumpgdP9QCnw==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "10.0.203", + "contentHash": "QYAnhBCOkT3ZUT/fHag11+bamwlbZ3U9Vi/WfKrD9emdUf1t3aqjWv0V2KtEGHSRSC81aBc8Oy/mvyGpEYd9Pg==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "0Ti4Jv1ga3eurH5HaCVsPybcBl+08YfzM9smqAJzHvqV494xK+0pSbytGrMTWhph+zsyKIaoGNiR5u3by5bj+A==" + }, + "System.IO.Hashing": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "6hsjdSr4VOXSOnhALkYplHpAxnTG1J33YN42IB6nH2fEg4QnJqrZ4Ft+qn7mkrKAOYC8pCSFYwVWw6rQbmwgLQ==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "FEBU/i3gHeleqayWrbWieI5MlepDBTcjAxMzjGbK7FwGtlpxGSU1Lt11W1/o4ExdqsNtboiVhT5s4kNtH3eqvw==", + "dependencies": { + "System.Collections.Immutable": "10.0.7" + } + }, + "System.Reflection.MetadataLoadContext": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "XDkKntYPUaANhLVdo7AHbrJ+QbUAY5u/T7lG5rHSx5kLxeHceoc3JcIMVAc/vZT+3rbwFYlrdnikxdi6G+2nvA==", + "dependencies": { + "System.Collections.Immutable": "10.0.7", + "System.Reflection.Metadata": "10.0.7" + } + }, + "feff.testfixtures": { + "type": "Project", + "dependencies": { + "FEFF.TestFixtures.Abstractions": "[0.0.1, )", + "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.7, )" + } + }, + "feff.testfixtures.abstractions": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "[10.0.7, )" + } + }, + "feff.testfixtures.engine": { + "type": "Project", + "dependencies": { + "FEFF.TestFixtures.Abstractions": "[0.0.1, )", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "[10.0.7, )", + "Microsoft.Extensions.DependencyInjection": "[10.0.7, )", + "Microsoft.Extensions.Options": "[10.0.7, )", + "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.7, )", + "System.Reflection.MetadataLoadContext": "[10.0.7, )" + } + } + } + } +} \ No newline at end of file diff --git a/tests/FEFF.TestFixtures.ApiVerification.Tests/ApiVerificationTests.cs b/tests/FEFF.TestFixtures.ApiVerification.Tests/ApiVerificationTests.cs index 4727890..9a236f6 100644 --- a/tests/FEFF.TestFixtures.ApiVerification.Tests/ApiVerificationTests.cs +++ b/tests/FEFF.TestFixtures.ApiVerification.Tests/ApiVerificationTests.cs @@ -20,6 +20,7 @@ public class ApiVerificationTests [InlineData("FEFF.TestFixtures.AspNetCore.EF")] [InlineData("FEFF.TestFixtures.AspNetCore.SignalR")] [InlineData("FEFF.TestFixtures.XunitV3")] + [InlineData("FEFF.TestFixtures.XunitV4")] [InlineData("FEFF.TestFixtures.TUnit")] public Task API_should_not_change(string assemblyName) { diff --git a/tests/FEFF.TestFixtures.ApiVerification.Tests/FEFF.TestFixtures.ApiVerification.Tests.csproj b/tests/FEFF.TestFixtures.ApiVerification.Tests/FEFF.TestFixtures.ApiVerification.Tests.csproj index 459e252..5678d7c 100644 --- a/tests/FEFF.TestFixtures.ApiVerification.Tests/FEFF.TestFixtures.ApiVerification.Tests.csproj +++ b/tests/FEFF.TestFixtures.ApiVerification.Tests/FEFF.TestFixtures.ApiVerification.Tests.csproj @@ -40,6 +40,7 @@ + diff --git a/tests/FEFF.TestFixtures.ApiVerification.Tests/packages.lock.json b/tests/FEFF.TestFixtures.ApiVerification.Tests/packages.lock.json index 497efca..a695074 100644 --- a/tests/FEFF.TestFixtures.ApiVerification.Tests/packages.lock.json +++ b/tests/FEFF.TestFixtures.ApiVerification.Tests/packages.lock.json @@ -627,8 +627,8 @@ }, "TUnit.Core": { "type": "Transitive", - "resolved": "1.40.5", - "contentHash": "NcO9LzoVc0RAx5Qv3WWC+w9fxQ6ojC+i2ZQf9z+IH+mHP79jQmXls7O6d0auG8Wa5mUYzaTlM6LTjwcdWU6v/Q==" + "resolved": "1.40.10", + "contentHash": "e09Mumq7XAnm2olh4/YzgrijNbJN5V0qMahFiyxblSrbibBv6uJFrEdaOZSn9wtds3zsE8mmcpTzCCm6MXEeXQ==" }, "Verify": { "type": "Transitive", @@ -700,7 +700,7 @@ "feff.testfixtures": { "type": "Project", "dependencies": { - "FEFF.TestFixtures.Abstractions": "[1.4.3, )", + "FEFF.TestFixtures.Abstractions": "[0.0.1, )", "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.7, )" } }, @@ -713,7 +713,7 @@ "feff.testfixtures.aspnetcore": { "type": "Project", "dependencies": { - "FEFF.TestFixtures": "[1.4.3, )", + "FEFF.TestFixtures": "[0.0.1, )", "Microsoft.AspNetCore.Mvc.Testing": "[10.0.7, )", "Microsoft.Extensions.Diagnostics.Testing": "[10.5.0, )", "Microsoft.Extensions.TimeProvider.Testing": "[10.5.0, )" @@ -722,21 +722,21 @@ "feff.testfixtures.aspnetcore.ef": { "type": "Project", "dependencies": { - "FEFF.TestFixtures.AspNetCore": "[1.4.3, )", + "FEFF.TestFixtures.AspNetCore": "[0.0.1, )", "Microsoft.EntityFrameworkCore": "[10.0.7, )" } }, "feff.testfixtures.aspnetcore.signalr": { "type": "Project", "dependencies": { - "FEFF.TestFixtures.AspNetCore": "[1.4.3, )", + "FEFF.TestFixtures.AspNetCore": "[0.0.1, )", "Microsoft.AspNetCore.SignalR.Client": "[10.0.7, )" } }, "feff.testfixtures.engine": { "type": "Project", "dependencies": { - "FEFF.TestFixtures.Abstractions": "[1.4.3, )", + "FEFF.TestFixtures.Abstractions": "[0.0.1, )", "Microsoft.Extensions.Configuration.EnvironmentVariables": "[10.0.7, )", "Microsoft.Extensions.DependencyInjection": "[10.0.7, )", "Microsoft.Extensions.Options": "[10.0.7, )", @@ -747,18 +747,26 @@ "feff.testfixtures.tunit": { "type": "Project", "dependencies": { - "FEFF.TestFixtures": "[1.4.3, )", - "FEFF.TestFixtures.Engine": "[1.4.3, )", - "TUnit.Core": "[1.40.5, )" + "FEFF.TestFixtures": "[0.0.1, )", + "FEFF.TestFixtures.Engine": "[0.0.1, )", + "TUnit.Core": "[1.40.10, )" } }, "feff.testfixtures.xunitv3": { "type": "Project", "dependencies": { - "FEFF.TestFixtures": "[1.4.3, )", - "FEFF.TestFixtures.Engine": "[1.4.3, )", + "FEFF.TestFixtures": "[0.0.1, )", + "FEFF.TestFixtures.Engine": "[0.0.1, )", "xunit.v3.extensibility.core": "[3.2.2, )" } + }, + "feff.testfixtures.xunitv4": { + "type": "Project", + "dependencies": { + "FEFF.TestFixtures": "[0.0.1, )", + "FEFF.TestFixtures.Engine": "[0.0.1, )", + "TUnit.Core": "[1.40.10, )" + } } } } diff --git a/tests/FEFF.TestFixtures.XunitV4.Tests/FEFF.TestFixtures.XunitV4.Tests.csproj b/tests/FEFF.TestFixtures.XunitV4.Tests/FEFF.TestFixtures.XunitV4.Tests.csproj new file mode 100644 index 0000000..db8282f --- /dev/null +++ b/tests/FEFF.TestFixtures.XunitV4.Tests/FEFF.TestFixtures.XunitV4.Tests.csproj @@ -0,0 +1,21 @@ + + + + enable + enable + Exe + net10.0 + + + + + + + + + + + + + + diff --git a/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs b/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs new file mode 100644 index 0000000..a0f6a59 --- /dev/null +++ b/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs @@ -0,0 +1,71 @@ +using System.Diagnostics; +using System.Text; +using AwesomeAssertions; +using AwesomeAssertions.Json; +using Newtonsoft.Json.Linq; + +namespace FEFF.TestFixtures.XunitV4.Tests; + +//TODO: test event order: https://tunit.dev/docs/writing-tests/event-subscribing/ + +public class TUnitIntegrationTests +{ + // Fixture creating is tested inside TestSubject + [Test] + public async Task Fixtures__should_be_disposed() + { + // Arrange + var fi = new FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location); + var d = fi.Directory!.FullName; + // testSubject is referenced by this project, therefore it is built and copied here + var testSubject = $"{d}/XunitV4.TestSubject.dll"; + // testSubject creates a file in its dir + var resultFile = $"{d}/test-subject-result.json"; + + TryDelete(resultFile); + File.Exists(resultFile).Should().BeFalse(); + + // Act + var pi = new ProcessStartInfo("dotnet") + { + Arguments = testSubject + }; + var p = Process.Start(pi); + p.Should().NotBeNull(); + + using (p) + { + await p.WaitForExitAsync(); + p.HasExited.Should().BeTrue(); + p.ExitCode.Should().Be(0); + } + + // Assert + File.Exists(resultFile).Should().BeTrue(); + + // resultFile contains dispose calls per fixture counters + var res = File.ReadAllText(resultFile, Encoding.UTF8); + JToken.Parse(res) + .Should().BeEquivalentTo( + """ + { + "TestFix":4, + "ClassFix":2, + "AssemblyFix":1, + "SessionFix":1, + "SingletonFix":1, + } + """); + } + + private static void TryDelete(string f) + { + try + { + new FileInfo(f).Delete(); + } + catch (FileNotFoundException) + { + } + } +} \ No newline at end of file diff --git a/tests/FEFF.TestFixtures.XunitV4.Tests/packages.lock.json b/tests/FEFF.TestFixtures.XunitV4.Tests/packages.lock.json new file mode 100644 index 0000000..506d4ea --- /dev/null +++ b/tests/FEFF.TestFixtures.XunitV4.Tests/packages.lock.json @@ -0,0 +1,253 @@ +{ + "version": 1, + "dependencies": { + "net10.0": { + "AwesomeAssertions": { + "type": "Direct", + "requested": "[9.4.0, )", + "resolved": "9.4.0", + "contentHash": "dJxkWiQ8D+xT6Gr2sSL83+Mar+Vpy2JTcUPxFcckpPJ8VYBfSgnk+zqpS6t7kcGnjz8NLyF14qfuoL4bKzzoew==" + }, + "AwesomeAssertions.Json": { + "type": "Direct", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "h8xOiCwdKP45ezx9l2zwHNZm0YC0jambOQUKBz9aM9KPkuVingkM0DIOMTegS8o4RIJyvUfCTkvsVrK7dUqb+g==", + "dependencies": { + "AwesomeAssertions": "9.0.0", + "Newtonsoft.Json": "13.0.1" + } + }, + "Microsoft.Testing.Extensions.CodeCoverage": { + "type": "Direct", + "requested": "[18.6.2, )", + "resolved": "18.6.2", + "contentHash": "vRDhB96XQyVdYFp4cQZOMz/lx0okfCdzTXPxGiuFhKx2yUL0FT/skTpnTv+7x13+tjNOcT39i2Ln3BYtslzf2w==", + "dependencies": { + "Microsoft.DiaSymReader": "2.2.5", + "Microsoft.Extensions.DependencyModel": "8.0.2", + "Microsoft.Testing.Platform": "2.1.0" + } + }, + "TUnit": { + "type": "Direct", + "requested": "[1.*, )", + "resolved": "1.40.10", + "contentHash": "oM4E+wGh3J+PnKyEgs22wPnn6mxx/IhchfkE2wDRxI9Xqf7kt2HhwDlwpUnNytCd6aWgeSWRdD6kOkweWpn+LA==", + "dependencies": { + "Microsoft.Testing.Extensions.CodeCoverage": "18.6.2", + "Microsoft.Testing.Extensions.Telemetry": "2.2.1", + "Microsoft.Testing.Extensions.TrxReport": "2.2.1", + "TUnit.Assertions": "1.40.10", + "TUnit.Engine": "1.40.10" + } + }, + "EnumerableAsyncProcessor": { + "type": "Transitive", + "resolved": "3.8.4", + "contentHash": "KlbpupRCz3Kf+P7gsiDvFXJ980i/9lfihMZFmmxIk0Gf6mopEjy74OTJZmdaKDQpE29eQDBnMZB5khyW3eugrg==" + }, + "Microsoft.ApplicationInsights": { + "type": "Transitive", + "resolved": "2.23.0", + "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw==" + }, + "Microsoft.DiaSymReader": { + "type": "Transitive", + "resolved": "2.2.5", + "contentHash": "Cq0DLpL8oQmXX3EUCClAYWDBy7Nf3Km6kmUw/eYWlYcTeC3g3Nekd/Z/ldsiy+Oi3xboanlQV9oaVCkgdLEhOQ==" + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "wZbGh7J8R1vXN525O6d8dlcDTxhRTnd5MyW4LdfP5S0tSnTwTCseYSrq6g0Mxh7W9xn8P/2xPuf0D/m6k2dy2w==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7", + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "t56nEgvECcyLPojZIUFWJknQQDAbgfTf9J+QMYJE1YYvVgz69vN6B/AKL8Grvj3Lcnp8kTpNqwmwFhb3YLJmtQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "8bS1qIaRivny+WX+49pmeJ6iAylbtX8C0DLEcCQWZjdxQvLqaMssXiGD9P/6pYElrHbK5/nAHmjbQ8STqdMYeg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.7", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "TWto3imA+mJMLZI+5sbgLiFFoOFNFkizQYNaC5jTuiHKn3diwm1RN7mWDOEZN9kG2bixw7IvgpvtUG5/teSRzA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.7", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "91F/o3emPV/+xY/ip3s2LqDNF14kjttlVtq0BXgg6p4MnCzeSZxnUJm+t6WRrtD3JdGo88/oX+z7OwK4y8PZuw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "Z6mfFEaFcwCfSboxJwOLfu7/31npCY9q70WUamHW/vRQhDvBKOT4Vf9YkZj5J6hLvJpb0oDEYfHunQZj0xxvKw==" + }, + "Microsoft.Extensions.DependencyModel": { + "type": "Transitive", + "resolved": "8.0.2", + "contentHash": "mUBDZZRgZrSyFOsJ2qJJ9fXfqd/kXJwf3AiDoqLD9m6TjY5OO/vLNOb9fb4juC0487eq4hcGN/M2Rh/CKS7QYw==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "00SHUGTh2jSMvIr6x9Xwd2nE+B5/qFCO/9hDwUDhJsjYRDlADmaBZ7tqehXzBDsfjHSXJzuRHJzPYPPjphBQ7Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7", + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "IT7f+EMXZtkjatEcF+o6aOw/7OE4etRrMiDGEWH/iiTu2R3uhC4NEQJCfHiibtX45U3sIQ5Fh6tbb1qaOz3YAg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7", + "Microsoft.Extensions.Configuration.Binder": "10.0.7", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7", + "Microsoft.Extensions.Options": "10.0.7", + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "D5M0Jr551iTgwkZMN9rm0pSkgNLj5quUWQUmQPMZh7k/bnvZTnXRGfE2KuvXf1EEjt/ofD9yw9IumpgdP9QCnw==" + }, + "Microsoft.Testing.Extensions.Telemetry": { + "type": "Transitive", + "resolved": "2.2.1", + "contentHash": "7zB8BjffOyvqfHF26rFVPuK0w1fCf5+j1tLuhHIr76CqxXkGb+fMJtq6YNOV+m6qPytExHMXxluk3RgJ+dSIqw==", + "dependencies": { + "Microsoft.ApplicationInsights": "2.23.0", + "Microsoft.Testing.Platform": "2.2.1" + } + }, + "Microsoft.Testing.Extensions.TrxReport": { + "type": "Transitive", + "resolved": "2.2.1", + "contentHash": "FWaktPQHSiZh/+2ft2PHH/4bLlg8BKlrbLiil8mRcpoP0oHzKpgBfmN3QepGlAbxG0yDrZGN8tuPy77FYdEaMw==", + "dependencies": { + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.1", + "Microsoft.Testing.Platform": "2.2.1" + } + }, + "Microsoft.Testing.Extensions.TrxReport.Abstractions": { + "type": "Transitive", + "resolved": "2.2.1", + "contentHash": "RD6D1Jx6cKDA5IHd1H2q8ylIuQG3PD+gdULI0JC8CvsRtaypFzTFpB5xDPuQi8o6kAkcM04cBhAiJPxZboNH2Q==", + "dependencies": { + "Microsoft.Testing.Platform": "2.2.1" + } + }, + "Microsoft.Testing.Platform": { + "type": "Transitive", + "resolved": "2.2.1", + "contentHash": "9bbPuls/b6/vUFzxbSjJLZlJHyKBfOZE5kjIY+ITI2ASqlFPJhR83BdLydJeQOCLEZhEbrEcz5xtt1B69nwSVg==" + }, + "Microsoft.Testing.Platform.MSBuild": { + "type": "Transitive", + "resolved": "2.2.1", + "contentHash": "CSJOcZHfKlTyPbS0CTJk6iEnU4gJC+eUA5z72UBnMDRdgVHYOmB8k9Y7jT233gZjnCOQiYFg3acQHRfu2H62nw==", + "dependencies": { + "Microsoft.Testing.Platform": "2.2.1" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, + "System.Reflection.MetadataLoadContext": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "XDkKntYPUaANhLVdo7AHbrJ+QbUAY5u/T7lG5rHSx5kLxeHceoc3JcIMVAc/vZT+3rbwFYlrdnikxdi6G+2nvA==" + }, + "TUnit.Assertions": { + "type": "Transitive", + "resolved": "1.40.10", + "contentHash": "DCqMyNB0WcvUO675JQEQEKCi4fK/1oXuq2DrWqIf3ErZGXbDtuG+22jXCOaoxIv5WyD9L65LAY5Q9/4ufN9mdA==" + }, + "TUnit.Core": { + "type": "Transitive", + "resolved": "1.40.10", + "contentHash": "e09Mumq7XAnm2olh4/YzgrijNbJN5V0qMahFiyxblSrbibBv6uJFrEdaOZSn9wtds3zsE8mmcpTzCCm6MXEeXQ==" + }, + "TUnit.Engine": { + "type": "Transitive", + "resolved": "1.40.10", + "contentHash": "WeIdqoON5bZfW1oGdT+KdePkfo7VHAhOUpakOY52rRuajvnNlCuKMWmzCxElKnlOtMSE2MSpVumT0nxbkS4sEw==", + "dependencies": { + "EnumerableAsyncProcessor": "3.8.4", + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.1", + "Microsoft.Testing.Platform": "2.2.1", + "Microsoft.Testing.Platform.MSBuild": "2.2.1", + "TUnit.Core": "1.40.10" + } + }, + "feff.testfixtures": { + "type": "Project", + "dependencies": { + "FEFF.TestFixtures.Abstractions": "[0.0.1, )", + "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.7, )" + } + }, + "feff.testfixtures.abstractions": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "[10.0.7, )" + } + }, + "feff.testfixtures.engine": { + "type": "Project", + "dependencies": { + "FEFF.TestFixtures.Abstractions": "[0.0.1, )", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "[10.0.7, )", + "Microsoft.Extensions.DependencyInjection": "[10.0.7, )", + "Microsoft.Extensions.Options": "[10.0.7, )", + "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.7, )", + "System.Reflection.MetadataLoadContext": "[10.0.7, )" + } + }, + "feff.testfixtures.xunitv4": { + "type": "Project", + "dependencies": { + "FEFF.TestFixtures": "[0.0.1, )", + "FEFF.TestFixtures.Engine": "[0.0.1, )", + "TUnit.Core": "[1.40.10, )" + } + }, + "xunitv4.testsubject": { + "type": "Project", + "dependencies": { + "AwesomeAssertions": "[9.4.0, )", + "FEFF.TestFixtures.XunitV4": "[0.0.1, )", + "Microsoft.Testing.Extensions.CodeCoverage": "[18.6.2, )", + "TUnit": "[1.*, )" + } + } + } + } +} \ No newline at end of file diff --git a/tests/Files/API/FEFF.TestFixtures.Abstractions.verified.txt b/tests/Files/API/FEFF.TestFixtures.Abstractions.verified.txt index 8c9cf72..e4df0f2 100644 --- a/tests/Files/API/FEFF.TestFixtures.Abstractions.verified.txt +++ b/tests/Files/API/FEFF.TestFixtures.Abstractions.verified.txt @@ -4,6 +4,7 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("FEFF.TestFixtures.Engine")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("FEFF.TestFixtures.TUnit")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("FEFF.TestFixtures.XunitV3")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("FEFF.TestFixtures.XunitV4")] [assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v10.0", FrameworkDisplayName=".NET 10.0")] namespace FEFF.TestFixtures { diff --git a/tests/Files/API/FEFF.TestFixtures.XunitV4.verified.txt b/tests/Files/API/FEFF.TestFixtures.XunitV4.verified.txt new file mode 100644 index 0000000..7c8edee --- /dev/null +++ b/tests/Files/API/FEFF.TestFixtures.XunitV4.verified.txt @@ -0,0 +1,29 @@ +[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/metacoder-feff/FEFF.TestFixtures")] +[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v10.0", FrameworkDisplayName=".NET 10.0")] +namespace FEFF.TestFixtures.XunitV4 +{ + public enum FixtureScopeType + { + TestCase = 0, + Class = 1, + Assembly = 2, + Session = 3, + } + public static class GlobalHooksExtension + { + [TUnit.Core.AfterEvery(TUnit.Core.HookType.Assembly)] + "cs", 141)] + public static System.Threading.Tasks.Task AfterA(TUnit.Core.AssemblyHookContext ctx) { } + [TUnit.Core.AfterEvery(TUnit.Core.HookType.Class)] + "cs", 130)] + public static System.Threading.Tasks.Task AfterC(TUnit.Core.ClassHookContext ctx) { } + [TUnit.Core.After(TUnit.Core.HookType.TestSession)] + "cs", 153)] + public static System.Threading.Tasks.Task AfterS(TUnit.Core.TestSessionContext ctx) { } + [TUnit.Core.AfterEvery(TUnit.Core.HookType.Test)] + "cs", 119)] + public static System.Threading.Tasks.Task AfterT(TUnit.Core.TestContext ctx) { } + public static T GetFeffFixture(this TUnit.Core.TestContext ctx, FEFF.TestFixtures.XunitV4.FixtureScopeType scopeType = 0) + where T : notnull { } + } +} \ No newline at end of file diff --git a/tests/Subjects/TUnit.TestSubject/packages.lock.json b/tests/Subjects/TUnit.TestSubject/packages.lock.json index 095f48d..1930499 100644 --- a/tests/Subjects/TUnit.TestSubject/packages.lock.json +++ b/tests/Subjects/TUnit.TestSubject/packages.lock.json @@ -22,14 +22,14 @@ "TUnit": { "type": "Direct", "requested": "[1.*, )", - "resolved": "1.40.5", - "contentHash": "5wEPNdEG3Ixwyo1p/dkyrXXygAAN05EANBYq1b9LwtR3dPCA1Hmx9KSyVNMplcBs73Iz0sags9rdIvCLq9BYHg==", + "resolved": "1.40.10", + "contentHash": "oM4E+wGh3J+PnKyEgs22wPnn6mxx/IhchfkE2wDRxI9Xqf7kt2HhwDlwpUnNytCd6aWgeSWRdD6kOkweWpn+LA==", "dependencies": { "Microsoft.Testing.Extensions.CodeCoverage": "18.6.2", "Microsoft.Testing.Extensions.Telemetry": "2.2.1", "Microsoft.Testing.Extensions.TrxReport": "2.2.1", - "TUnit.Assertions": "1.40.5", - "TUnit.Engine": "1.40.5" + "TUnit.Assertions": "1.40.10", + "TUnit.Engine": "1.40.10" } }, "EnumerableAsyncProcessor": { @@ -172,30 +172,30 @@ }, "TUnit.Assertions": { "type": "Transitive", - "resolved": "1.40.5", - "contentHash": "VPxokYwqZcUH9F4v6LoL3V3blQ1q6xG5lAJ2BAmqzqb/gZ9CeUp/QbJImdlH9nAuMEEffFKQ0xDYwYkkJhG3tw==" + "resolved": "1.40.10", + "contentHash": "DCqMyNB0WcvUO675JQEQEKCi4fK/1oXuq2DrWqIf3ErZGXbDtuG+22jXCOaoxIv5WyD9L65LAY5Q9/4ufN9mdA==" }, "TUnit.Core": { "type": "Transitive", - "resolved": "1.40.5", - "contentHash": "NcO9LzoVc0RAx5Qv3WWC+w9fxQ6ojC+i2ZQf9z+IH+mHP79jQmXls7O6d0auG8Wa5mUYzaTlM6LTjwcdWU6v/Q==" + "resolved": "1.40.10", + "contentHash": "e09Mumq7XAnm2olh4/YzgrijNbJN5V0qMahFiyxblSrbibBv6uJFrEdaOZSn9wtds3zsE8mmcpTzCCm6MXEeXQ==" }, "TUnit.Engine": { "type": "Transitive", - "resolved": "1.40.5", - "contentHash": "MkkFwplhGd+CyuqRTT2p4LOJeiiRyVE5QdQHLuOTtqQVELN8CZuNYRklCh3xuzT58fRHxD66x2flZwG8OETR2w==", + "resolved": "1.40.10", + "contentHash": "WeIdqoON5bZfW1oGdT+KdePkfo7VHAhOUpakOY52rRuajvnNlCuKMWmzCxElKnlOtMSE2MSpVumT0nxbkS4sEw==", "dependencies": { "EnumerableAsyncProcessor": "3.8.4", "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.1", "Microsoft.Testing.Platform": "2.2.1", "Microsoft.Testing.Platform.MSBuild": "2.2.1", - "TUnit.Core": "1.40.5" + "TUnit.Core": "1.40.10" } }, "feff.testfixtures": { "type": "Project", "dependencies": { - "FEFF.TestFixtures.Abstractions": "[1.4.3, )", + "FEFF.TestFixtures.Abstractions": "[0.0.1, )", "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.7, )" } }, @@ -208,7 +208,7 @@ "feff.testfixtures.engine": { "type": "Project", "dependencies": { - "FEFF.TestFixtures.Abstractions": "[1.4.3, )", + "FEFF.TestFixtures.Abstractions": "[0.0.1, )", "Microsoft.Extensions.Configuration.EnvironmentVariables": "[10.0.7, )", "Microsoft.Extensions.DependencyInjection": "[10.0.7, )", "Microsoft.Extensions.Options": "[10.0.7, )", @@ -219,9 +219,9 @@ "feff.testfixtures.tunit": { "type": "Project", "dependencies": { - "FEFF.TestFixtures": "[1.4.3, )", - "FEFF.TestFixtures.Engine": "[1.4.3, )", - "TUnit.Core": "[1.40.5, )" + "FEFF.TestFixtures": "[0.0.1, )", + "FEFF.TestFixtures.Engine": "[0.0.1, )", + "TUnit.Core": "[1.40.10, )" } } } diff --git a/tests/Subjects/XunitV4.TestSubject/Infrastructure.cs b/tests/Subjects/XunitV4.TestSubject/Infrastructure.cs new file mode 100644 index 0000000..41f013d --- /dev/null +++ b/tests/Subjects/XunitV4.TestSubject/Infrastructure.cs @@ -0,0 +1,26 @@ +using System.Collections.Concurrent; +using System.Text; +using System.Text.Json; + +namespace FEFF.TestFixtures.XunitV4.Tests; + +public class Infrastructure +{ + private static ConcurrentDictionary _result = []; + + public static void Add(string s) + { + _result.AddOrUpdate(s, 1, (_, prev) => prev + 1); + } + + // Dispose _manager here. + [After(TestSession)] + public async static Task AfterS(TestSessionContext ctx) + { + var s = JsonSerializer.Serialize(_result); + + var fi = new FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location); + var d = fi.Directory!.FullName; + File.WriteAllText($"{d}/test-subject-result.json", s, Encoding.UTF8); + } +} diff --git a/tests/Subjects/XunitV4.TestSubject/TestSubject.cs b/tests/Subjects/XunitV4.TestSubject/TestSubject.cs new file mode 100644 index 0000000..bcce627 --- /dev/null +++ b/tests/Subjects/XunitV4.TestSubject/TestSubject.cs @@ -0,0 +1,74 @@ +using AwesomeAssertions; +using Microsoft.Extensions.DependencyInjection; + +namespace FEFF.TestFixtures.XunitV4.Tests; + +internal class BaseFix : IDisposable +{ + public void Dispose() + { + var name = this.GetType().Name; + Infrastructure.Add(name); + } +} + +[Fixture] +class TestFix : BaseFix { } +[Fixture] +class ClassFix : BaseFix { } +[Fixture] +class AssemblyFix : BaseFix { } +[Fixture] +class SessionFix : BaseFix { } + +class SingletonFix : BaseFix, IFixtureRegistrar +{ + public static void RegisterFixture(IServiceCollection services) + { + services.AddSingleton(); + } +} + +public class TestSubject +{ + protected static T GetFixture(FixtureScopeType scopeType = FixtureScopeType.TestCase) + where T : notnull + { + return TestContext.Current!.GetFeffFixture(scopeType); + } + + [Test] + public void Fixtures__should_be_registered_and_materialized() + { + var f1 = GetFixture(); + var f2 = GetFixture(FixtureScopeType.Class); + var f4 = GetFixture(FixtureScopeType.Assembly); + var f5 = GetFixture(FixtureScopeType.Session); + var s = GetFixture(); + + f1.Should().BeOfType(); + f2.Should().BeOfType(); + f4.Should().BeOfType(); + f5.Should().BeOfType(); + s.Should().BeOfType(); + } + + [Test] + public void Second_test_method() + { + var f1 = GetFixture(); + var f2 = GetFixture(FixtureScopeType.Class); + var f4 = GetFixture(FixtureScopeType.Assembly); + var f5 = GetFixture(FixtureScopeType.Session); + var s0 = GetFixture(); + + f1.Should().BeOfType(); + f2.Should().BeOfType(); + f4.Should().BeOfType(); + f5.Should().BeOfType(); + s0.Should().BeOfType(); + } +} + +[InheritsTests] +public class SecondTestSubject : TestSubject { } diff --git a/tests/Subjects/XunitV4.TestSubject/XunitV4.TestSubject.csproj b/tests/Subjects/XunitV4.TestSubject/XunitV4.TestSubject.csproj new file mode 100644 index 0000000..7ad4c40 --- /dev/null +++ b/tests/Subjects/XunitV4.TestSubject/XunitV4.TestSubject.csproj @@ -0,0 +1,20 @@ + + + + enable + enable + Exe + net10.0 + + + + + + + + + + + + + diff --git a/tests/Subjects/XunitV4.TestSubject/packages.lock.json b/tests/Subjects/XunitV4.TestSubject/packages.lock.json new file mode 100644 index 0000000..085a093 --- /dev/null +++ b/tests/Subjects/XunitV4.TestSubject/packages.lock.json @@ -0,0 +1,229 @@ +{ + "version": 1, + "dependencies": { + "net10.0": { + "AwesomeAssertions": { + "type": "Direct", + "requested": "[9.4.0, )", + "resolved": "9.4.0", + "contentHash": "dJxkWiQ8D+xT6Gr2sSL83+Mar+Vpy2JTcUPxFcckpPJ8VYBfSgnk+zqpS6t7kcGnjz8NLyF14qfuoL4bKzzoew==" + }, + "Microsoft.Testing.Extensions.CodeCoverage": { + "type": "Direct", + "requested": "[18.6.2, )", + "resolved": "18.6.2", + "contentHash": "vRDhB96XQyVdYFp4cQZOMz/lx0okfCdzTXPxGiuFhKx2yUL0FT/skTpnTv+7x13+tjNOcT39i2Ln3BYtslzf2w==", + "dependencies": { + "Microsoft.DiaSymReader": "2.2.5", + "Microsoft.Extensions.DependencyModel": "8.0.2", + "Microsoft.Testing.Platform": "2.1.0" + } + }, + "TUnit": { + "type": "Direct", + "requested": "[1.*, )", + "resolved": "1.40.10", + "contentHash": "oM4E+wGh3J+PnKyEgs22wPnn6mxx/IhchfkE2wDRxI9Xqf7kt2HhwDlwpUnNytCd6aWgeSWRdD6kOkweWpn+LA==", + "dependencies": { + "Microsoft.Testing.Extensions.CodeCoverage": "18.6.2", + "Microsoft.Testing.Extensions.Telemetry": "2.2.1", + "Microsoft.Testing.Extensions.TrxReport": "2.2.1", + "TUnit.Assertions": "1.40.10", + "TUnit.Engine": "1.40.10" + } + }, + "EnumerableAsyncProcessor": { + "type": "Transitive", + "resolved": "3.8.4", + "contentHash": "KlbpupRCz3Kf+P7gsiDvFXJ980i/9lfihMZFmmxIk0Gf6mopEjy74OTJZmdaKDQpE29eQDBnMZB5khyW3eugrg==" + }, + "Microsoft.ApplicationInsights": { + "type": "Transitive", + "resolved": "2.23.0", + "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw==" + }, + "Microsoft.DiaSymReader": { + "type": "Transitive", + "resolved": "2.2.5", + "contentHash": "Cq0DLpL8oQmXX3EUCClAYWDBy7Nf3Km6kmUw/eYWlYcTeC3g3Nekd/Z/ldsiy+Oi3xboanlQV9oaVCkgdLEhOQ==" + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "wZbGh7J8R1vXN525O6d8dlcDTxhRTnd5MyW4LdfP5S0tSnTwTCseYSrq6g0Mxh7W9xn8P/2xPuf0D/m6k2dy2w==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7", + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "t56nEgvECcyLPojZIUFWJknQQDAbgfTf9J+QMYJE1YYvVgz69vN6B/AKL8Grvj3Lcnp8kTpNqwmwFhb3YLJmtQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "8bS1qIaRivny+WX+49pmeJ6iAylbtX8C0DLEcCQWZjdxQvLqaMssXiGD9P/6pYElrHbK5/nAHmjbQ8STqdMYeg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.7", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "TWto3imA+mJMLZI+5sbgLiFFoOFNFkizQYNaC5jTuiHKn3diwm1RN7mWDOEZN9kG2bixw7IvgpvtUG5/teSRzA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.7", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "91F/o3emPV/+xY/ip3s2LqDNF14kjttlVtq0BXgg6p4MnCzeSZxnUJm+t6WRrtD3JdGo88/oX+z7OwK4y8PZuw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "Z6mfFEaFcwCfSboxJwOLfu7/31npCY9q70WUamHW/vRQhDvBKOT4Vf9YkZj5J6hLvJpb0oDEYfHunQZj0xxvKw==" + }, + "Microsoft.Extensions.DependencyModel": { + "type": "Transitive", + "resolved": "8.0.2", + "contentHash": "mUBDZZRgZrSyFOsJ2qJJ9fXfqd/kXJwf3AiDoqLD9m6TjY5OO/vLNOb9fb4juC0487eq4hcGN/M2Rh/CKS7QYw==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "00SHUGTh2jSMvIr6x9Xwd2nE+B5/qFCO/9hDwUDhJsjYRDlADmaBZ7tqehXzBDsfjHSXJzuRHJzPYPPjphBQ7Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7", + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "IT7f+EMXZtkjatEcF+o6aOw/7OE4etRrMiDGEWH/iiTu2R3uhC4NEQJCfHiibtX45U3sIQ5Fh6tbb1qaOz3YAg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7", + "Microsoft.Extensions.Configuration.Binder": "10.0.7", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7", + "Microsoft.Extensions.Options": "10.0.7", + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "D5M0Jr551iTgwkZMN9rm0pSkgNLj5quUWQUmQPMZh7k/bnvZTnXRGfE2KuvXf1EEjt/ofD9yw9IumpgdP9QCnw==" + }, + "Microsoft.Testing.Extensions.Telemetry": { + "type": "Transitive", + "resolved": "2.2.1", + "contentHash": "7zB8BjffOyvqfHF26rFVPuK0w1fCf5+j1tLuhHIr76CqxXkGb+fMJtq6YNOV+m6qPytExHMXxluk3RgJ+dSIqw==", + "dependencies": { + "Microsoft.ApplicationInsights": "2.23.0", + "Microsoft.Testing.Platform": "2.2.1" + } + }, + "Microsoft.Testing.Extensions.TrxReport": { + "type": "Transitive", + "resolved": "2.2.1", + "contentHash": "FWaktPQHSiZh/+2ft2PHH/4bLlg8BKlrbLiil8mRcpoP0oHzKpgBfmN3QepGlAbxG0yDrZGN8tuPy77FYdEaMw==", + "dependencies": { + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.1", + "Microsoft.Testing.Platform": "2.2.1" + } + }, + "Microsoft.Testing.Extensions.TrxReport.Abstractions": { + "type": "Transitive", + "resolved": "2.2.1", + "contentHash": "RD6D1Jx6cKDA5IHd1H2q8ylIuQG3PD+gdULI0JC8CvsRtaypFzTFpB5xDPuQi8o6kAkcM04cBhAiJPxZboNH2Q==", + "dependencies": { + "Microsoft.Testing.Platform": "2.2.1" + } + }, + "Microsoft.Testing.Platform": { + "type": "Transitive", + "resolved": "2.2.1", + "contentHash": "9bbPuls/b6/vUFzxbSjJLZlJHyKBfOZE5kjIY+ITI2ASqlFPJhR83BdLydJeQOCLEZhEbrEcz5xtt1B69nwSVg==" + }, + "Microsoft.Testing.Platform.MSBuild": { + "type": "Transitive", + "resolved": "2.2.1", + "contentHash": "CSJOcZHfKlTyPbS0CTJk6iEnU4gJC+eUA5z72UBnMDRdgVHYOmB8k9Y7jT233gZjnCOQiYFg3acQHRfu2H62nw==", + "dependencies": { + "Microsoft.Testing.Platform": "2.2.1" + } + }, + "System.Reflection.MetadataLoadContext": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "XDkKntYPUaANhLVdo7AHbrJ+QbUAY5u/T7lG5rHSx5kLxeHceoc3JcIMVAc/vZT+3rbwFYlrdnikxdi6G+2nvA==" + }, + "TUnit.Assertions": { + "type": "Transitive", + "resolved": "1.40.10", + "contentHash": "DCqMyNB0WcvUO675JQEQEKCi4fK/1oXuq2DrWqIf3ErZGXbDtuG+22jXCOaoxIv5WyD9L65LAY5Q9/4ufN9mdA==" + }, + "TUnit.Core": { + "type": "Transitive", + "resolved": "1.40.10", + "contentHash": "e09Mumq7XAnm2olh4/YzgrijNbJN5V0qMahFiyxblSrbibBv6uJFrEdaOZSn9wtds3zsE8mmcpTzCCm6MXEeXQ==" + }, + "TUnit.Engine": { + "type": "Transitive", + "resolved": "1.40.10", + "contentHash": "WeIdqoON5bZfW1oGdT+KdePkfo7VHAhOUpakOY52rRuajvnNlCuKMWmzCxElKnlOtMSE2MSpVumT0nxbkS4sEw==", + "dependencies": { + "EnumerableAsyncProcessor": "3.8.4", + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.1", + "Microsoft.Testing.Platform": "2.2.1", + "Microsoft.Testing.Platform.MSBuild": "2.2.1", + "TUnit.Core": "1.40.10" + } + }, + "feff.testfixtures": { + "type": "Project", + "dependencies": { + "FEFF.TestFixtures.Abstractions": "[0.0.1, )", + "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.7, )" + } + }, + "feff.testfixtures.abstractions": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "[10.0.7, )" + } + }, + "feff.testfixtures.engine": { + "type": "Project", + "dependencies": { + "FEFF.TestFixtures.Abstractions": "[0.0.1, )", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "[10.0.7, )", + "Microsoft.Extensions.DependencyInjection": "[10.0.7, )", + "Microsoft.Extensions.Options": "[10.0.7, )", + "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.7, )", + "System.Reflection.MetadataLoadContext": "[10.0.7, )" + } + }, + "feff.testfixtures.xunitv4": { + "type": "Project", + "dependencies": { + "FEFF.TestFixtures": "[0.0.1, )", + "FEFF.TestFixtures.Engine": "[0.0.1, )", + "TUnit.Core": "[1.40.10, )" + } + } + } + } +} \ No newline at end of file From 2f79d2ffa840392ef2dd03367859b89b216e54e3 Mon Sep 17 00:00:00 2001 From: metacoder_FEFF Date: Wed, 29 Apr 2026 10:49:49 +0000 Subject: [PATCH 06/11] work on XunitV4 --- NuGet.Config | 12 ++ .../packages.lock.json | 140 +++++++++++++++ .../Extension/Enums.cs | 27 +++ .../Extension/TestContextExtensions.cs | 29 +++ .../TestFixturesExtensionAttribute.cs | 27 +++ .../FEFF.TestFixtures.XunitV4.csproj | 2 +- .../GlobalHooksExtension.cs | 165 ------------------ .../Internal/FixtureAdapter.cs | 90 ++++++++++ .../Internal/ScopeIdHelper.cs | 30 ++++ .../packages.lock.json | 48 ++++- .../ApiVerificationTests.cs | 3 +- ....TestFixtures.ApiVerification.Tests.csproj | 2 +- .../packages.lock.json | 34 ++-- .../FEFF.TestFixtures.XunitV4.Tests.csproj | 2 +- .../XunitV4IntegrationTests.cs | 9 +- .../packages.lock.json | 107 ++++++++---- .../FEFF.TestFixtures.XunitV4.verified.txt | 29 --- .../XunitV4.TestSubject/Infrastructure.cs | 2 +- .../XunitV4.TestSubject/TestSubject.cs | 42 +++-- .../XunitV4.TestSubject.csproj | 2 +- .../XunitV4.TestSubject/packages.lock.json | 105 +++++++---- 21 files changed, 589 insertions(+), 318 deletions(-) create mode 100644 NuGet.Config create mode 100644 src/FEFF.TestFixtures.XunitV4/Extension/Enums.cs create mode 100644 src/FEFF.TestFixtures.XunitV4/Extension/TestContextExtensions.cs create mode 100644 src/FEFF.TestFixtures.XunitV4/Extension/TestFixturesExtensionAttribute.cs delete mode 100644 src/FEFF.TestFixtures.XunitV4/GlobalHooksExtension.cs create mode 100644 src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs create mode 100644 src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs delete mode 100644 tests/Files/API/FEFF.TestFixtures.XunitV4.verified.txt diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000..b641abc --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/FEFF.TestFixtures.TUnit/packages.lock.json b/src/FEFF.TestFixtures.TUnit/packages.lock.json index d85fe0a..471d442 100644 --- a/src/FEFF.TestFixtures.TUnit/packages.lock.json +++ b/src/FEFF.TestFixtures.TUnit/packages.lock.json @@ -1,6 +1,146 @@ { "version": 1, "dependencies": { + "net10.0": { + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[10.0.203, )", + "resolved": "10.0.203", + "contentHash": "R4Tvr1oACImMS+Y5M7NM07ll9QyJSKnki3Dvz8QwG1W6FEmd+9fmZXAF6BE6UPswHF6n0v41wgMQGlaudOspqA==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "10.0.203", + "Microsoft.SourceLink.Common": "10.0.203", + "System.IO.Hashing": "10.0.7" + } + }, + "TUnit.Core": { + "type": "Direct", + "requested": "[1.40.10, )", + "resolved": "1.40.10", + "contentHash": "e09Mumq7XAnm2olh4/YzgrijNbJN5V0qMahFiyxblSrbibBv6uJFrEdaOZSn9wtds3zsE8mmcpTzCCm6MXEeXQ==" + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "10.0.203", + "contentHash": "m56WtzvIcL6t7JR3c7ogYitHizNM2QnRSo8yqxrQi+m5E/GGyDEmqymP+2p6YsFXn0j/Tzz67s4FQnrTLC7GKQ==", + "dependencies": { + "System.IO.Hashing": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "wZbGh7J8R1vXN525O6d8dlcDTxhRTnd5MyW4LdfP5S0tSnTwTCseYSrq6g0Mxh7W9xn8P/2xPuf0D/m6k2dy2w==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7", + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "t56nEgvECcyLPojZIUFWJknQQDAbgfTf9J+QMYJE1YYvVgz69vN6B/AKL8Grvj3Lcnp8kTpNqwmwFhb3YLJmtQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "8bS1qIaRivny+WX+49pmeJ6iAylbtX8C0DLEcCQWZjdxQvLqaMssXiGD9P/6pYElrHbK5/nAHmjbQ8STqdMYeg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.7", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "TWto3imA+mJMLZI+5sbgLiFFoOFNFkizQYNaC5jTuiHKn3diwm1RN7mWDOEZN9kG2bixw7IvgpvtUG5/teSRzA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.7", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "91F/o3emPV/+xY/ip3s2LqDNF14kjttlVtq0BXgg6p4MnCzeSZxnUJm+t6WRrtD3JdGo88/oX+z7OwK4y8PZuw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "Z6mfFEaFcwCfSboxJwOLfu7/31npCY9q70WUamHW/vRQhDvBKOT4Vf9YkZj5J6hLvJpb0oDEYfHunQZj0xxvKw==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "00SHUGTh2jSMvIr6x9Xwd2nE+B5/qFCO/9hDwUDhJsjYRDlADmaBZ7tqehXzBDsfjHSXJzuRHJzPYPPjphBQ7Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7", + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "IT7f+EMXZtkjatEcF+o6aOw/7OE4etRrMiDGEWH/iiTu2R3uhC4NEQJCfHiibtX45U3sIQ5Fh6tbb1qaOz3YAg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.7", + "Microsoft.Extensions.Configuration.Binder": "10.0.7", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7", + "Microsoft.Extensions.Options": "10.0.7", + "Microsoft.Extensions.Primitives": "10.0.7" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "D5M0Jr551iTgwkZMN9rm0pSkgNLj5quUWQUmQPMZh7k/bnvZTnXRGfE2KuvXf1EEjt/ofD9yw9IumpgdP9QCnw==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "10.0.203", + "contentHash": "QYAnhBCOkT3ZUT/fHag11+bamwlbZ3U9Vi/WfKrD9emdUf1t3aqjWv0V2KtEGHSRSC81aBc8Oy/mvyGpEYd9Pg==" + }, + "System.IO.Hashing": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "6hsjdSr4VOXSOnhALkYplHpAxnTG1J33YN42IB6nH2fEg4QnJqrZ4Ft+qn7mkrKAOYC8pCSFYwVWw6rQbmwgLQ==" + }, + "System.Reflection.MetadataLoadContext": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "XDkKntYPUaANhLVdo7AHbrJ+QbUAY5u/T7lG5rHSx5kLxeHceoc3JcIMVAc/vZT+3rbwFYlrdnikxdi6G+2nvA==" + }, + "feff.testfixtures": { + "type": "Project", + "dependencies": { + "FEFF.TestFixtures.Abstractions": "[0.0.1, )", + "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.7, )" + } + }, + "feff.testfixtures.abstractions": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "[10.0.7, )" + } + }, + "feff.testfixtures.engine": { + "type": "Project", + "dependencies": { + "FEFF.TestFixtures.Abstractions": "[0.0.1, )", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "[10.0.7, )", + "Microsoft.Extensions.DependencyInjection": "[10.0.7, )", + "Microsoft.Extensions.Options": "[10.0.7, )", + "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.7, )", + "System.Reflection.MetadataLoadContext": "[10.0.7, )" + } + } + }, "net8.0": { "Microsoft.SourceLink.GitHub": { "type": "Direct", diff --git a/src/FEFF.TestFixtures.XunitV4/Extension/Enums.cs b/src/FEFF.TestFixtures.XunitV4/Extension/Enums.cs new file mode 100644 index 0000000..b70c294 --- /dev/null +++ b/src/FEFF.TestFixtures.XunitV4/Extension/Enums.cs @@ -0,0 +1,27 @@ +namespace FEFF.TestFixtures.Xunit; + +/// +/// Defines the lifetime scope for a fixture in xUnit v3 tests. +/// +public enum FixtureScopeType +{ + /// + /// The fixture is scoped to an individual test case. + /// + TestCase, + + /// + /// The fixture is scoped to a test class. + /// + Class, + + /// + /// The fixture is scoped to a test collection. + /// + Collection, + + /// + /// The fixture is scoped to the entire test assembly. + /// + Assembly +} diff --git a/src/FEFF.TestFixtures.XunitV4/Extension/TestContextExtensions.cs b/src/FEFF.TestFixtures.XunitV4/Extension/TestContextExtensions.cs new file mode 100644 index 0000000..9dc0dac --- /dev/null +++ b/src/FEFF.TestFixtures.XunitV4/Extension/TestContextExtensions.cs @@ -0,0 +1,29 @@ +using FEFF.TestFixtures.Xunit; +using FEFF.TestFixtures.Xunit.Internal; + +namespace Xunit.v3; + +/// +/// Extension methods for to resolve FEFF.TestFixtures fixtures in xUnit v3. +/// +public static class TestContextExtensions +{ + /// + /// Resolves a fixture from the specified scope within the test context. + /// + /// The type of fixture to resolve. + /// The current xUnit test context. + /// The lifetime scope for the fixture. Defaults to . + /// The resolved fixture instance. + /// Thrown when the extension is not properly registered. + public static T GetFeffFixture(this ITestContext ctx, FixtureScopeType scopeType = FixtureScopeType.TestCase) + where T : notnull + { + var scopeId = ScopeIdHelper.GetScopeId(ctx, scopeType); + + return FixtureAdapter + .GetCurrent(ctx) + .GetScope(scopeId) + .GetFixture(); + } +} diff --git a/src/FEFF.TestFixtures.XunitV4/Extension/TestFixturesExtensionAttribute.cs b/src/FEFF.TestFixtures.XunitV4/Extension/TestFixturesExtensionAttribute.cs new file mode 100644 index 0000000..1bfa6be --- /dev/null +++ b/src/FEFF.TestFixtures.XunitV4/Extension/TestFixturesExtensionAttribute.cs @@ -0,0 +1,27 @@ +using FEFF.TestFixtures.Xunit.Internal; +using Xunit; +using Xunit.v3; + +namespace FEFF.TestFixtures.Xunit; + +//TODO: revert xml comment +//TestContextExtensions.GetFeffFixture{T} + +/// +/// Manages for xUnit tests.
+/// Enables the use of . +///
+/// +/// Apply this attribute at the assembly level in an AssemblyInfo.cs or any source file: +/// [assembly: TestFixturesExtension] +/// +// [AttributeUsage(AttributeTargets.Assembly)] +public class TestFixturesExtensionAttribute : AssemblyFixtureAttribute +{ +//TODO: add comment + /// + /// + public TestFixturesExtensionAttribute() : base(typeof(FixtureAdapter)) + { + } +} diff --git a/src/FEFF.TestFixtures.XunitV4/FEFF.TestFixtures.XunitV4.csproj b/src/FEFF.TestFixtures.XunitV4/FEFF.TestFixtures.XunitV4.csproj index 318c679..8d0db41 100644 --- a/src/FEFF.TestFixtures.XunitV4/FEFF.TestFixtures.XunitV4.csproj +++ b/src/FEFF.TestFixtures.XunitV4/FEFF.TestFixtures.XunitV4.csproj @@ -13,7 +13,7 @@
- + diff --git a/src/FEFF.TestFixtures.XunitV4/GlobalHooksExtension.cs b/src/FEFF.TestFixtures.XunitV4/GlobalHooksExtension.cs deleted file mode 100644 index dd3d535..0000000 --- a/src/FEFF.TestFixtures.XunitV4/GlobalHooksExtension.cs +++ /dev/null @@ -1,165 +0,0 @@ -using FEFF.Extensions; - -namespace FEFF.TestFixtures.XunitV4; - -using Engine; - -/// -/// Defines the lifetime scope for a fixture in TUnit tests. -/// -public enum FixtureScopeType -{ - /// - /// The fixture is scoped to an individual test case. - /// - TestCase, - - /// - /// The fixture is scoped to a test class. - /// - Class, - - /// - /// The fixture is scoped to a test assembly. - /// - Assembly, - - /// - /// The fixture is scoped to the entire test session. - /// - Session -} - -/// -/// Provides TUnit integration hooks for the FEFF.TestFixtures framework. -/// Automatically manages fixture lifecycles across test, class, assembly, and session scopes. -/// -public static class GlobalHooksExtension -{ - //TODO: multiple sessions??? - // => scopeId - // => dispose - -#if NET9_0_OR_GREATER - private static readonly Lock _lock = new(); -#else - private static readonly Object _lock = new(); -#endif - private static volatile FixtureManager? _manager; - - /// - /// Resolves a fixture from the specified scope within the test context. - /// - /// The type of fixture to resolve. - /// The current TUnit test context. - /// The lifetime scope for the fixture. Defaults to . - /// The resolved fixture instance. - /// Thrown when is null. - public static T GetFeffFixture(this TestContext ctx, FixtureScopeType scopeType = FixtureScopeType.TestCase) - where T : notnull - { - ArgumentNullException.ThrowIfNull(ctx); - - var m = GetManager(); - var id = GetScopeId(ctx, scopeType); - return m.GetScope(id).GetFixture(); - } - - private static string GetScopeId(TestContext ctx, FixtureScopeType scopeType) => scopeType switch - { - FixtureScopeType.TestCase => GetScopeId(ctx), - FixtureScopeType.Class => GetScopeId(ctx.ClassContext), - FixtureScopeType.Assembly => GetScopeId(ctx.ClassContext.AssemblyContext), - FixtureScopeType.Session => GetScopeId(ctx.ClassContext.AssemblyContext.TestSessionContext), - _ => - throw EnumMatchException.From(scopeType) - }; - - private static string GetScopeId(string ctxId, FixtureScopeType scopeType) => - $"{scopeType}-{ctxId}"; - - private static string GetScopeId(TestContext ctx) => - GetScopeId(ctx.Id, FixtureScopeType.TestCase); - - private static string GetScopeId(ClassHookContext ctx) => - GetScopeId($"{ctx.ClassType.FullName}_{ctx.ClassType.Assembly.FullName}", FixtureScopeType.Class); - - private static string GetScopeId(AssemblyHookContext ctx) => - GetScopeId(ctx.Assembly.FullName!, FixtureScopeType.Assembly); - - private static string GetScopeId(TestSessionContext ctx) => - GetScopeId(ctx.Id, FixtureScopeType.Session); - - private static FixtureManager GetManager() - { - // double-check: optimization - if (_manager != null) - return _manager; - - lock (_lock) - { - // double-check: guard - _manager ??= new FixtureManagerBuilder().Build(); - } - return _manager; - } - - private static ValueTask RemoveScope(FixtureManager? manager, string id) - { - if (manager == null) - return ValueTask.CompletedTask; - - return manager.RemoveScopeAsync(id); - } - - /// - /// TUnit hook executed after every test case. Disposes the test-case-scoped fixtures. - /// - /// The test context. - [AfterEvery(Test)] - public async static Task AfterT(TestContext ctx) - { - var id = GetScopeId(ctx); - await RemoveScope(_manager, id).ConfigureAwait(false); - } - - /// - /// TUnit hook executed after every test class. Disposes the class-scoped fixtures. - /// - /// The class hook context. - [AfterEvery(Class)] - public async static Task AfterC(ClassHookContext ctx) - { - var id = GetScopeId(ctx); - await RemoveScope(_manager, id).ConfigureAwait(false); - } - - /// - /// TUnit hook executed after every test assembly. Disposes the assembly-scoped fixtures. - /// - /// The assembly hook context. - [AfterEvery(Assembly)] - public async static Task AfterA(AssemblyHookContext ctx) - { - var id = GetScopeId(ctx); - await RemoveScope(_manager, id).ConfigureAwait(false); - } - - /// - /// TUnit hook executed after the test session. Disposes the session-scoped fixture - /// and the underlying . - /// - /// The test session context. - [After(TestSession)] - public async static Task AfterS(TestSessionContext ctx) - { - var m = Interlocked.Exchange(ref _manager, null); - - var id = GetScopeId(ctx); - await RemoveScope(m, id).ConfigureAwait(false); - - // dispose both: manager & assemblyFixtureScope - if (m != null) - await m.DisposeAsync().ConfigureAwait(false); - } -} diff --git a/src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs b/src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs new file mode 100644 index 0000000..ae50431 --- /dev/null +++ b/src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs @@ -0,0 +1,90 @@ +using FEFF.Extensions; +using FEFF.TestFixtures.Engine; +using Xunit; +using Xunit.v3; + +namespace FEFF.TestFixtures.Xunit.Internal; + +internal class FixtureAdapter + : INotifyTestAssemblyLifecycleAsync + , INotifyTestCollectionLifecycleAsync + , INotifyTestClassLifecycleAsync + , INotifyTestCaseLifecycleAsync + , IAsyncDisposable +{ + private readonly FixtureManager _fixtureManager; + + public FixtureAdapter() + { + _fixtureManager = new FixtureManagerBuilder().Build(); + SetCurrent(TestContext.Current, this); + } + + public ValueTask DisposeAsync() + { + return _fixtureManager.DisposeAsync(); + } + + public ValueTask OnTestAssemblyFinishedAsync(IXunitTestAssembly testAssembly) + { + // TODO: use arg + var id = ScopeIdHelper.GetScopeId(TestContext.Current, FixtureScopeType.Assembly); + return _fixtureManager.RemoveScopeAsync(id); + } + public ValueTask OnTestCollectionFinishedAsync(IXunitTestCollection testCollection) + { + // TODO: use arg + var id = ScopeIdHelper.GetScopeId(TestContext.Current, FixtureScopeType.Collection); + return _fixtureManager.RemoveScopeAsync(id); + } + public ValueTask OnTestClassFinishedAsync(IXunitTestClass testClass) + { + // TODO: use arg + var id = ScopeIdHelper.GetScopeId(TestContext.Current, FixtureScopeType.Class); + return _fixtureManager.RemoveScopeAsync(id); + } + public ValueTask OnTestCaseFinishedAsync(IXunitTestCase testCase) + { + // TODO: use arg + var id = ScopeIdHelper.GetScopeId(TestContext.Current, FixtureScopeType.TestCase); + return _fixtureManager.RemoveScopeAsync(id); + } + + public ValueTask OnTestAssemblyStartingAsync(IXunitTestAssembly testAssembly) => ValueTask.CompletedTask; + public ValueTask OnTestCaseStartingAsync(IXunitTestCase testCase) => ValueTask.CompletedTask; + public ValueTask OnTestClassStartingAsync(IXunitTestClass testClass) => ValueTask.CompletedTask; + public ValueTask OnTestCollectionStartingAsync(IXunitTestCollection testCollection) => ValueTask.CompletedTask; + + internal IFixtureScope GetScope(string scopeId) + { + return _fixtureManager.GetScope(scopeId); + } + + #region static + + private static void SetCurrent(ITestContext ctx, FixtureAdapter obj) + { + var key = GetKey(ctx); + var added = ctx.KeyValueStorage.TryAdd(key, obj); + if (added == false) + throw new InvalidOperationException("Another value exists in 'TestContext.KeyValueStorage'."); + } + + private static string GetKey(ITestContext ctx) + { + var testAssembly = ctx.TestAssembly; + ThrowHelper.Assert(testAssembly is not null); + + return $"{nameof(FixtureAdapter)}-{testAssembly.UniqueID}"; + } + + internal static FixtureAdapter GetCurrent(ITestContext ctx) + { + var key = GetKey(ctx); + _ = ctx.KeyValueStorage.TryGetValue(key, out var res); + + return res as FixtureAdapter + ?? throw new InvalidOperationException($"Extension is not registered. Use '[assembly: {nameof(TestFixturesExtensionAttribute)}]'."); + } + #endregion +} \ No newline at end of file diff --git a/src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs b/src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs new file mode 100644 index 0000000..d166d20 --- /dev/null +++ b/src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs @@ -0,0 +1,30 @@ +using FEFF.Extensions; +using Xunit; + +namespace FEFF.TestFixtures.Xunit.Internal; + +internal static class ScopeIdHelper +{ + internal static string GetScopeId(ITestContext ctx, FixtureScopeType scopeType) + { + var id = GetId(scopeType, ctx); + return GetScopeId(scopeType, id); + } + + private static string GetScopeId(FixtureScopeType scopeType, string id) => $"{scopeType}-{id}"; + + private static string GetId(FixtureScopeType scopeType, ITestContext testContext) + { + return scopeType switch + { + FixtureScopeType.TestCase => ThrowHelper.EnsureNotNull(testContext.Test).UniqueID, + FixtureScopeType.Class => ThrowHelper.EnsureNotNull(testContext.TestClass).UniqueID, + FixtureScopeType.Collection => ThrowHelper.EnsureNotNull(testContext.TestCollection).UniqueID, + FixtureScopeType.Assembly => ThrowHelper.EnsureNotNull(testContext.TestAssembly).UniqueID, + _ => + throw EnumMatchException.From(scopeType) + }; + } + + // internal static string GetTestCaseScopeId(IXunitTest test) => GetScopeId(FixtureScopeType.TestCase, test.UniqueID); +} \ No newline at end of file diff --git a/src/FEFF.TestFixtures.XunitV4/packages.lock.json b/src/FEFF.TestFixtures.XunitV4/packages.lock.json index 471d442..a899d19 100644 --- a/src/FEFF.TestFixtures.XunitV4/packages.lock.json +++ b/src/FEFF.TestFixtures.XunitV4/packages.lock.json @@ -13,11 +13,19 @@ "System.IO.Hashing": "10.0.7" } }, - "TUnit.Core": { + "xunit.v3.extensibility.core": { "type": "Direct", - "requested": "[1.40.10, )", - "resolved": "1.40.10", - "contentHash": "e09Mumq7XAnm2olh4/YzgrijNbJN5V0qMahFiyxblSrbibBv6uJFrEdaOZSn9wtds3zsE8mmcpTzCCm6MXEeXQ==" + "requested": "[4.0.0-pre.103, )", + "resolved": "4.0.0-pre.103", + "contentHash": "WVO4mpv2HErpqe5yqYXUXeVZz+Hx7E3JYrVdOMtVuirrXk3ls3i1zRoyGrhQY+thzY5K3beww/8h8B9Qis0S/Q==", + "dependencies": { + "xunit.v3.common": "[4.0.0-pre.103]" + } + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", @@ -116,6 +124,14 @@ "resolved": "10.0.7", "contentHash": "XDkKntYPUaANhLVdo7AHbrJ+QbUAY5u/T7lG5rHSx5kLxeHceoc3JcIMVAc/vZT+3rbwFYlrdnikxdi6G+2nvA==" }, + "xunit.v3.common": { + "type": "Transitive", + "resolved": "4.0.0-pre.103", + "contentHash": "9d6oYJqTNLG0/a2n2sR7i5ChJx9xV9DH287u1SqiFZ8imx+Cua7bMuObNANOlvEd5EpTXQjCOC+p9JoULdzF1w==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "6.0.0" + } + }, "feff.testfixtures": { "type": "Project", "dependencies": { @@ -153,11 +169,19 @@ "System.IO.Hashing": "10.0.7" } }, - "TUnit.Core": { + "xunit.v3.extensibility.core": { "type": "Direct", - "requested": "[1.40.10, )", - "resolved": "1.40.10", - "contentHash": "e09Mumq7XAnm2olh4/YzgrijNbJN5V0qMahFiyxblSrbibBv6uJFrEdaOZSn9wtds3zsE8mmcpTzCCm6MXEeXQ==" + "requested": "[4.0.0-pre.103, )", + "resolved": "4.0.0-pre.103", + "contentHash": "WVO4mpv2HErpqe5yqYXUXeVZz+Hx7E3JYrVdOMtVuirrXk3ls3i1zRoyGrhQY+thzY5K3beww/8h8B9Qis0S/Q==", + "dependencies": { + "xunit.v3.common": "[4.0.0-pre.103]" + } + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", @@ -273,6 +297,14 @@ "System.Reflection.Metadata": "10.0.7" } }, + "xunit.v3.common": { + "type": "Transitive", + "resolved": "4.0.0-pre.103", + "contentHash": "9d6oYJqTNLG0/a2n2sR7i5ChJx9xV9DH287u1SqiFZ8imx+Cua7bMuObNANOlvEd5EpTXQjCOC+p9JoULdzF1w==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "6.0.0" + } + }, "feff.testfixtures": { "type": "Project", "dependencies": { diff --git a/tests/FEFF.TestFixtures.ApiVerification.Tests/ApiVerificationTests.cs b/tests/FEFF.TestFixtures.ApiVerification.Tests/ApiVerificationTests.cs index 9a236f6..3455f54 100644 --- a/tests/FEFF.TestFixtures.ApiVerification.Tests/ApiVerificationTests.cs +++ b/tests/FEFF.TestFixtures.ApiVerification.Tests/ApiVerificationTests.cs @@ -20,7 +20,8 @@ public class ApiVerificationTests [InlineData("FEFF.TestFixtures.AspNetCore.EF")] [InlineData("FEFF.TestFixtures.AspNetCore.SignalR")] [InlineData("FEFF.TestFixtures.XunitV3")] - [InlineData("FEFF.TestFixtures.XunitV4")] + // TODO: test without project reference + // [InlineData("FEFF.TestFixtures.XunitV4")] [InlineData("FEFF.TestFixtures.TUnit")] public Task API_should_not_change(string assemblyName) { diff --git a/tests/FEFF.TestFixtures.ApiVerification.Tests/FEFF.TestFixtures.ApiVerification.Tests.csproj b/tests/FEFF.TestFixtures.ApiVerification.Tests/FEFF.TestFixtures.ApiVerification.Tests.csproj index 5678d7c..148d080 100644 --- a/tests/FEFF.TestFixtures.ApiVerification.Tests/FEFF.TestFixtures.ApiVerification.Tests.csproj +++ b/tests/FEFF.TestFixtures.ApiVerification.Tests/FEFF.TestFixtures.ApiVerification.Tests.csproj @@ -40,11 +40,11 @@ - +
diff --git a/tests/FEFF.TestFixtures.TUnit.Tests/packages.lock.json b/tests/FEFF.TestFixtures.TUnit.Tests/packages.lock.json index 77bcd31..523868b 100644 --- a/tests/FEFF.TestFixtures.TUnit.Tests/packages.lock.json +++ b/tests/FEFF.TestFixtures.TUnit.Tests/packages.lock.json @@ -32,14 +32,14 @@ "TUnit": { "type": "Direct", "requested": "[1.*, )", - "resolved": "1.40.5", - "contentHash": "5wEPNdEG3Ixwyo1p/dkyrXXygAAN05EANBYq1b9LwtR3dPCA1Hmx9KSyVNMplcBs73Iz0sags9rdIvCLq9BYHg==", + "resolved": "1.40.10", + "contentHash": "oM4E+wGh3J+PnKyEgs22wPnn6mxx/IhchfkE2wDRxI9Xqf7kt2HhwDlwpUnNytCd6aWgeSWRdD6kOkweWpn+LA==", "dependencies": { "Microsoft.Testing.Extensions.CodeCoverage": "18.6.2", "Microsoft.Testing.Extensions.Telemetry": "2.2.1", "Microsoft.Testing.Extensions.TrxReport": "2.2.1", - "TUnit.Assertions": "1.40.5", - "TUnit.Engine": "1.40.5" + "TUnit.Assertions": "1.40.10", + "TUnit.Engine": "1.40.10" } }, "EnumerableAsyncProcessor": { @@ -187,30 +187,30 @@ }, "TUnit.Assertions": { "type": "Transitive", - "resolved": "1.40.5", - "contentHash": "VPxokYwqZcUH9F4v6LoL3V3blQ1q6xG5lAJ2BAmqzqb/gZ9CeUp/QbJImdlH9nAuMEEffFKQ0xDYwYkkJhG3tw==" + "resolved": "1.40.10", + "contentHash": "DCqMyNB0WcvUO675JQEQEKCi4fK/1oXuq2DrWqIf3ErZGXbDtuG+22jXCOaoxIv5WyD9L65LAY5Q9/4ufN9mdA==" }, "TUnit.Core": { "type": "Transitive", - "resolved": "1.40.5", - "contentHash": "NcO9LzoVc0RAx5Qv3WWC+w9fxQ6ojC+i2ZQf9z+IH+mHP79jQmXls7O6d0auG8Wa5mUYzaTlM6LTjwcdWU6v/Q==" + "resolved": "1.40.10", + "contentHash": "e09Mumq7XAnm2olh4/YzgrijNbJN5V0qMahFiyxblSrbibBv6uJFrEdaOZSn9wtds3zsE8mmcpTzCCm6MXEeXQ==" }, "TUnit.Engine": { "type": "Transitive", - "resolved": "1.40.5", - "contentHash": "MkkFwplhGd+CyuqRTT2p4LOJeiiRyVE5QdQHLuOTtqQVELN8CZuNYRklCh3xuzT58fRHxD66x2flZwG8OETR2w==", + "resolved": "1.40.10", + "contentHash": "WeIdqoON5bZfW1oGdT+KdePkfo7VHAhOUpakOY52rRuajvnNlCuKMWmzCxElKnlOtMSE2MSpVumT0nxbkS4sEw==", "dependencies": { "EnumerableAsyncProcessor": "3.8.4", "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.1", "Microsoft.Testing.Platform": "2.2.1", "Microsoft.Testing.Platform.MSBuild": "2.2.1", - "TUnit.Core": "1.40.5" + "TUnit.Core": "1.40.10" } }, "feff.testfixtures": { "type": "Project", "dependencies": { - "FEFF.TestFixtures.Abstractions": "[1.4.3, )", + "FEFF.TestFixtures.Abstractions": "[0.0.1, )", "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.7, )" } }, @@ -223,7 +223,7 @@ "feff.testfixtures.engine": { "type": "Project", "dependencies": { - "FEFF.TestFixtures.Abstractions": "[1.4.3, )", + "FEFF.TestFixtures.Abstractions": "[0.0.1, )", "Microsoft.Extensions.Configuration.EnvironmentVariables": "[10.0.7, )", "Microsoft.Extensions.DependencyInjection": "[10.0.7, )", "Microsoft.Extensions.Options": "[10.0.7, )", @@ -234,16 +234,16 @@ "feff.testfixtures.tunit": { "type": "Project", "dependencies": { - "FEFF.TestFixtures": "[1.4.3, )", - "FEFF.TestFixtures.Engine": "[1.4.3, )", - "TUnit.Core": "[1.40.5, )" + "FEFF.TestFixtures": "[0.0.1, )", + "FEFF.TestFixtures.Engine": "[0.0.1, )", + "TUnit.Core": "[1.40.10, )" } }, "tunit.testsubject": { "type": "Project", "dependencies": { "AwesomeAssertions": "[9.4.0, )", - "FEFF.TestFixtures.TUnit": "[1.4.3, )", + "FEFF.TestFixtures.TUnit": "[0.0.1, )", "Microsoft.Testing.Extensions.CodeCoverage": "[18.6.2, )", "TUnit": "[1.*, )" } diff --git a/tests/FEFF.TestFixtures.XunitV4.Tests/FEFF.TestFixtures.XunitV4.Tests.csproj b/tests/FEFF.TestFixtures.XunitV4.Tests/FEFF.TestFixtures.XunitV4.Tests.csproj index db8282f..915cd10 100644 --- a/tests/FEFF.TestFixtures.XunitV4.Tests/FEFF.TestFixtures.XunitV4.Tests.csproj +++ b/tests/FEFF.TestFixtures.XunitV4.Tests/FEFF.TestFixtures.XunitV4.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs b/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs index a0f6a59..4aa19eb 100644 --- a/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs +++ b/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs @@ -3,15 +3,14 @@ using AwesomeAssertions; using AwesomeAssertions.Json; using Newtonsoft.Json.Linq; +using Xunit; namespace FEFF.TestFixtures.XunitV4.Tests; -//TODO: test event order: https://tunit.dev/docs/writing-tests/event-subscribing/ - -public class TUnitIntegrationTests +public class XunitV4IntegrationTests { // Fixture creating is tested inside TestSubject - [Test] + [Fact] public async Task Fixtures__should_be_disposed() { // Arrange @@ -35,7 +34,7 @@ public async Task Fixtures__should_be_disposed() using (p) { - await p.WaitForExitAsync(); + await p.WaitForExitAsync(TestContext.Current.CancellationToken); p.HasExited.Should().BeTrue(); p.ExitCode.Should().Be(0); } diff --git a/tests/FEFF.TestFixtures.XunitV4.Tests/packages.lock.json b/tests/FEFF.TestFixtures.XunitV4.Tests/packages.lock.json index 506d4ea..6045b0a 100644 --- a/tests/FEFF.TestFixtures.XunitV4.Tests/packages.lock.json +++ b/tests/FEFF.TestFixtures.XunitV4.Tests/packages.lock.json @@ -29,29 +29,27 @@ "Microsoft.Testing.Platform": "2.1.0" } }, - "TUnit": { + "xunit.v3.mtp-v2": { "type": "Direct", - "requested": "[1.*, )", - "resolved": "1.40.10", - "contentHash": "oM4E+wGh3J+PnKyEgs22wPnn6mxx/IhchfkE2wDRxI9Xqf7kt2HhwDlwpUnNytCd6aWgeSWRdD6kOkweWpn+LA==", + "requested": "[4.0.0-pre.103, )", + "resolved": "4.0.0-pre.103", + "contentHash": "mi6ZiojT2iUbiMUSDRdjovSOdrqqaeobH1ZttIPgEkf9bw0o22z8ioVxAe7MVlVyaS0NkgPeaBw79aWQ8hoVkQ==", "dependencies": { - "Microsoft.Testing.Extensions.CodeCoverage": "18.6.2", - "Microsoft.Testing.Extensions.Telemetry": "2.2.1", - "Microsoft.Testing.Extensions.TrxReport": "2.2.1", - "TUnit.Assertions": "1.40.10", - "TUnit.Engine": "1.40.10" + "xunit.analyzers": "2.0.0-pre.40", + "xunit.v3.assert": "[4.0.0-pre.103]", + "xunit.v3.core.mtp-v2": "[4.0.0-pre.103]" } }, - "EnumerableAsyncProcessor": { - "type": "Transitive", - "resolved": "3.8.4", - "contentHash": "KlbpupRCz3Kf+P7gsiDvFXJ980i/9lfihMZFmmxIk0Gf6mopEjy74OTJZmdaKDQpE29eQDBnMZB5khyW3eugrg==" - }, "Microsoft.ApplicationInsights": { "type": "Transitive", "resolved": "2.23.0", "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw==" }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==" + }, "Microsoft.DiaSymReader": { "type": "Transitive", "resolved": "2.2.5", @@ -145,15 +143,6 @@ "Microsoft.Testing.Platform": "2.2.1" } }, - "Microsoft.Testing.Extensions.TrxReport": { - "type": "Transitive", - "resolved": "2.2.1", - "contentHash": "FWaktPQHSiZh/+2ft2PHH/4bLlg8BKlrbLiil8mRcpoP0oHzKpgBfmN3QepGlAbxG0yDrZGN8tuPy77FYdEaMw==", - "dependencies": { - "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.1", - "Microsoft.Testing.Platform": "2.2.1" - } - }, "Microsoft.Testing.Extensions.TrxReport.Abstractions": { "type": "Transitive", "resolved": "2.2.1", @@ -175,6 +164,11 @@ "Microsoft.Testing.Platform": "2.2.1" } }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==" + }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "13.0.1", @@ -185,26 +179,67 @@ "resolved": "10.0.7", "contentHash": "XDkKntYPUaANhLVdo7AHbrJ+QbUAY5u/T7lG5rHSx5kLxeHceoc3JcIMVAc/vZT+3rbwFYlrdnikxdi6G+2nvA==" }, - "TUnit.Assertions": { + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "6.0.1", + "contentHash": "IQ4NXP/B3Ayzvw0rDQzVTYsCKyy0Jp9KI6aYcK7UnGVlR9+Awz++TIPCQtPYfLJfOpm8ajowMR09V7quD3sEHw==" + }, + "xunit.analyzers": { "type": "Transitive", - "resolved": "1.40.10", - "contentHash": "DCqMyNB0WcvUO675JQEQEKCi4fK/1oXuq2DrWqIf3ErZGXbDtuG+22jXCOaoxIv5WyD9L65LAY5Q9/4ufN9mdA==" + "resolved": "2.0.0-pre.40", + "contentHash": "TCfW5P8EnzDta+ETuKil1R6pCv2mW/kYqdUTjRagdhEnFKeBHqLwHF+xLmsl5TeBAlx/VdGJR7/26YUZMf72aA==" }, - "TUnit.Core": { + "xunit.v3.assert": { "type": "Transitive", - "resolved": "1.40.10", - "contentHash": "e09Mumq7XAnm2olh4/YzgrijNbJN5V0qMahFiyxblSrbibBv6uJFrEdaOZSn9wtds3zsE8mmcpTzCCm6MXEeXQ==" + "resolved": "4.0.0-pre.103", + "contentHash": "vhke3b55zpgL9BwqlK3TmXMAPyVqD1MwHYu2jO35Jvb96Wrts0zL9pXWbrJrS1rny0pgoIeagAsguUGXjJpAag==" }, - "TUnit.Engine": { + "xunit.v3.common": { "type": "Transitive", - "resolved": "1.40.10", - "contentHash": "WeIdqoON5bZfW1oGdT+KdePkfo7VHAhOUpakOY52rRuajvnNlCuKMWmzCxElKnlOtMSE2MSpVumT0nxbkS4sEw==", + "resolved": "4.0.0-pre.103", + "contentHash": "9d6oYJqTNLG0/a2n2sR7i5ChJx9xV9DH287u1SqiFZ8imx+Cua7bMuObNANOlvEd5EpTXQjCOC+p9JoULdzF1w==", "dependencies": { - "EnumerableAsyncProcessor": "3.8.4", + "Microsoft.Bcl.AsyncInterfaces": "6.0.0" + } + }, + "xunit.v3.core.mtp-v2": { + "type": "Transitive", + "resolved": "4.0.0-pre.103", + "contentHash": "vm5t24zX+N+cKGLA2pJVxHclVxhtXLXyhBWrZqjK8N8oo1WFDkPAcF2Toxc/VfF0r4DV4U8trZpl8WPNd8viZA==", + "dependencies": { + "Microsoft.Testing.Extensions.Telemetry": "2.2.1", "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.1", "Microsoft.Testing.Platform": "2.2.1", "Microsoft.Testing.Platform.MSBuild": "2.2.1", - "TUnit.Core": "1.40.10" + "xunit.v3.extensibility.core": "[4.0.0-pre.103]", + "xunit.v3.runner.inproc.console": "[4.0.0-pre.103]" + } + }, + "xunit.v3.extensibility.core": { + "type": "Transitive", + "resolved": "4.0.0-pre.103", + "contentHash": "WVO4mpv2HErpqe5yqYXUXeVZz+Hx7E3JYrVdOMtVuirrXk3ls3i1zRoyGrhQY+thzY5K3beww/8h8B9Qis0S/Q==", + "dependencies": { + "xunit.v3.common": "[4.0.0-pre.103]" + } + }, + "xunit.v3.runner.common": { + "type": "Transitive", + "resolved": "4.0.0-pre.103", + "contentHash": "eHOnqLu5g16Kbgi3m7cXzGbwEAtVbDCF0pla4u6xtQOuLXs129IazvDjnxoLZnKPt7uMZkfhIQIMojlI/kjrKA==", + "dependencies": { + "Microsoft.Win32.Registry": "[5.0.0]", + "System.Security.AccessControl": "[6.0.1]", + "xunit.v3.common": "[4.0.0-pre.103]" + } + }, + "xunit.v3.runner.inproc.console": { + "type": "Transitive", + "resolved": "4.0.0-pre.103", + "contentHash": "2M3cPwh5VXrnRDkhAbxCIhrDpZN44wxAuFg0Vl3gupORdUeZ4ya62sN7pzM99IRwABZ3lKp17UlFu11e04wfFw==", + "dependencies": { + "xunit.v3.extensibility.core": "[4.0.0-pre.103]", + "xunit.v3.runner.common": "[4.0.0-pre.103]" } }, "feff.testfixtures": { @@ -236,7 +271,7 @@ "dependencies": { "FEFF.TestFixtures": "[0.0.1, )", "FEFF.TestFixtures.Engine": "[0.0.1, )", - "TUnit.Core": "[1.40.10, )" + "xunit.v3.extensibility.core": "[4.0.0-pre.103, )" } }, "xunitv4.testsubject": { @@ -245,7 +280,7 @@ "AwesomeAssertions": "[9.4.0, )", "FEFF.TestFixtures.XunitV4": "[0.0.1, )", "Microsoft.Testing.Extensions.CodeCoverage": "[18.6.2, )", - "TUnit": "[1.*, )" + "xunit.v3.mtp-v2": "[4.0.0-pre.103, )" } } } diff --git a/tests/Files/API/FEFF.TestFixtures.XunitV4.verified.txt b/tests/Files/API/FEFF.TestFixtures.XunitV4.verified.txt deleted file mode 100644 index 7c8edee..0000000 --- a/tests/Files/API/FEFF.TestFixtures.XunitV4.verified.txt +++ /dev/null @@ -1,29 +0,0 @@ -[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/metacoder-feff/FEFF.TestFixtures")] -[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v10.0", FrameworkDisplayName=".NET 10.0")] -namespace FEFF.TestFixtures.XunitV4 -{ - public enum FixtureScopeType - { - TestCase = 0, - Class = 1, - Assembly = 2, - Session = 3, - } - public static class GlobalHooksExtension - { - [TUnit.Core.AfterEvery(TUnit.Core.HookType.Assembly)] - "cs", 141)] - public static System.Threading.Tasks.Task AfterA(TUnit.Core.AssemblyHookContext ctx) { } - [TUnit.Core.AfterEvery(TUnit.Core.HookType.Class)] - "cs", 130)] - public static System.Threading.Tasks.Task AfterC(TUnit.Core.ClassHookContext ctx) { } - [TUnit.Core.After(TUnit.Core.HookType.TestSession)] - "cs", 153)] - public static System.Threading.Tasks.Task AfterS(TUnit.Core.TestSessionContext ctx) { } - [TUnit.Core.AfterEvery(TUnit.Core.HookType.Test)] - "cs", 119)] - public static System.Threading.Tasks.Task AfterT(TUnit.Core.TestContext ctx) { } - public static T GetFeffFixture(this TUnit.Core.TestContext ctx, FEFF.TestFixtures.XunitV4.FixtureScopeType scopeType = 0) - where T : notnull { } - } -} \ No newline at end of file diff --git a/tests/Subjects/XunitV4.TestSubject/Infrastructure.cs b/tests/Subjects/XunitV4.TestSubject/Infrastructure.cs index 41f013d..d77cb16 100644 --- a/tests/Subjects/XunitV4.TestSubject/Infrastructure.cs +++ b/tests/Subjects/XunitV4.TestSubject/Infrastructure.cs @@ -2,7 +2,7 @@ using System.Text; using System.Text.Json; -namespace FEFF.TestFixtures.XunitV4.Tests; +namespace FEFF.TestFixtures.XunitV4.TestSubjects; public class Infrastructure { diff --git a/tests/Subjects/XunitV4.TestSubject/TestSubject.cs b/tests/Subjects/XunitV4.TestSubject/TestSubject.cs index bcce627..7f7c08c 100644 --- a/tests/Subjects/XunitV4.TestSubject/TestSubject.cs +++ b/tests/Subjects/XunitV4.TestSubject/TestSubject.cs @@ -1,7 +1,13 @@ using AwesomeAssertions; +using FEFF.TestFixtures.Xunit; using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.v3; -namespace FEFF.TestFixtures.XunitV4.Tests; +// register the extension +[assembly: TestFixturesExtension] + +namespace FEFF.TestFixtures.XunitV4.TestSubjects; internal class BaseFix : IDisposable { @@ -17,15 +23,17 @@ class TestFix : BaseFix { } [Fixture] class ClassFix : BaseFix { } [Fixture] -class AssemblyFix : BaseFix { } +class CollectionFix : BaseFix { } [Fixture] -class SessionFix : BaseFix { } +class AssemblyFix : BaseFix { } -class SingletonFix : BaseFix, IFixtureRegistrar +class SingletonTester : BaseFix, IFixtureRegistrar { + public static bool IsDisposed { get; set; } + public static void RegisterFixture(IServiceCollection services) { - services.AddSingleton(); + services.AddSingleton(); } } @@ -34,41 +42,41 @@ public class TestSubject protected static T GetFixture(FixtureScopeType scopeType = FixtureScopeType.TestCase) where T : notnull { - return TestContext.Current!.GetFeffFixture(scopeType); + return TestContext.Current.GetFeffFixture(scopeType); } - [Test] + [Fact] public void Fixtures__should_be_registered_and_materialized() { var f1 = GetFixture(); var f2 = GetFixture(FixtureScopeType.Class); + var f3 = GetFixture(FixtureScopeType.Collection); var f4 = GetFixture(FixtureScopeType.Assembly); - var f5 = GetFixture(FixtureScopeType.Session); - var s = GetFixture(); + var s0 = GetFixture(); f1.Should().BeOfType(); f2.Should().BeOfType(); + f3.Should().BeOfType(); f4.Should().BeOfType(); - f5.Should().BeOfType(); - s.Should().BeOfType(); + s0.Should().BeOfType(); } - [Test] + [Fact] public void Second_test_method() { var f1 = GetFixture(); var f2 = GetFixture(FixtureScopeType.Class); + var f3 = GetFixture(FixtureScopeType.Collection); var f4 = GetFixture(FixtureScopeType.Assembly); - var f5 = GetFixture(FixtureScopeType.Session); - var s0 = GetFixture(); f1.Should().BeOfType(); f2.Should().BeOfType(); + f3.Should().BeOfType(); f4.Should().BeOfType(); - f5.Should().BeOfType(); - s0.Should().BeOfType(); } } -[InheritsTests] +[Collection("collecion-a")] public class SecondTestSubject : TestSubject { } +[Collection("collecion-a")] +public class ThirdTestSubject : TestSubject { } \ No newline at end of file diff --git a/tests/Subjects/XunitV4.TestSubject/XunitV4.TestSubject.csproj b/tests/Subjects/XunitV4.TestSubject/XunitV4.TestSubject.csproj index 7ad4c40..a7879b6 100644 --- a/tests/Subjects/XunitV4.TestSubject/XunitV4.TestSubject.csproj +++ b/tests/Subjects/XunitV4.TestSubject/XunitV4.TestSubject.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/Subjects/XunitV4.TestSubject/packages.lock.json b/tests/Subjects/XunitV4.TestSubject/packages.lock.json index 085a093..f464066 100644 --- a/tests/Subjects/XunitV4.TestSubject/packages.lock.json +++ b/tests/Subjects/XunitV4.TestSubject/packages.lock.json @@ -19,29 +19,27 @@ "Microsoft.Testing.Platform": "2.1.0" } }, - "TUnit": { + "xunit.v3.mtp-v2": { "type": "Direct", - "requested": "[1.*, )", - "resolved": "1.40.10", - "contentHash": "oM4E+wGh3J+PnKyEgs22wPnn6mxx/IhchfkE2wDRxI9Xqf7kt2HhwDlwpUnNytCd6aWgeSWRdD6kOkweWpn+LA==", + "requested": "[4.0.0-pre.103, )", + "resolved": "4.0.0-pre.103", + "contentHash": "mi6ZiojT2iUbiMUSDRdjovSOdrqqaeobH1ZttIPgEkf9bw0o22z8ioVxAe7MVlVyaS0NkgPeaBw79aWQ8hoVkQ==", "dependencies": { - "Microsoft.Testing.Extensions.CodeCoverage": "18.6.2", - "Microsoft.Testing.Extensions.Telemetry": "2.2.1", - "Microsoft.Testing.Extensions.TrxReport": "2.2.1", - "TUnit.Assertions": "1.40.10", - "TUnit.Engine": "1.40.10" + "xunit.analyzers": "2.0.0-pre.40", + "xunit.v3.assert": "[4.0.0-pre.103]", + "xunit.v3.core.mtp-v2": "[4.0.0-pre.103]" } }, - "EnumerableAsyncProcessor": { - "type": "Transitive", - "resolved": "3.8.4", - "contentHash": "KlbpupRCz3Kf+P7gsiDvFXJ980i/9lfihMZFmmxIk0Gf6mopEjy74OTJZmdaKDQpE29eQDBnMZB5khyW3eugrg==" - }, "Microsoft.ApplicationInsights": { "type": "Transitive", "resolved": "2.23.0", "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw==" }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==" + }, "Microsoft.DiaSymReader": { "type": "Transitive", "resolved": "2.2.5", @@ -135,15 +133,6 @@ "Microsoft.Testing.Platform": "2.2.1" } }, - "Microsoft.Testing.Extensions.TrxReport": { - "type": "Transitive", - "resolved": "2.2.1", - "contentHash": "FWaktPQHSiZh/+2ft2PHH/4bLlg8BKlrbLiil8mRcpoP0oHzKpgBfmN3QepGlAbxG0yDrZGN8tuPy77FYdEaMw==", - "dependencies": { - "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.1", - "Microsoft.Testing.Platform": "2.2.1" - } - }, "Microsoft.Testing.Extensions.TrxReport.Abstractions": { "type": "Transitive", "resolved": "2.2.1", @@ -165,31 +154,77 @@ "Microsoft.Testing.Platform": "2.2.1" } }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==" + }, "System.Reflection.MetadataLoadContext": { "type": "Transitive", "resolved": "10.0.7", "contentHash": "XDkKntYPUaANhLVdo7AHbrJ+QbUAY5u/T7lG5rHSx5kLxeHceoc3JcIMVAc/vZT+3rbwFYlrdnikxdi6G+2nvA==" }, - "TUnit.Assertions": { + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "6.0.1", + "contentHash": "IQ4NXP/B3Ayzvw0rDQzVTYsCKyy0Jp9KI6aYcK7UnGVlR9+Awz++TIPCQtPYfLJfOpm8ajowMR09V7quD3sEHw==" + }, + "xunit.analyzers": { "type": "Transitive", - "resolved": "1.40.10", - "contentHash": "DCqMyNB0WcvUO675JQEQEKCi4fK/1oXuq2DrWqIf3ErZGXbDtuG+22jXCOaoxIv5WyD9L65LAY5Q9/4ufN9mdA==" + "resolved": "2.0.0-pre.40", + "contentHash": "TCfW5P8EnzDta+ETuKil1R6pCv2mW/kYqdUTjRagdhEnFKeBHqLwHF+xLmsl5TeBAlx/VdGJR7/26YUZMf72aA==" }, - "TUnit.Core": { + "xunit.v3.assert": { "type": "Transitive", - "resolved": "1.40.10", - "contentHash": "e09Mumq7XAnm2olh4/YzgrijNbJN5V0qMahFiyxblSrbibBv6uJFrEdaOZSn9wtds3zsE8mmcpTzCCm6MXEeXQ==" + "resolved": "4.0.0-pre.103", + "contentHash": "vhke3b55zpgL9BwqlK3TmXMAPyVqD1MwHYu2jO35Jvb96Wrts0zL9pXWbrJrS1rny0pgoIeagAsguUGXjJpAag==" }, - "TUnit.Engine": { + "xunit.v3.common": { "type": "Transitive", - "resolved": "1.40.10", - "contentHash": "WeIdqoON5bZfW1oGdT+KdePkfo7VHAhOUpakOY52rRuajvnNlCuKMWmzCxElKnlOtMSE2MSpVumT0nxbkS4sEw==", + "resolved": "4.0.0-pre.103", + "contentHash": "9d6oYJqTNLG0/a2n2sR7i5ChJx9xV9DH287u1SqiFZ8imx+Cua7bMuObNANOlvEd5EpTXQjCOC+p9JoULdzF1w==", "dependencies": { - "EnumerableAsyncProcessor": "3.8.4", + "Microsoft.Bcl.AsyncInterfaces": "6.0.0" + } + }, + "xunit.v3.core.mtp-v2": { + "type": "Transitive", + "resolved": "4.0.0-pre.103", + "contentHash": "vm5t24zX+N+cKGLA2pJVxHclVxhtXLXyhBWrZqjK8N8oo1WFDkPAcF2Toxc/VfF0r4DV4U8trZpl8WPNd8viZA==", + "dependencies": { + "Microsoft.Testing.Extensions.Telemetry": "2.2.1", "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.1", "Microsoft.Testing.Platform": "2.2.1", "Microsoft.Testing.Platform.MSBuild": "2.2.1", - "TUnit.Core": "1.40.10" + "xunit.v3.extensibility.core": "[4.0.0-pre.103]", + "xunit.v3.runner.inproc.console": "[4.0.0-pre.103]" + } + }, + "xunit.v3.extensibility.core": { + "type": "Transitive", + "resolved": "4.0.0-pre.103", + "contentHash": "WVO4mpv2HErpqe5yqYXUXeVZz+Hx7E3JYrVdOMtVuirrXk3ls3i1zRoyGrhQY+thzY5K3beww/8h8B9Qis0S/Q==", + "dependencies": { + "xunit.v3.common": "[4.0.0-pre.103]" + } + }, + "xunit.v3.runner.common": { + "type": "Transitive", + "resolved": "4.0.0-pre.103", + "contentHash": "eHOnqLu5g16Kbgi3m7cXzGbwEAtVbDCF0pla4u6xtQOuLXs129IazvDjnxoLZnKPt7uMZkfhIQIMojlI/kjrKA==", + "dependencies": { + "Microsoft.Win32.Registry": "[5.0.0]", + "System.Security.AccessControl": "[6.0.1]", + "xunit.v3.common": "[4.0.0-pre.103]" + } + }, + "xunit.v3.runner.inproc.console": { + "type": "Transitive", + "resolved": "4.0.0-pre.103", + "contentHash": "2M3cPwh5VXrnRDkhAbxCIhrDpZN44wxAuFg0Vl3gupORdUeZ4ya62sN7pzM99IRwABZ3lKp17UlFu11e04wfFw==", + "dependencies": { + "xunit.v3.extensibility.core": "[4.0.0-pre.103]", + "xunit.v3.runner.common": "[4.0.0-pre.103]" } }, "feff.testfixtures": { @@ -221,7 +256,7 @@ "dependencies": { "FEFF.TestFixtures": "[0.0.1, )", "FEFF.TestFixtures.Engine": "[0.0.1, )", - "TUnit.Core": "[1.40.10, )" + "xunit.v3.extensibility.core": "[4.0.0-pre.103, )" } } } From 7b093e63373c7e02356f0435a122bf0e24d9034f Mon Sep 17 00:00:00 2001 From: metacoder_FEFF Date: Wed, 29 Apr 2026 15:08:08 +0000 Subject: [PATCH 07/11] done XunitV4 + tests --- .../FEFF.TestFixtures.XunitV4.csproj | 1 + .../Internal/FixtureAdapter.cs | 21 ++++--- .../Internal/ScopeIdHelper.cs | 30 ++++----- .../XunitV4IntegrationTests.cs | 54 ++++++++++------ .../XunitV4.TestSubject/Infrastructure.cs | 28 ++++----- .../XunitV4.TestSubject/TestSubject.cs | 62 ++++++++++++++++++- 6 files changed, 137 insertions(+), 59 deletions(-) diff --git a/src/FEFF.TestFixtures.XunitV4/FEFF.TestFixtures.XunitV4.csproj b/src/FEFF.TestFixtures.XunitV4/FEFF.TestFixtures.XunitV4.csproj index 8d0db41..d3deede 100644 --- a/src/FEFF.TestFixtures.XunitV4/FEFF.TestFixtures.XunitV4.csproj +++ b/src/FEFF.TestFixtures.XunitV4/FEFF.TestFixtures.XunitV4.csproj @@ -4,6 +4,7 @@ true + preview xUnit v4 extension, adds 'FEFF.TestFixtures'. testing;fixtures;xUnit;xUnitV4;FEFF; diff --git a/src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs b/src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs index ae50431..2818f2a 100644 --- a/src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs +++ b/src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs @@ -27,26 +27,30 @@ public ValueTask DisposeAsync() public ValueTask OnTestAssemblyFinishedAsync(IXunitTestAssembly testAssembly) { - // TODO: use arg - var id = ScopeIdHelper.GetScopeId(TestContext.Current, FixtureScopeType.Assembly); + ArgumentNullException.ThrowIfNull(testAssembly); + var id = ScopeIdHelper.GetScopeId(testAssembly); return _fixtureManager.RemoveScopeAsync(id); } + + # region EventHanlers public ValueTask OnTestCollectionFinishedAsync(IXunitTestCollection testCollection) { - // TODO: use arg - var id = ScopeIdHelper.GetScopeId(TestContext.Current, FixtureScopeType.Collection); + ArgumentNullException.ThrowIfNull(testCollection); + var id = ScopeIdHelper.GetScopeId(testCollection); return _fixtureManager.RemoveScopeAsync(id); } + public ValueTask OnTestClassFinishedAsync(IXunitTestClass testClass) { - // TODO: use arg - var id = ScopeIdHelper.GetScopeId(TestContext.Current, FixtureScopeType.Class); + ArgumentNullException.ThrowIfNull(testClass); + var id = ScopeIdHelper.GetScopeId(testClass); return _fixtureManager.RemoveScopeAsync(id); } + public ValueTask OnTestCaseFinishedAsync(IXunitTestCase testCase) { - // TODO: use arg - var id = ScopeIdHelper.GetScopeId(TestContext.Current, FixtureScopeType.TestCase); + ArgumentNullException.ThrowIfNull(testCase); + var id = ScopeIdHelper.GetScopeId(testCase); return _fixtureManager.RemoveScopeAsync(id); } @@ -54,6 +58,7 @@ public ValueTask OnTestCaseFinishedAsync(IXunitTestCase testCase) public ValueTask OnTestCaseStartingAsync(IXunitTestCase testCase) => ValueTask.CompletedTask; public ValueTask OnTestClassStartingAsync(IXunitTestClass testClass) => ValueTask.CompletedTask; public ValueTask OnTestCollectionStartingAsync(IXunitTestCollection testCollection) => ValueTask.CompletedTask; + # endregion internal IFixtureScope GetScope(string scopeId) { diff --git a/src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs b/src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs index d166d20..4b4d762 100644 --- a/src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs +++ b/src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs @@ -1,30 +1,32 @@ using FEFF.Extensions; using Xunit; +using Xunit.Sdk; namespace FEFF.TestFixtures.Xunit.Internal; internal static class ScopeIdHelper { - internal static string GetScopeId(ITestContext ctx, FixtureScopeType scopeType) - { - var id = GetId(scopeType, ctx); - return GetScopeId(scopeType, id); - } - - private static string GetScopeId(FixtureScopeType scopeType, string id) => $"{scopeType}-{id}"; - - private static string GetId(FixtureScopeType scopeType, ITestContext testContext) + internal static string GetScopeId(ITestContext testContext, FixtureScopeType scopeType) { return scopeType switch { - FixtureScopeType.TestCase => ThrowHelper.EnsureNotNull(testContext.Test).UniqueID, - FixtureScopeType.Class => ThrowHelper.EnsureNotNull(testContext.TestClass).UniqueID, - FixtureScopeType.Collection => ThrowHelper.EnsureNotNull(testContext.TestCollection).UniqueID, - FixtureScopeType.Assembly => ThrowHelper.EnsureNotNull(testContext.TestAssembly).UniqueID, + FixtureScopeType.TestCase => GetScopeId(ThrowHelper.EnsureNotNull(testContext.TestCase)), + FixtureScopeType.Class => GetScopeId(ThrowHelper.EnsureNotNull(testContext.TestClass)), + FixtureScopeType.Collection => GetScopeId(ThrowHelper.EnsureNotNull(testContext.TestCollection)), + FixtureScopeType.Assembly => GetScopeId(ThrowHelper.EnsureNotNull(testContext.TestAssembly)), _ => throw EnumMatchException.From(scopeType) }; } - // internal static string GetTestCaseScopeId(IXunitTest test) => GetScopeId(FixtureScopeType.TestCase, test.UniqueID); + internal static string GetScopeId(ITestCaseMetadata testCase) => + GetScopeId(FixtureScopeType.TestCase, testCase.UniqueID); + internal static string GetScopeId(ITestClassMetadata testClass) => + GetScopeId(FixtureScopeType.Class, testClass.UniqueID); + internal static string GetScopeId(ITestCollectionMetadata testCollection) => + GetScopeId(FixtureScopeType.Collection, testCollection.UniqueID); + internal static string GetScopeId(IAssemblyMetadata testAssembly) => + GetScopeId(FixtureScopeType.Assembly, testAssembly.UniqueID); + + private static string GetScopeId(FixtureScopeType scopeType, string id) => $"{scopeType}-{id}"; } \ No newline at end of file diff --git a/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs b/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs index 4aa19eb..30bcb95 100644 --- a/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs +++ b/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs @@ -2,7 +2,6 @@ using System.Text; using AwesomeAssertions; using AwesomeAssertions.Json; -using Newtonsoft.Json.Linq; using Xunit; namespace FEFF.TestFixtures.XunitV4.Tests; @@ -14,12 +13,8 @@ public class XunitV4IntegrationTests public async Task Fixtures__should_be_disposed() { // Arrange - var fi = new FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location); - var d = fi.Directory!.FullName; - // testSubject is referenced by this project, therefore it is built and copied here - var testSubject = $"{d}/XunitV4.TestSubject.dll"; - // testSubject creates a file in its dir - var resultFile = $"{d}/test-subject-result.json"; + var resultFile = TestSubjects.Infrastructure.ResultName; + var testSubject = TestSubjects.Infrastructure.AssemblyName; TryDelete(resultFile); File.Exists(resultFile).Should().BeFalse(); @@ -41,20 +36,39 @@ public async Task Fixtures__should_be_disposed() // Assert File.Exists(resultFile).Should().BeTrue(); + var res = File.ReadAllLines(resultFile, Encoding.UTF8); + + // Assert that all fixures are disposed (and created) + // order may varry because tests run in parallel + res.Should().BeEquivalentTo( + [ + "TestFix:{'test':'TestMethod_1','collection':'','class':'TestSubject'}", + "TestFix:{'test':'TestMethod_1','collection':'collecion-a','class':'SecondTestSubject'}", + "TestFix:{'test':'TestMethod_2','collection':'','class':'TestSubject'}", + "TestFix:{'test':'TestMethod_2','collection':'collecion-a','class':'SecondTestSubject'}", + "ClassFix:{'collection':'','class':'TestSubject'}", + "ClassFix:{'collection':'collecion-a','class':'SecondTestSubject'}", + "CollectionFix:{'collection':''}", // default collection for 'TestSubject' + "TestFix:{'test':'TestMethod_2','collection':'collecion-a','class':'ThirdTestSubject'}", + "TestFix:{'test':'TestMethod_1','collection':'collecion-a','class':'ThirdTestSubject'}", + "ClassFix:{'collection':'collecion-a','class':'ThirdTestSubject'}", + "CollectionFix:{'collection':'collecion-a'}", + "AssemblyFix:{}", + "SingletonTester:{}", + ]); + + // Assert that fixture disposal order depends on FixtureScope + // strict order checking + res.Should().ContainInOrder( + [ + "TestFix:{'test':'TestMethod_1','collection':'collecion-a','class':'ThirdTestSubject'}", + "ClassFix:{'collection':'collecion-a','class':'ThirdTestSubject'}", + "CollectionFix:{'collection':'collecion-a'}", + "AssemblyFix:{}", + "SingletonTester:{}", + ]); + - // resultFile contains dispose calls per fixture counters - var res = File.ReadAllText(resultFile, Encoding.UTF8); - JToken.Parse(res) - .Should().BeEquivalentTo( - """ - { - "TestFix":4, - "ClassFix":2, - "AssemblyFix":1, - "SessionFix":1, - "SingletonFix":1, - } - """); } private static void TryDelete(string f) diff --git a/tests/Subjects/XunitV4.TestSubject/Infrastructure.cs b/tests/Subjects/XunitV4.TestSubject/Infrastructure.cs index d77cb16..1570dfd 100644 --- a/tests/Subjects/XunitV4.TestSubject/Infrastructure.cs +++ b/tests/Subjects/XunitV4.TestSubject/Infrastructure.cs @@ -1,26 +1,26 @@ -using System.Collections.Concurrent; using System.Text; -using System.Text.Json; namespace FEFF.TestFixtures.XunitV4.TestSubjects; -public class Infrastructure +public static class Infrastructure { - private static ConcurrentDictionary _result = []; + public static string ResultName { get; } = GetFileName(); + public static string AssemblyName => System.Reflection.Assembly.GetExecutingAssembly().Location; - public static void Add(string s) - { - _result.AddOrUpdate(s, 1, (_, prev) => prev + 1); - } + private static readonly Lock _lock = new(); - // Dispose _manager here. - [After(TestSession)] - public async static Task AfterS(TestSessionContext ctx) + private static string GetFileName() { - var s = JsonSerializer.Serialize(_result); - var fi = new FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location); var d = fi.Directory!.FullName; - File.WriteAllText($"{d}/test-subject-result.json", s, Encoding.UTF8); + return Path.Combine(d, "test-subject-result.txt"); + } + + public static void Add(string s) + { + lock(_lock) + { + File.AppendAllLines(ResultName, [s], Encoding.UTF8); + } } } diff --git a/tests/Subjects/XunitV4.TestSubject/TestSubject.cs b/tests/Subjects/XunitV4.TestSubject/TestSubject.cs index 7f7c08c..4d65c7f 100644 --- a/tests/Subjects/XunitV4.TestSubject/TestSubject.cs +++ b/tests/Subjects/XunitV4.TestSubject/TestSubject.cs @@ -1,3 +1,5 @@ +using System.Text.Json; +using System.Text.Json.Serialization; using AwesomeAssertions; using FEFF.TestFixtures.Xunit; using Microsoft.Extensions.DependencyInjection; @@ -11,10 +13,61 @@ namespace FEFF.TestFixtures.XunitV4.TestSubjects; internal class BaseFix : IDisposable { + private readonly string? _testName; + private readonly JsonSerializerOptions _options = new() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + WriteIndented = false, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }; + + public BaseFix() + { + // for simplier test + if(GetType() == typeof(AssemblyFix) || GetType() == typeof(SingletonTester)) + { + _testName = "{}"; + return; + } + + var tst = TestContext.Current.TestMethod?.MethodName; + var cls = TestContext.Current.TestClass?.TestClassName; + var col = TestContext.Current.TestCollection?.TestCollectionDisplayName; + + // for simplier test + if(cls != null) + cls = cls.Replace("FEFF.TestFixtures.XunitV4.TestSubjects.", ""); + + // remove default collection name for simplier test + if (col != null && col.StartsWith("Test collection for ")) + col = ""; + + // first tst/cls may vary beacuse of parallel execution + if(GetType() == typeof(ClassFix)) + tst = null; + if(GetType() == typeof(CollectionFix)) + { + tst = null; + cls = null; + } + + _testName = JsonSerializer.Serialize( + options: _options, + value: new + { + Test = tst, + Collection = col, + Class = cls, + }) + .Replace("\"", "'") // for test convenience + ; + + } + public void Dispose() { var name = this.GetType().Name; - Infrastructure.Add(name); + Infrastructure.Add($"{name}:{_testName}"); } } @@ -46,7 +99,8 @@ protected static T GetFixture(FixtureScopeType scopeType = FixtureScopeType.T } [Fact] - public void Fixtures__should_be_registered_and_materialized() + //public void Fixtures__should_be_registered_and_materialized() + public void TestMethod_1() { var f1 = GetFixture(); var f2 = GetFixture(FixtureScopeType.Class); @@ -62,7 +116,9 @@ public void Fixtures__should_be_registered_and_materialized() } [Fact] - public void Second_test_method() + // public void Second_test_method() + public void TestMethod_2() + { var f1 = GetFixture(); var f2 = GetFixture(FixtureScopeType.Class); From a5eb133539bcbd76990214270c8eee6445ea4e2c Mon Sep 17 00:00:00 2001 From: metacoder_FEFF Date: Wed, 29 Apr 2026 15:27:17 +0000 Subject: [PATCH 08/11] minor fixes --- .../TestFixturesExtensionAttribute.cs | 2 +- .../Internal/FixtureAdapter.cs | 23 ++++++++++++++++--- .../Internal/ScopeIdHelper.cs | 2 +- .../XunitV4IntegrationTests.cs | 2 +- .../XunitV4.TestSubject/TestSubject.cs | 2 +- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/FEFF.TestFixtures.XunitV4/Extension/TestFixturesExtensionAttribute.cs b/src/FEFF.TestFixtures.XunitV4/Extension/TestFixturesExtensionAttribute.cs index 1bfa6be..b21acb8 100644 --- a/src/FEFF.TestFixtures.XunitV4/Extension/TestFixturesExtensionAttribute.cs +++ b/src/FEFF.TestFixtures.XunitV4/Extension/TestFixturesExtensionAttribute.cs @@ -18,8 +18,8 @@ namespace FEFF.TestFixtures.Xunit; // [AttributeUsage(AttributeTargets.Assembly)] public class TestFixturesExtensionAttribute : AssemblyFixtureAttribute { -//TODO: add comment /// + /// Initializes a new instance of the class. /// public TestFixturesExtensionAttribute() : base(typeof(FixtureAdapter)) { diff --git a/src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs b/src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs index 2818f2a..6af13d3 100644 --- a/src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs +++ b/src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs @@ -5,6 +5,11 @@ namespace FEFF.TestFixtures.Xunit.Internal; +/// +/// Adapter that integrates FEFF.TestFixtures with xUnit v4 lifecycle events. +/// Manages the FixtureManager and handles scope creation/cleanup for test assemblies, +/// collections, classes, and test cases. +/// internal class FixtureAdapter : INotifyTestAssemblyLifecycleAsync , INotifyTestCollectionLifecycleAsync @@ -20,11 +25,13 @@ public FixtureAdapter() SetCurrent(TestContext.Current, this); } + /// public ValueTask DisposeAsync() { return _fixtureManager.DisposeAsync(); } + /// public ValueTask OnTestAssemblyFinishedAsync(IXunitTestAssembly testAssembly) { ArgumentNullException.ThrowIfNull(testAssembly); @@ -32,7 +39,8 @@ public ValueTask OnTestAssemblyFinishedAsync(IXunitTestAssembly testAssembly) return _fixtureManager.RemoveScopeAsync(id); } - # region EventHanlers + #region EventHandlers + /// public ValueTask OnTestCollectionFinishedAsync(IXunitTestCollection testCollection) { ArgumentNullException.ThrowIfNull(testCollection); @@ -40,6 +48,7 @@ public ValueTask OnTestCollectionFinishedAsync(IXunitTestCollection testCollecti return _fixtureManager.RemoveScopeAsync(id); } + /// public ValueTask OnTestClassFinishedAsync(IXunitTestClass testClass) { ArgumentNullException.ThrowIfNull(testClass); @@ -47,6 +56,7 @@ public ValueTask OnTestClassFinishedAsync(IXunitTestClass testClass) return _fixtureManager.RemoveScopeAsync(id); } + /// public ValueTask OnTestCaseFinishedAsync(IXunitTestCase testCase) { ArgumentNullException.ThrowIfNull(testCase); @@ -54,11 +64,18 @@ public ValueTask OnTestCaseFinishedAsync(IXunitTestCase testCase) return _fixtureManager.RemoveScopeAsync(id); } + /// public ValueTask OnTestAssemblyStartingAsync(IXunitTestAssembly testAssembly) => ValueTask.CompletedTask; + + /// public ValueTask OnTestCaseStartingAsync(IXunitTestCase testCase) => ValueTask.CompletedTask; + + /// public ValueTask OnTestClassStartingAsync(IXunitTestClass testClass) => ValueTask.CompletedTask; + + /// public ValueTask OnTestCollectionStartingAsync(IXunitTestCollection testCollection) => ValueTask.CompletedTask; - # endregion + #endregion internal IFixtureScope GetScope(string scopeId) { @@ -92,4 +109,4 @@ internal static FixtureAdapter GetCurrent(ITestContext ctx) ?? throw new InvalidOperationException($"Extension is not registered. Use '[assembly: {nameof(TestFixturesExtensionAttribute)}]'."); } #endregion -} \ No newline at end of file +} diff --git a/src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs b/src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs index 4b4d762..5d3deed 100644 --- a/src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs +++ b/src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs @@ -29,4 +29,4 @@ internal static string GetScopeId(IAssemblyMetadata testAssembly) => GetScopeId(FixtureScopeType.Assembly, testAssembly.UniqueID); private static string GetScopeId(FixtureScopeType scopeType, string id) => $"{scopeType}-{id}"; -} \ No newline at end of file +} diff --git a/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs b/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs index 30bcb95..11e4eb3 100644 --- a/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs +++ b/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs @@ -81,4 +81,4 @@ private static void TryDelete(string f) { } } -} \ No newline at end of file +} diff --git a/tests/Subjects/XunitV4.TestSubject/TestSubject.cs b/tests/Subjects/XunitV4.TestSubject/TestSubject.cs index 4d65c7f..c51b9b2 100644 --- a/tests/Subjects/XunitV4.TestSubject/TestSubject.cs +++ b/tests/Subjects/XunitV4.TestSubject/TestSubject.cs @@ -135,4 +135,4 @@ public void TestMethod_2() [Collection("collecion-a")] public class SecondTestSubject : TestSubject { } [Collection("collecion-a")] -public class ThirdTestSubject : TestSubject { } \ No newline at end of file +public class ThirdTestSubject : TestSubject { } From 514f947357becf76d431c47b62be341005ffb255 Mon Sep 17 00:00:00 2001 From: metacoder_FEFF Date: Wed, 29 Apr 2026 15:33:26 +0000 Subject: [PATCH 09/11] fix review --- .../Utils/ThrowHelper.cs | 10 +++++++--- .../DatabaseLifecycleFixture.cs | 11 ++++++++-- .../Core/ChannelExtensions.cs | 20 +++++++++++++------ .../FakeRandom/FakeRandom.cs | 20 +++++++++---------- .../Engine/FixtureManager.cs | 20 +++++++++---------- .../Fixtures/EnvironmentFixture.cs | 16 +++++++-------- 6 files changed, 58 insertions(+), 39 deletions(-) diff --git a/src/FEFF.TestFixtures.Abstractions/Utils/ThrowHelper.cs b/src/FEFF.TestFixtures.Abstractions/Utils/ThrowHelper.cs index db82f37..8a1a33f 100644 --- a/src/FEFF.TestFixtures.Abstractions/Utils/ThrowHelper.cs +++ b/src/FEFF.TestFixtures.Abstractions/Utils/ThrowHelper.cs @@ -30,17 +30,21 @@ public static void Assert( /// Returns argument if it is not null.
/// Throws otherwise. /// + /// The type of the argument. + /// The value to check for null. + /// The name of the parameter (auto-populated). + /// The argument if not null. /// /// It is convenient to wrap a complex expression with : /// /// - /// var result = ThrowHelper.Ensure(anObject?.Nested?.Nested?.Value); + /// var result = ThrowHelper.EnsureNotNull(anObject?.Nested?.Nested?.Value); /// /// /// Here: /// - /// The whole expression is serialized into error message, if expression is null. - /// The expression is calculated only once and is returned to consumer, if expression is NOT null. + /// The whole expression is serialized into error message, if expression is null. + /// The expression is calculated only once and is returned to consumer, if expression is NOT null. /// /// /// diff --git a/src/FEFF.TestFixtures.AspNetCore.EF/DatabaseLifecycleFixture.cs b/src/FEFF.TestFixtures.AspNetCore.EF/DatabaseLifecycleFixture.cs index b68d170..55c8917 100644 --- a/src/FEFF.TestFixtures.AspNetCore.EF/DatabaseLifecycleFixture.cs +++ b/src/FEFF.TestFixtures.AspNetCore.EF/DatabaseLifecycleFixture.cs @@ -27,7 +27,7 @@ public interface IDatabaseLifecycleFixture : IDatabaseLifecycleFixture /// Gets the instance resolved from the service provider. /// /// - /// Starts the application under test if not already running. + /// Accessing this property starts the application under test if not already running. /// TContext LazyDbContext { get; } } @@ -39,8 +39,11 @@ public interface IDatabaseLifecycleFixture : IDatabaseLifecycleFixture /// A fixture that manages Entity Framework Core database creation and deletion /// for the lifetime of a test scope. The database is deleted on . /// +/// +/// Note: EnsureDeleted may fail for admin databases. Consider using TmpDbNameFixture for such cases. +/// /// The application entry point type. -/// The type to manage. +/// The type to manage. [Fixture] public sealed class DatabaseLifecycleFixture : IAsyncDisposable, IDatabaseLifecycleFixture where TEntryPoint : class @@ -70,6 +73,10 @@ public async ValueTask DisposeAsync() return; await LazyDbContext.Database.EnsureDeletedAsync().ConfigureAwait(false); +// TODO: + // Log warning but do not throw to prevent test failures during cleanup + // This can happen when database is locked or inaccessible + // System.Console.Error.WriteLine($"[DatabaseLifecycleFixture] Warning: Failed to delete database: {ex.Message}"); } /// diff --git a/src/FEFF.TestFixtures.AspNetCore.SignalR/Core/ChannelExtensions.cs b/src/FEFF.TestFixtures.AspNetCore.SignalR/Core/ChannelExtensions.cs index cfa98a1..d828930 100644 --- a/src/FEFF.TestFixtures.AspNetCore.SignalR/Core/ChannelExtensions.cs +++ b/src/FEFF.TestFixtures.AspNetCore.SignalR/Core/ChannelExtensions.cs @@ -6,24 +6,32 @@ namespace FEFF.Extensions.Threading; internal static class ChannelExtensions { + /// + /// Attempts to read a value from the channel with a timeout. + /// + /// The type of the value to read. + /// The channel reader. + /// The timeout duration. + /// A token to cancel the operation. + /// The read value, or null if the timeout occurred. public static async Task TryReadAsync(this ChannelReader src, TimeSpan timeout, CancellationToken cancellationToken) where T : notnull { using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); timeoutCts.CancelAfter(timeout); - var timeoutToken = timeoutCts.Token; - + var timeoutToken = timeoutCts.Token; + try { - return await src.ReadAsync(timeoutToken); + return await src.ReadAsync(timeoutToken).ConfigureAwait(false); } - catch (OperationCanceledException e) - when (e.CancellationToken == timeoutToken + catch (OperationCanceledException e) + when (e.CancellationToken == timeoutToken && timeoutCts.IsCancellationRequested == true && cancellationToken.IsCancellationRequested == false) { return default; } } -} +} diff --git a/src/FEFF.TestFixtures.AspNetCore/FakeRandom/FakeRandom.cs b/src/FEFF.TestFixtures.AspNetCore/FakeRandom/FakeRandom.cs index 5704a65..12d67f5 100644 --- a/src/FEFF.TestFixtures.AspNetCore/FakeRandom/FakeRandom.cs +++ b/src/FEFF.TestFixtures.AspNetCore/FakeRandom/FakeRandom.cs @@ -2,7 +2,7 @@ namespace FEFF.TestFixtures.AspNetCore.Randomness; /// /// A configurable random number generator for testing.
-/// All public methods ARE threadsafe via lock(). +/// All public methods are thread-safe via lock(). ///
/// /// The default behavior uses a constant seed. Additional strategies such as @@ -118,7 +118,7 @@ private int NextI32Internal(INextStrategy int32Next, int min, int max) /// public override int Next() { - lock(_lock) + lock (_lock) { // just do as other "Assert arguments" pattern var def = base.Next(); @@ -137,7 +137,7 @@ public override int Next() /// public override int Next(int maxValue) { - lock(_lock) + lock (_lock) { // Assert arguments var def = base.Next(maxValue); @@ -153,7 +153,7 @@ public override int Next(int maxValue) /// public override int Next(int minValue, int maxValue) { - lock(_lock) + lock (_lock) { // Assert arguments var def = base.Next(minValue, maxValue); @@ -185,7 +185,7 @@ private long NextI64Internal(INextStrategy int64Next, long min, long max) /// public override long NextInt64() { - lock(_lock) + lock (_lock) { var def = base.NextInt64(); var strategy = Int64Next; @@ -199,7 +199,7 @@ public override long NextInt64() /// public override long NextInt64(long maxValue) { - lock(_lock) + lock (_lock) { // Assert arguments var def = base.NextInt64(maxValue); @@ -214,7 +214,7 @@ public override long NextInt64(long maxValue) /// public override long NextInt64(long minValue, long maxValue) { - lock(_lock) + lock (_lock) { // Assert arguments var def = base.NextInt64(minValue, maxValue); @@ -243,7 +243,7 @@ private float NextSingleInternal(INextStrategy singleNext) /// public override float NextSingle() { - lock(_lock) + lock (_lock) { var def = base.NextSingle(); var strategy = SingleNext; @@ -268,7 +268,7 @@ private double NextDoubleInternal(INextStrategy doubleNext) /// public override double NextDouble() { - lock(_lock) + lock (_lock) { var def = base.NextDouble(); var strategy = DoubleNext; @@ -291,7 +291,7 @@ public override void NextBytes(byte[] buffer) /// public override void NextBytes(Span buffer) { - lock(_lock) + lock (_lock) { // Assert arguments base.NextBytes(buffer); diff --git a/src/FEFF.TestFixtures.Engine/Engine/FixtureManager.cs b/src/FEFF.TestFixtures.Engine/Engine/FixtureManager.cs index 88f9dd3..6064a7a 100644 --- a/src/FEFF.TestFixtures.Engine/Engine/FixtureManager.cs +++ b/src/FEFF.TestFixtures.Engine/Engine/FixtureManager.cs @@ -18,7 +18,7 @@ public interface IFixtureScope internal interface IFixtureManagerOptions { - ServiceProvider BuildServiceProvider(); + ServiceProvider BuildServiceProvider(); } /// @@ -36,11 +36,11 @@ public sealed class FixtureManager : IAsyncDisposable #if NET9_0_OR_GREATER private readonly Lock _lock = new(); #else - private readonly Object _lock = new(); + private readonly Object _lock = new(); #endif - private bool _isDisposed; - - //TODO: public ctor + private bool _isDisposed; + + //TODO: public ctor /// /// Use for instance construction. /// @@ -72,7 +72,7 @@ public IFixtureScope GetScope(string id) var res = CreateScope(); _scopes[id] = res; - return res; + return res; } } @@ -108,11 +108,11 @@ public ValueTask RemoveScopeAsync(string scopeId) { FixtureScope scope; lock (_lock) - { - //TODO: optimize + { + //TODO: optimize if (_scopes.ContainsKey(scopeId) == false) - return ValueTask.CompletedTask; - + return ValueTask.CompletedTask; + scope = _scopes[scopeId]; _scopes.Remove(scopeId); } diff --git a/src/FEFF.TestFixtures/Fixtures/EnvironmentFixture.cs b/src/FEFF.TestFixtures/Fixtures/EnvironmentFixture.cs index 243a9ed..c6f2476 100644 --- a/src/FEFF.TestFixtures/Fixtures/EnvironmentFixture.cs +++ b/src/FEFF.TestFixtures/Fixtures/EnvironmentFixture.cs @@ -18,10 +18,10 @@ public sealed class EnvironmentFixture : IDisposable #if NET9_0_OR_GREATER private static readonly Lock __lockObj = new(); #else - private static readonly Object __lockObj = new(); + private static readonly object __lockObj = new(); #endif - - // Disallow parallel environment variable saving or restoring process-wide + + // Disallow parallel environment variable saving or restoring process-wide private static volatile Env? __oldEnv; /// @@ -41,20 +41,20 @@ public EnvironmentFixture() lock (__lockObj) { if (__oldEnv != null) - throw new InvalidOperationException($"'{nameof(EnvironmentFixture)}' mutates process environment and cannot be used in parallel. Ensure tests using this fixture run sequentially. For xUnit, consider using the [Collection] attribute on all test classes that will be part of a collection. Tests within the same collection run sequentially."); - + throw new InvalidOperationException($"'{nameof(EnvironmentFixture)}' mutates process environment and cannot be used in parallel. Ensure tests using this fixture run sequentially. For xUnit, consider using the [Collection] attribute on all test classes that will be part of a collection. Tests within the same collection run sequentially."); + InitialSnapshot = EnvironmentHelper.GetEnvironmentVariables(); __oldEnv = InitialSnapshot; } - } - + } + /// /// Same as .
/// This method is used not to forget to instantiate . ///
/// - /// Underling can be also be used instad of this. + /// Underlying can also be used instead of this. /// /// The name of the environment variable. /// The value to set for the environment variable. From 38468c60f5d2a1c98ecf58e417ce30bb6d067214 Mon Sep 17 00:00:00 2001 From: metacoder_FEFF Date: Wed, 29 Apr 2026 15:58:48 +0000 Subject: [PATCH 10/11] fix nuget reame missing --- src/FEFF.TestFixtures.XunitV4/README.md | 59 +++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/FEFF.TestFixtures.XunitV4/README.md diff --git a/src/FEFF.TestFixtures.XunitV4/README.md b/src/FEFF.TestFixtures.XunitV4/README.md new file mode 100644 index 0000000..c1460b7 --- /dev/null +++ b/src/FEFF.TestFixtures.XunitV4/README.md @@ -0,0 +1,59 @@ +# FEFF.TestFixtures.XunitV3 + +PREVIEW! Xunit v4 integration for the **FEFF.TestFixtures** solution. +Built against [Xunit-v3 4.0.0-pre.*](https://xunit.net/docs/using-ci-builds) + +## About + +Part of the **FEFF.TestFixtures** ecosystem — a framework-agnostic library for creating reusable test fixtures with scoped lifetimes. It replaces setup/teardown methods and the disposable pattern on test classes with composable, dependency-injected fixtures that can be shared across test projects. +See the [main README](https://github.com/metacoder-feff/FEFF.TestFixtures/blob/main/README.md) for full documentation. + +## This Package + +This package integrates the fixture engine with **Xunit v4**, enabling fixture resolution through the xUnit test context. + +### Quick Start + +1. Add the package to your test project: + + ```bash + dotnet add package FEFF.TestFixtures.XunitV3 + ``` + +2. Enable the extension at the assembly level: + + ```csharp + [assembly: FEFF.TestFixtures.Xunit.TestFixturesExtension] + ``` + +3. Resolve fixtures in your tests: + + ```csharp + var tmpDir = TestContext.Current.GetFeffFixture(); + var scoped = TestContext.Current.GetFeffFixture(FixtureScopeType.Class); + ``` + +### Supported Scopes + +| Scope | Description | +|-------|-------------| +| `TestCase` | Created and destroyed per test case | +| `Class` | Shared across all tests in a test class | +| `Collection` | Shared across all tests in a test collection | +| `Assembly` | Shared across all tests in the assembly | + +## Examples + +- [Example](https://github.com/metacoder-feff/FEFF.TestFixtures/blob/main/examples/ExampleTests.XunitV3/ExampleTests.cs) +- [AspNetCore example](https://github.com/metacoder-feff/FEFF.TestFixtures/blob/main/examples/ExampleTests.AspNetCore/ApiTests.cs) + +## See Also + +| Package | Description | +|---------|-------------| +| [FEFF.TestFixtures](https://www.nuget.org/packages/FEFF.TestFixtures) | Core fixtures library | +| [FEFF.TestFixtures.XunitV3](https://www.nuget.org/packages/FEFF.TestFixtures.XunitV3) | **current package** | +| [FEFF.TestFixtures.TUnit](https://www.nuget.org/packages/FEFF.TestFixtures.TUnit) | TUnit integration | +| [FEFF.TestFixtures.AspNetCore](https://www.nuget.org/packages/FEFF.TestFixtures.AspNetCore) | ASP.NET Core fixtures | +| [FEFF.TestFixtures.AspNetCore.EF](https://www.nuget.org/packages/FEFF.TestFixtures.AspNetCore.EF) | EF Core database lifecycle fixture | +| [FEFF.TestFixtures.AspNetCore.SignalR](https://www.nuget.org/packages/FEFF.TestFixtures.AspNetCore.SignalR) | SignalR testing fixture | From bce161b04b9004611bd61d525f2a77c98b968c36 Mon Sep 17 00:00:00 2001 From: metacoder_FEFF Date: Wed, 29 Apr 2026 16:20:59 +0000 Subject: [PATCH 11/11] fix docfx errors --- src/FEFF.TestFixtures.XunitV4/Extension/Enums.cs | 2 +- .../Extension/TestContextExtensions.cs | 6 +++--- .../Extension/TestFixturesExtensionAttribute.cs | 4 ++-- src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs | 2 +- src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs | 2 +- .../XunitV4IntegrationTests.cs | 2 +- tests/Subjects/XunitV4.TestSubject/Infrastructure.cs | 2 +- tests/Subjects/XunitV4.TestSubject/TestSubject.cs | 6 +++--- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/FEFF.TestFixtures.XunitV4/Extension/Enums.cs b/src/FEFF.TestFixtures.XunitV4/Extension/Enums.cs index b70c294..5b57709 100644 --- a/src/FEFF.TestFixtures.XunitV4/Extension/Enums.cs +++ b/src/FEFF.TestFixtures.XunitV4/Extension/Enums.cs @@ -1,4 +1,4 @@ -namespace FEFF.TestFixtures.Xunit; +namespace FEFF.TestFixtures.Xunit.V4; /// /// Defines the lifetime scope for a fixture in xUnit v3 tests. diff --git a/src/FEFF.TestFixtures.XunitV4/Extension/TestContextExtensions.cs b/src/FEFF.TestFixtures.XunitV4/Extension/TestContextExtensions.cs index 9dc0dac..6c9cf38 100644 --- a/src/FEFF.TestFixtures.XunitV4/Extension/TestContextExtensions.cs +++ b/src/FEFF.TestFixtures.XunitV4/Extension/TestContextExtensions.cs @@ -1,12 +1,12 @@ -using FEFF.TestFixtures.Xunit; -using FEFF.TestFixtures.Xunit.Internal; +using FEFF.TestFixtures.Xunit.V4; +using FEFF.TestFixtures.Xunit.V4.Internal; namespace Xunit.v3; /// /// Extension methods for to resolve FEFF.TestFixtures fixtures in xUnit v3. /// -public static class TestContextExtensions +public static class TestContextExtensionsV4 { /// /// Resolves a fixture from the specified scope within the test context. diff --git a/src/FEFF.TestFixtures.XunitV4/Extension/TestFixturesExtensionAttribute.cs b/src/FEFF.TestFixtures.XunitV4/Extension/TestFixturesExtensionAttribute.cs index b21acb8..613c3cb 100644 --- a/src/FEFF.TestFixtures.XunitV4/Extension/TestFixturesExtensionAttribute.cs +++ b/src/FEFF.TestFixtures.XunitV4/Extension/TestFixturesExtensionAttribute.cs @@ -1,8 +1,8 @@ -using FEFF.TestFixtures.Xunit.Internal; +using FEFF.TestFixtures.Xunit.V4.Internal; using Xunit; using Xunit.v3; -namespace FEFF.TestFixtures.Xunit; +namespace FEFF.TestFixtures.Xunit.V4; //TODO: revert xml comment //TestContextExtensions.GetFeffFixture{T} diff --git a/src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs b/src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs index 6af13d3..d37ecf3 100644 --- a/src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs +++ b/src/FEFF.TestFixtures.XunitV4/Internal/FixtureAdapter.cs @@ -3,7 +3,7 @@ using Xunit; using Xunit.v3; -namespace FEFF.TestFixtures.Xunit.Internal; +namespace FEFF.TestFixtures.Xunit.V4.Internal; /// /// Adapter that integrates FEFF.TestFixtures with xUnit v4 lifecycle events. diff --git a/src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs b/src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs index 5d3deed..8ea9e66 100644 --- a/src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs +++ b/src/FEFF.TestFixtures.XunitV4/Internal/ScopeIdHelper.cs @@ -2,7 +2,7 @@ using Xunit; using Xunit.Sdk; -namespace FEFF.TestFixtures.Xunit.Internal; +namespace FEFF.TestFixtures.Xunit.V4.Internal; internal static class ScopeIdHelper { diff --git a/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs b/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs index 11e4eb3..a8d4c39 100644 --- a/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs +++ b/tests/FEFF.TestFixtures.XunitV4.Tests/XunitV4IntegrationTests.cs @@ -4,7 +4,7 @@ using AwesomeAssertions.Json; using Xunit; -namespace FEFF.TestFixtures.XunitV4.Tests; +namespace FEFF.TestFixtures.Xunit.V4.Tests; public class XunitV4IntegrationTests { diff --git a/tests/Subjects/XunitV4.TestSubject/Infrastructure.cs b/tests/Subjects/XunitV4.TestSubject/Infrastructure.cs index 1570dfd..8135abe 100644 --- a/tests/Subjects/XunitV4.TestSubject/Infrastructure.cs +++ b/tests/Subjects/XunitV4.TestSubject/Infrastructure.cs @@ -1,6 +1,6 @@ using System.Text; -namespace FEFF.TestFixtures.XunitV4.TestSubjects; +namespace FEFF.TestFixtures.Xunit.V4.TestSubjects; public static class Infrastructure { diff --git a/tests/Subjects/XunitV4.TestSubject/TestSubject.cs b/tests/Subjects/XunitV4.TestSubject/TestSubject.cs index c51b9b2..5504546 100644 --- a/tests/Subjects/XunitV4.TestSubject/TestSubject.cs +++ b/tests/Subjects/XunitV4.TestSubject/TestSubject.cs @@ -1,7 +1,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using AwesomeAssertions; -using FEFF.TestFixtures.Xunit; +using FEFF.TestFixtures.Xunit.V4; using Microsoft.Extensions.DependencyInjection; using Xunit; using Xunit.v3; @@ -9,7 +9,7 @@ // register the extension [assembly: TestFixturesExtension] -namespace FEFF.TestFixtures.XunitV4.TestSubjects; +namespace FEFF.TestFixtures.Xunit.V4.TestSubjects; internal class BaseFix : IDisposable { @@ -36,7 +36,7 @@ public BaseFix() // for simplier test if(cls != null) - cls = cls.Replace("FEFF.TestFixtures.XunitV4.TestSubjects.", ""); + cls = cls.Replace("FEFF.TestFixtures.Xunit.V4.TestSubjects.", ""); // remove default collection name for simplier test if (col != null && col.StartsWith("Test collection for "))