From f9856613027af3f58350f81d08d3c76a24c07460 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 19 Feb 2026 04:04:01 +0000 Subject: [PATCH 1/4] feat: add ITUnitPlugin interface and improve XML docs for extension points Add a unified ITUnitPlugin interface that bundles the common test lifecycle event receiver interfaces (ITestRegisteredEventReceiver, ITestDiscoveryEventReceiver, ITestStartEventReceiver, ITestEndEventReceiver, ITestSkippedEventReceiver, ITestRetryEventReceiver) with default no-op implementations on .NET 8.0+. This makes it easier for third-party libraries to create plugins that hook into multiple lifecycle events without implementing each interface separately. Also add comprehensive XML documentation to previously undocumented or minimally documented extension interfaces: ITestRetryEventReceiver, ITestSkippedEventReceiver, IHookRegisteredEventReceiver, IHookExecutor, ITestFinder, and improve docs for ITestStartEventReceiver, ITestEndEventReceiver, ITestRegisteredEventReceiver, and ITestDiscoveryEventReceiver with usage examples and lifecycle ordering. --- TUnit.Core/Interfaces/IHookExecutor.cs | 97 +++++++++++++++++++ .../IHookRegisteredEventReceiver.cs | 26 ++++- TUnit.Core/Interfaces/ITUnitPlugin.cs | 87 +++++++++++++++++ .../Interfaces/ITestDiscoveryEventReceiver.cs | 44 ++++++++- .../Interfaces/ITestEndEventReceiver.cs | 26 ++++- TUnit.Core/Interfaces/ITestFinder.cs | 28 ++++++ .../ITestRegisteredEventReceiver.cs | 21 +++- .../Interfaces/ITestRetryEventReceiver.cs | 37 +++++++ .../Interfaces/ITestSkippedEventReceiver.cs | 37 +++++++ .../Interfaces/ITestStartEventReceiver.cs | 50 +++++++++- 10 files changed, 443 insertions(+), 10 deletions(-) create mode 100644 TUnit.Core/Interfaces/ITUnitPlugin.cs diff --git a/TUnit.Core/Interfaces/IHookExecutor.cs b/TUnit.Core/Interfaces/IHookExecutor.cs index 6d3d1c8e81..d48e70fc2a 100644 --- a/TUnit.Core/Interfaces/IHookExecutor.cs +++ b/TUnit.Core/Interfaces/IHookExecutor.cs @@ -1,16 +1,113 @@ namespace TUnit.Core.Interfaces; +/// +/// Defines a mechanism for executing lifecycle hooks within the TUnit test framework. +/// +/// +/// +/// The interface provides a way to customize how lifecycle hooks +/// (such as [Before(Test)], [After(Class)], etc.) are executed. Implementers +/// can control the execution environment, threading model, or synchronization context +/// for hook execution, similar to how controls test execution. +/// +/// +/// This is particularly useful when hooks need to run on a specific thread (e.g., STA thread +/// for UI testing), within a specific synchronization context, or with custom error handling. +/// +/// +/// Hook executors can be specified using the [HookExecutor<T>] attribute at the +/// assembly, class, or method level. +/// +/// public interface IHookExecutor { + /// + /// Executes a "before test discovery" hook. + /// + /// Metadata about the hook method being executed. + /// The context for the test discovery phase. + /// The hook body to execute. + /// A representing the asynchronous operation. ValueTask ExecuteBeforeTestDiscoveryHook(MethodMetadata hookMethodInfo, BeforeTestDiscoveryContext context, Func action); + + /// + /// Executes a "before test session" hook. + /// + /// Metadata about the hook method being executed. + /// The test session context. + /// The hook body to execute. + /// A representing the asynchronous operation. ValueTask ExecuteBeforeTestSessionHook(MethodMetadata hookMethodInfo, TestSessionContext context, Func action); + + /// + /// Executes a "before assembly" hook. + /// + /// Metadata about the hook method being executed. + /// The assembly hook context. + /// The hook body to execute. + /// A representing the asynchronous operation. ValueTask ExecuteBeforeAssemblyHook(MethodMetadata hookMethodInfo, AssemblyHookContext context, Func action); + + /// + /// Executes a "before class" hook. + /// + /// Metadata about the hook method being executed. + /// The class hook context. + /// The hook body to execute. + /// A representing the asynchronous operation. ValueTask ExecuteBeforeClassHook(MethodMetadata hookMethodInfo, ClassHookContext context, Func action); + + /// + /// Executes a "before test" hook. + /// + /// Metadata about the hook method being executed. + /// The test context for the test about to execute. + /// The hook body to execute. + /// A representing the asynchronous operation. ValueTask ExecuteBeforeTestHook(MethodMetadata hookMethodInfo, TestContext context, Func action); + /// + /// Executes an "after test discovery" hook. + /// + /// Metadata about the hook method being executed. + /// The test discovery context. + /// The hook body to execute. + /// A representing the asynchronous operation. ValueTask ExecuteAfterTestDiscoveryHook(MethodMetadata hookMethodInfo, TestDiscoveryContext context, Func action); + + /// + /// Executes an "after test session" hook. + /// + /// Metadata about the hook method being executed. + /// The test session context. + /// The hook body to execute. + /// A representing the asynchronous operation. ValueTask ExecuteAfterTestSessionHook(MethodMetadata hookMethodInfo, TestSessionContext context, Func action); + + /// + /// Executes an "after assembly" hook. + /// + /// Metadata about the hook method being executed. + /// The assembly hook context. + /// The hook body to execute. + /// A representing the asynchronous operation. ValueTask ExecuteAfterAssemblyHook(MethodMetadata hookMethodInfo, AssemblyHookContext context, Func action); + + /// + /// Executes an "after class" hook. + /// + /// Metadata about the hook method being executed. + /// The class hook context. + /// The hook body to execute. + /// A representing the asynchronous operation. ValueTask ExecuteAfterClassHook(MethodMetadata hookMethodInfo, ClassHookContext context, Func action); + + /// + /// Executes an "after test" hook. + /// + /// Metadata about the hook method being executed. + /// The test context for the completed test. + /// The hook body to execute. + /// A representing the asynchronous operation. ValueTask ExecuteAfterTestHook(MethodMetadata hookMethodInfo, TestContext context, Func action); } diff --git a/TUnit.Core/Interfaces/IHookRegisteredEventReceiver.cs b/TUnit.Core/Interfaces/IHookRegisteredEventReceiver.cs index 8f3ac3da53..d378740e38 100644 --- a/TUnit.Core/Interfaces/IHookRegisteredEventReceiver.cs +++ b/TUnit.Core/Interfaces/IHookRegisteredEventReceiver.cs @@ -1,12 +1,34 @@ namespace TUnit.Core.Interfaces; /// -/// Interface for hook registered event receivers +/// Defines an event receiver that is notified when a lifecycle hook is registered with the test framework. /// +/// +/// +/// Implement this interface to perform custom logic when hooks (such as [Before(Test)], +/// [After(Class)], etc.) are registered. This is useful for modifying hook behavior, +/// such as applying timeouts to hooks or logging hook registration. +/// +/// +/// Built-in attributes such as implement this interface to +/// apply their configuration to hooks in addition to tests. +/// +/// +/// The parameter provides access to the hook's metadata +/// and allows modification of hook properties such as timeout values. +/// +/// +/// The property can be used to control the execution order +/// when multiple implementations of this interface exist. +/// +/// public interface IHookRegisteredEventReceiver : IEventReceiver { /// - /// Called when a hook is registered + /// Called when a lifecycle hook is registered with the test framework. /// + /// The hook registered context containing information about the hook, + /// including its method metadata and configurable properties such as timeout. + /// A representing the asynchronous operation. ValueTask OnHookRegistered(HookRegisteredContext context); } \ No newline at end of file diff --git a/TUnit.Core/Interfaces/ITUnitPlugin.cs b/TUnit.Core/Interfaces/ITUnitPlugin.cs new file mode 100644 index 0000000000..d9d6c32e9b --- /dev/null +++ b/TUnit.Core/Interfaces/ITUnitPlugin.cs @@ -0,0 +1,87 @@ +namespace TUnit.Core.Interfaces; + +/// +/// Defines a unified plugin interface for extending TUnit's test lifecycle. +/// +/// +/// +/// Implement this interface to create a self-contained plugin that can hook into multiple +/// points of the TUnit test lifecycle. On .NET 8.0+, this interface provides default (no-op) +/// implementations for all lifecycle methods, so plugins only need to override the methods +/// they care about. +/// +/// +/// Plugins are activated by applying them as attributes to test methods, classes, or assemblies +/// (when the plugin inherits from ), or by inheriting from a test +/// base class that implements this interface. +/// +/// +/// The lifecycle methods are called in the following order: +/// +/// - when a test is registered with the framework +/// - when a test is discovered (allows configuration changes) +/// - immediately before a test executes +/// Test method runs +/// - immediately after a test completes (pass or fail) +/// - if a test was skipped instead of executed +/// - before a failed test is retried +/// +/// +/// +/// For session, assembly, and class-level events, see , +/// , , +/// , , +/// and . +/// +/// +/// +/// +/// // A plugin attribute that logs test lifecycle events +/// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] +/// public class TestLoggingPluginAttribute : TUnitAttribute, ITUnitPlugin +/// { +/// public ValueTask OnTestStart(TestContext context) +/// { +/// Console.WriteLine($"Starting: {context.TestDetails.TestName}"); +/// return ValueTask.CompletedTask; +/// } +/// +/// public ValueTask OnTestEnd(TestContext context) +/// { +/// Console.WriteLine($"Finished: {context.TestDetails.TestName} - {context.Result?.Status}"); +/// return ValueTask.CompletedTask; +/// } +/// +/// // On .NET 8.0+, other methods have default no-op implementations. +/// // On older frameworks, all methods must be explicitly implemented. +/// } +/// +/// +public interface ITUnitPlugin : + ITestRegisteredEventReceiver, + ITestDiscoveryEventReceiver, + ITestStartEventReceiver, + ITestEndEventReceiver, + ITestSkippedEventReceiver, + ITestRetryEventReceiver +{ +#if NET + /// + ValueTask ITestRegisteredEventReceiver.OnTestRegistered(TestRegisteredContext context) => default; + + /// + ValueTask ITestDiscoveryEventReceiver.OnTestDiscovered(DiscoveredTestContext context) => default; + + /// + ValueTask ITestStartEventReceiver.OnTestStart(TestContext context) => default; + + /// + ValueTask ITestEndEventReceiver.OnTestEnd(TestContext context) => default; + + /// + ValueTask ITestSkippedEventReceiver.OnTestSkipped(TestContext context) => default; + + /// + ValueTask ITestRetryEventReceiver.OnTestRetry(TestContext context, int retryAttempt) => default; +#endif +} diff --git a/TUnit.Core/Interfaces/ITestDiscoveryEventReceiver.cs b/TUnit.Core/Interfaces/ITestDiscoveryEventReceiver.cs index 028c79bef6..a06935e1cc 100644 --- a/TUnit.Core/Interfaces/ITestDiscoveryEventReceiver.cs +++ b/TUnit.Core/Interfaces/ITestDiscoveryEventReceiver.cs @@ -1,12 +1,52 @@ namespace TUnit.Core.Interfaces; /// -/// Simplified interface for test discovery event receivers +/// Defines an event receiver that is notified when a test is discovered during the discovery phase. /// +/// +/// +/// Implement this interface to perform custom logic when a test is discovered, such as +/// modifying test metadata, setting timeouts, configuring retry logic, or conditionally +/// skipping tests based on runtime conditions. +/// +/// +/// This is one of the most commonly implemented event receivers for third-party extensions, +/// as it allows modifying test behavior before execution begins. Many built-in attributes +/// such as and implement this interface. +/// +/// +/// The parameter provides methods to modify the test's +/// configuration, such as setting retry limits, timeouts, and custom properties. +/// +/// +/// The property can be used to control the execution order +/// when multiple implementations of this interface exist. +/// +/// +/// +/// +/// public class ConditionalSkipReceiver : ITestDiscoveryEventReceiver +/// { +/// public int Order => 0; +/// +/// public ValueTask OnTestDiscovered(DiscoveredTestContext context) +/// { +/// if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) +/// { +/// context.SkipTest("This test only runs on Windows"); +/// } +/// return ValueTask.CompletedTask; +/// } +/// } +/// +/// public interface ITestDiscoveryEventReceiver : IEventReceiver { /// - /// Called when a test is discovered + /// Called when a test is discovered during the test discovery phase. /// + /// The discovered test context, which provides methods to modify the test's + /// configuration such as retry limits, timeouts, and skip conditions. + /// A representing the asynchronous operation. ValueTask OnTestDiscovered(DiscoveredTestContext context); } diff --git a/TUnit.Core/Interfaces/ITestEndEventReceiver.cs b/TUnit.Core/Interfaces/ITestEndEventReceiver.cs index 9693c2804b..c7ad9c7030 100644 --- a/TUnit.Core/Interfaces/ITestEndEventReceiver.cs +++ b/TUnit.Core/Interfaces/ITestEndEventReceiver.cs @@ -3,13 +3,35 @@ namespace TUnit.Core.Interfaces; using TUnit.Core.Enums; /// -/// Simplified interface for test end event receivers +/// Defines an event receiver that is notified when a test has completed execution. /// +/// +/// +/// Implement this interface to perform custom logic after a test completes, such as +/// recording test results, cleaning up per-test resources, publishing metrics, +/// or capturing diagnostic information on failure. +/// +/// +/// This event fires regardless of whether the test passed, failed, or threw an exception. +/// The test result information is available through the parameter. +/// +/// +/// On .NET 8.0+, the property controls whether the receiver runs +/// before or after instance-level [After(Test)] hooks. The default is +/// for backward compatibility. +/// +/// +/// The property can be used to control the execution order +/// when multiple implementations of this interface exist. +/// +/// public interface ITestEndEventReceiver : IEventReceiver { /// - /// Called when a test ends + /// Called when a test has completed execution. /// + /// The test context containing information about the completed test, including its result. + /// A representing the asynchronous operation. ValueTask OnTestEnd(TestContext context); /// diff --git a/TUnit.Core/Interfaces/ITestFinder.cs b/TUnit.Core/Interfaces/ITestFinder.cs index 5d29caa3f8..75a8b56e42 100644 --- a/TUnit.Core/Interfaces/ITestFinder.cs +++ b/TUnit.Core/Interfaces/ITestFinder.cs @@ -1,9 +1,37 @@ namespace TUnit.Core.Interfaces; +/// +/// Defines a service for finding and retrieving test contexts by type or method signature. +/// +/// +/// +/// This interface is used internally by the TUnit framework to locate tests for +/// dependency resolution (via [DependsOn]) and other scenarios where tests +/// need to be looked up at runtime. +/// +/// +/// Third-party extensions can use this interface to query the set of registered tests +/// for a given class or to find specific tests by their method signature. +/// +/// public interface ITestFinder { + /// + /// Gets all test contexts for tests defined in the specified class type. + /// + /// The type of the test class to search. + /// An enumerable of test contexts for all tests in the specified class. IEnumerable GetTests(Type classType); + /// + /// Gets test contexts matching a specific method name and parameter signature. + /// + /// The name of the test method. + /// The types of the test method parameters. + /// The type of the test class containing the method. + /// The types of the class constructor parameters. + /// The class constructor argument values used to match a specific class instance. + /// An array of matching test contexts. TestContext[] GetTestsByNameAndParameters(string testName, IEnumerable methodParameterTypes, Type classType, IEnumerable classParameterTypes, IEnumerable classArguments); } diff --git a/TUnit.Core/Interfaces/ITestRegisteredEventReceiver.cs b/TUnit.Core/Interfaces/ITestRegisteredEventReceiver.cs index f42ef301f0..e5bce0f27f 100644 --- a/TUnit.Core/Interfaces/ITestRegisteredEventReceiver.cs +++ b/TUnit.Core/Interfaces/ITestRegisteredEventReceiver.cs @@ -1,12 +1,29 @@ namespace TUnit.Core.Interfaces; /// -/// Simplified interface for test registered event receivers +/// Defines an event receiver that is notified when a test is registered with the test framework. /// +/// +/// +/// Implement this interface to perform custom logic when a test is first registered, +/// before it is discovered or executed. This is useful for collecting test metadata, +/// modifying test registration, or tracking test counts. +/// +/// +/// Test registration occurs early in the test lifecycle, after the test has been +/// identified but before it goes through the discovery pipeline. +/// +/// +/// The property can be used to control the execution order +/// when multiple implementations of this interface exist. +/// +/// public interface ITestRegisteredEventReceiver : IEventReceiver { /// - /// Called when a test is registered + /// Called when a test is registered with the test framework. /// + /// The test registered context containing information about the registered test. + /// A representing the asynchronous operation. ValueTask OnTestRegistered(TestRegisteredContext context); } diff --git a/TUnit.Core/Interfaces/ITestRetryEventReceiver.cs b/TUnit.Core/Interfaces/ITestRetryEventReceiver.cs index 225ad30333..f499e25500 100644 --- a/TUnit.Core/Interfaces/ITestRetryEventReceiver.cs +++ b/TUnit.Core/Interfaces/ITestRetryEventReceiver.cs @@ -1,6 +1,43 @@ namespace TUnit.Core.Interfaces; +/// +/// Defines an event receiver that is notified when a test is about to be retried after a failure. +/// +/// +/// +/// Implement this interface to perform custom logic before a test retry attempt, such as +/// resetting shared state, logging retry information, or adjusting test configuration +/// between attempts. +/// +/// +/// This event is only triggered when a test has a (or a derived attribute) +/// applied to it and the test has failed. The event fires before each retry attempt, not before the +/// initial test execution. +/// +/// +/// The property can be used to control the execution order +/// when multiple implementations of this interface exist. +/// +/// +/// +/// +/// public class RetryLoggingReceiver : ITestRetryEventReceiver +/// { +/// public async ValueTask OnTestRetry(TestContext context, int retryAttempt) +/// { +/// await context.OutputWriter.WriteLineAsync( +/// $"Retrying test {context.TestDetails.TestName}, attempt {retryAttempt}"); +/// } +/// } +/// +/// public interface ITestRetryEventReceiver : IEventReceiver { + /// + /// Called when a test is about to be retried after a failure. + /// + /// The test context containing information about the test being retried. + /// The 1-based retry attempt number. The first retry is 1, the second is 2, and so on. + /// A representing the asynchronous operation. ValueTask OnTestRetry(TestContext context, int retryAttempt); } diff --git a/TUnit.Core/Interfaces/ITestSkippedEventReceiver.cs b/TUnit.Core/Interfaces/ITestSkippedEventReceiver.cs index 9cdf98e910..6494279ae4 100644 --- a/TUnit.Core/Interfaces/ITestSkippedEventReceiver.cs +++ b/TUnit.Core/Interfaces/ITestSkippedEventReceiver.cs @@ -1,6 +1,43 @@ namespace TUnit.Core.Interfaces; +/// +/// Defines an event receiver that is notified when a test is skipped. +/// +/// +/// +/// Implement this interface to perform custom logic when a test is skipped, such as +/// logging skip reasons, collecting metrics on skipped tests, or performing conditional +/// cleanup of resources that were prepared for the test. +/// +/// +/// A test can be skipped via the , by calling +/// during execution, or through other +/// framework mechanisms that mark a test as skipped. +/// +/// +/// The property can be used to control the execution order +/// when multiple implementations of this interface exist. +/// +/// +/// +/// +/// public class SkipTrackingReceiver : ITestSkippedEventReceiver +/// { +/// public async ValueTask OnTestSkipped(TestContext context) +/// { +/// await context.OutputWriter.WriteLineAsync( +/// $"Test {context.TestDetails.TestName} was skipped"); +/// } +/// } +/// +/// public interface ITestSkippedEventReceiver : IEventReceiver { + /// + /// Called when a test is skipped. + /// + /// The test context containing information about the skipped test, + /// including the skip reason accessible via the context. + /// A representing the asynchronous operation. ValueTask OnTestSkipped(TestContext context); } diff --git a/TUnit.Core/Interfaces/ITestStartEventReceiver.cs b/TUnit.Core/Interfaces/ITestStartEventReceiver.cs index 6fb6670f4e..c0964bfb2e 100644 --- a/TUnit.Core/Interfaces/ITestStartEventReceiver.cs +++ b/TUnit.Core/Interfaces/ITestStartEventReceiver.cs @@ -3,13 +3,59 @@ namespace TUnit.Core.Interfaces; using TUnit.Core.Enums; /// -/// Simplified interface for test start event receivers +/// Defines an event receiver that is notified when a test is about to start execution. /// +/// +/// +/// Implement this interface to perform custom logic before a test executes, such as +/// setting up per-test resources, initializing logging, recording timing information, +/// or configuring the test environment. +/// +/// +/// The order of test lifecycle events is: +/// +/// - when the test is registered +/// - when the test is discovered +/// - before the test executes (this interface) +/// Test method executes +/// - after the test completes +/// +/// +/// +/// On .NET 8.0+, the property controls whether the receiver runs +/// before or after instance-level [Before(Test)] hooks. The default is +/// for backward compatibility. +/// +/// +/// The property can be used to control the execution order +/// when multiple implementations of this interface exist. +/// +/// +/// +/// +/// public class TimingReceiver : ITestStartEventReceiver, ITestEndEventReceiver +/// { +/// public async ValueTask OnTestStart(TestContext context) +/// { +/// context.ObjectBag["StartTime"] = DateTime.UtcNow; +/// } +/// +/// public async ValueTask OnTestEnd(TestContext context) +/// { +/// var start = (DateTime)context.ObjectBag["StartTime"]; +/// var duration = DateTime.UtcNow - start; +/// await context.OutputWriter.WriteLineAsync($"Test took {duration.TotalMilliseconds}ms"); +/// } +/// } +/// +/// public interface ITestStartEventReceiver : IEventReceiver { /// - /// Called when a test starts + /// Called when a test is about to start execution. /// + /// The test context containing information about the test being executed. + /// A representing the asynchronous operation. ValueTask OnTestStart(TestContext context); /// From d7f5c5813191d2a0c3e6a86003c2735314d58182 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 19 Feb 2026 07:51:04 +0000 Subject: [PATCH 2/4] fix: update public API snapshot for plugin architecture --- .../Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt index 53eeecd01f..a25bd154dc 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt @@ -2409,6 +2409,7 @@ namespace .Interfaces void RegisterClassFactory(string testId, factory); void RegisterMethodInvoker(string testId, > invoker); } + public interface ITUnitPlugin : ., ., ., ., ., ., . { } public interface ITestClass { [] ClassGenericArguments { get; } From 887104db5a050046767bd2b2ddb5249a8611d44b Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 19 Feb 2026 15:15:11 +0000 Subject: [PATCH 3/4] fix: update DotNet9_0, DotNet10_0, Net4_7 core snapshots for ITUnitPlugin --- ...Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt | 1 + .../Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt | 1 + .../Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt | 1 + 3 files changed, 3 insertions(+) diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt index ae1c87cff8..5c75388a8f 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt @@ -2409,6 +2409,7 @@ namespace .Interfaces void RegisterClassFactory(string testId, factory); void RegisterMethodInvoker(string testId, > invoker); } + public interface ITUnitPlugin : ., ., ., ., ., ., . { } public interface ITestClass { [] ClassGenericArguments { get; } diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt index 8950a1b292..68b8e6d296 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -2409,6 +2409,7 @@ namespace .Interfaces void RegisterClassFactory(string testId, factory); void RegisterMethodInvoker(string testId, > invoker); } + public interface ITUnitPlugin : ., ., ., ., ., ., . { } public interface ITestClass { [] ClassGenericArguments { get; } diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt index f1106c74fd..db09a9860d 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -2348,6 +2348,7 @@ namespace .Interfaces void RegisterClassFactory(string testId, factory); void RegisterMethodInvoker(string testId, > invoker); } + public interface ITUnitPlugin : ., ., ., ., ., ., . { } public interface ITestClass { [] ClassGenericArguments { get; } From 126817a8c271409ea02193ddb512fa983dd433ac Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 19 Feb 2026 15:10:48 +0000 Subject: [PATCH 4/4] fix: update Net4_7 Core API snapshot with missing APIs from merged PRs Add Defaults class, SetRetryBackoff method, BackoffMs/BackoffMultiplier properties on RetryAttribute, RetryBackoffMs/RetryBackoffMultiplier on TestDetails and ITestConfiguration - matching the DotNet8_0 snapshot but without DynamicallyAccessedMembers annotations. --- ...Library_Has_No_API_Changes.Net4_7.verified.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt index db09a9860d..5092f73a19 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -511,6 +511,13 @@ namespace public static readonly .DefaultExecutor Instance; protected override . ExecuteAsync(<.> action) { } } + public static class Defaults + { + public static readonly ForcefulExitTimeout; + public static readonly HookTimeout; + public static readonly ProcessExitHookDelay; + public static readonly TestTimeout; + } public abstract class DependencyInjectionDataSourceAttribute : .UntypedDataSourceGeneratorAttribute { protected DependencyInjectionDataSourceAttribute() { } @@ -562,6 +569,7 @@ namespace public void SetDisplayName(string displayName) { } public void SetDisplayNameFormatter( formatterType) { } public void SetPriority(. priority) { } + public void SetRetryBackoff(int backoffMs, double backoffMultiplier) { } public void SetRetryLimit(int retryLimit) { } public void SetRetryLimit(int retryCount, <.TestContext, , int, .> shouldRetry) { } } @@ -1138,7 +1146,10 @@ namespace public class RetryAttribute : .TUnitAttribute, .IScopedAttribute, ., . { public RetryAttribute(int times) { } + public int BackoffMs { get; set; } + public double BackoffMultiplier { get; set; } public int Order { get; } + public []? RetryOnExceptionTypes { get; set; } public ScopeType { get; } public int Times { get; } public . OnTestDiscovered(.DiscoveredTestContext context) { } @@ -1407,6 +1418,8 @@ namespace public [] MethodGenericArguments { get; set; } public required .MethodMetadata MethodMetadata { get; set; } public required string MethodName { get; init; } + public int RetryBackoffMs { get; set; } + public double RetryBackoffMultiplier { get; set; } public int RetryLimit { get; set; } public required ReturnType { get; set; } public required object?[] TestClassArguments { get; set; } @@ -2360,6 +2373,8 @@ namespace .Interfaces } public interface ITestConfiguration { + int RetryBackoffMs { get; } + double RetryBackoffMultiplier { get; } int RetryLimit { get; } ? Timeout { get; } }