Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions TUnit.Core/Interfaces/IHookExecutor.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,113 @@
namespace TUnit.Core.Interfaces;

/// <summary>
/// Defines a mechanism for executing lifecycle hooks within the TUnit test framework.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="IHookExecutor"/> interface provides a way to customize how lifecycle hooks
/// (such as <c>[Before(Test)]</c>, <c>[After(Class)]</c>, etc.) are executed. Implementers
/// can control the execution environment, threading model, or synchronization context
/// for hook execution, similar to how <see cref="ITestExecutor"/> controls test execution.
/// </para>
/// <para>
/// 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.
/// </para>
/// <para>
/// Hook executors can be specified using the <c>[HookExecutor&lt;T&gt;]</c> attribute at the
/// assembly, class, or method level.
/// </para>
/// </remarks>
public interface IHookExecutor
{
/// <summary>
/// Executes a "before test discovery" hook.
/// </summary>
/// <param name="hookMethodInfo">Metadata about the hook method being executed.</param>
/// <param name="context">The context for the test discovery phase.</param>
/// <param name="action">The hook body to execute.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask ExecuteBeforeTestDiscoveryHook(MethodMetadata hookMethodInfo, BeforeTestDiscoveryContext context, Func<ValueTask> action);

/// <summary>
/// Executes a "before test session" hook.
/// </summary>
/// <param name="hookMethodInfo">Metadata about the hook method being executed.</param>
/// <param name="context">The test session context.</param>
/// <param name="action">The hook body to execute.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask ExecuteBeforeTestSessionHook(MethodMetadata hookMethodInfo, TestSessionContext context, Func<ValueTask> action);

/// <summary>
/// Executes a "before assembly" hook.
/// </summary>
/// <param name="hookMethodInfo">Metadata about the hook method being executed.</param>
/// <param name="context">The assembly hook context.</param>
/// <param name="action">The hook body to execute.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask ExecuteBeforeAssemblyHook(MethodMetadata hookMethodInfo, AssemblyHookContext context, Func<ValueTask> action);

/// <summary>
/// Executes a "before class" hook.
/// </summary>
/// <param name="hookMethodInfo">Metadata about the hook method being executed.</param>
/// <param name="context">The class hook context.</param>
/// <param name="action">The hook body to execute.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask ExecuteBeforeClassHook(MethodMetadata hookMethodInfo, ClassHookContext context, Func<ValueTask> action);

/// <summary>
/// Executes a "before test" hook.
/// </summary>
/// <param name="hookMethodInfo">Metadata about the hook method being executed.</param>
/// <param name="context">The test context for the test about to execute.</param>
/// <param name="action">The hook body to execute.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask ExecuteBeforeTestHook(MethodMetadata hookMethodInfo, TestContext context, Func<ValueTask> action);

/// <summary>
/// Executes an "after test discovery" hook.
/// </summary>
/// <param name="hookMethodInfo">Metadata about the hook method being executed.</param>
/// <param name="context">The test discovery context.</param>
/// <param name="action">The hook body to execute.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask ExecuteAfterTestDiscoveryHook(MethodMetadata hookMethodInfo, TestDiscoveryContext context, Func<ValueTask> action);

/// <summary>
/// Executes an "after test session" hook.
/// </summary>
/// <param name="hookMethodInfo">Metadata about the hook method being executed.</param>
/// <param name="context">The test session context.</param>
/// <param name="action">The hook body to execute.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask ExecuteAfterTestSessionHook(MethodMetadata hookMethodInfo, TestSessionContext context, Func<ValueTask> action);

/// <summary>
/// Executes an "after assembly" hook.
/// </summary>
/// <param name="hookMethodInfo">Metadata about the hook method being executed.</param>
/// <param name="context">The assembly hook context.</param>
/// <param name="action">The hook body to execute.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask ExecuteAfterAssemblyHook(MethodMetadata hookMethodInfo, AssemblyHookContext context, Func<ValueTask> action);

/// <summary>
/// Executes an "after class" hook.
/// </summary>
/// <param name="hookMethodInfo">Metadata about the hook method being executed.</param>
/// <param name="context">The class hook context.</param>
/// <param name="action">The hook body to execute.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask ExecuteAfterClassHook(MethodMetadata hookMethodInfo, ClassHookContext context, Func<ValueTask> action);

/// <summary>
/// Executes an "after test" hook.
/// </summary>
/// <param name="hookMethodInfo">Metadata about the hook method being executed.</param>
/// <param name="context">The test context for the completed test.</param>
/// <param name="action">The hook body to execute.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask ExecuteAfterTestHook(MethodMetadata hookMethodInfo, TestContext context, Func<ValueTask> action);
}
26 changes: 24 additions & 2 deletions TUnit.Core/Interfaces/IHookRegisteredEventReceiver.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,34 @@
namespace TUnit.Core.Interfaces;

/// <summary>
/// Interface for hook registered event receivers
/// Defines an event receiver that is notified when a lifecycle hook is registered with the test framework.
/// </summary>
/// <remarks>
/// <para>
/// Implement this interface to perform custom logic when hooks (such as <c>[Before(Test)]</c>,
/// <c>[After(Class)]</c>, etc.) are registered. This is useful for modifying hook behavior,
/// such as applying timeouts to hooks or logging hook registration.
/// </para>
/// <para>
/// Built-in attributes such as <see cref="TimeoutAttribute"/> implement this interface to
/// apply their configuration to hooks in addition to tests.
/// </para>
/// <para>
/// The <see cref="HookRegisteredContext"/> parameter provides access to the hook's metadata
/// and allows modification of hook properties such as timeout values.
/// </para>
/// <para>
/// The <see cref="IEventReceiver.Order"/> property can be used to control the execution order
/// when multiple implementations of this interface exist.
/// </para>
/// </remarks>
public interface IHookRegisteredEventReceiver : IEventReceiver
{
/// <summary>
/// Called when a hook is registered
/// Called when a lifecycle hook is registered with the test framework.
/// </summary>
/// <param name="context">The hook registered context containing information about the hook,
/// including its method metadata and configurable properties such as timeout.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask OnHookRegistered(HookRegisteredContext context);
}
87 changes: 87 additions & 0 deletions TUnit.Core/Interfaces/ITUnitPlugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
namespace TUnit.Core.Interfaces;

/// <summary>
/// Defines a unified plugin interface for extending TUnit's test lifecycle.
/// </summary>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>
/// Plugins are activated by applying them as attributes to test methods, classes, or assemblies
/// (when the plugin inherits from <see cref="TUnitAttribute"/>), or by inheriting from a test
/// base class that implements this interface.
/// </para>
/// <para>
/// The lifecycle methods are called in the following order:
/// <list type="number">
/// <item><see cref="ITestRegisteredEventReceiver.OnTestRegistered"/> - when a test is registered with the framework</item>
/// <item><see cref="ITestDiscoveryEventReceiver.OnTestDiscovered"/> - when a test is discovered (allows configuration changes)</item>
/// <item><see cref="ITestStartEventReceiver.OnTestStart"/> - immediately before a test executes</item>
/// <item>Test method runs</item>
/// <item><see cref="ITestEndEventReceiver.OnTestEnd"/> - immediately after a test completes (pass or fail)</item>
/// <item><see cref="ITestSkippedEventReceiver.OnTestSkipped"/> - if a test was skipped instead of executed</item>
/// <item><see cref="ITestRetryEventReceiver.OnTestRetry"/> - before a failed test is retried</item>
/// </list>
/// </para>
/// <para>
/// For session, assembly, and class-level events, see <see cref="IFirstTestInTestSessionEventReceiver"/>,
/// <see cref="ILastTestInTestSessionEventReceiver"/>, <see cref="IFirstTestInAssemblyEventReceiver"/>,
/// <see cref="ILastTestInAssemblyEventReceiver"/>, <see cref="IFirstTestInClassEventReceiver"/>,
/// and <see cref="ILastTestInClassEventReceiver"/>.
/// </para>
/// </remarks>
/// <example>
/// <code>
/// // 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.
/// }
/// </code>
/// </example>
public interface ITUnitPlugin :
ITestRegisteredEventReceiver,
ITestDiscoveryEventReceiver,
ITestStartEventReceiver,
ITestEndEventReceiver,
ITestSkippedEventReceiver,
ITestRetryEventReceiver
{
#if NET
/// <inheritdoc />
ValueTask ITestRegisteredEventReceiver.OnTestRegistered(TestRegisteredContext context) => default;

/// <inheritdoc />
ValueTask ITestDiscoveryEventReceiver.OnTestDiscovered(DiscoveredTestContext context) => default;

/// <inheritdoc />
ValueTask ITestStartEventReceiver.OnTestStart(TestContext context) => default;

/// <inheritdoc />
ValueTask ITestEndEventReceiver.OnTestEnd(TestContext context) => default;

/// <inheritdoc />
ValueTask ITestSkippedEventReceiver.OnTestSkipped(TestContext context) => default;

/// <inheritdoc />
ValueTask ITestRetryEventReceiver.OnTestRetry(TestContext context, int retryAttempt) => default;
#endif
}
44 changes: 42 additions & 2 deletions TUnit.Core/Interfaces/ITestDiscoveryEventReceiver.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,52 @@
namespace TUnit.Core.Interfaces;

/// <summary>
/// Simplified interface for test discovery event receivers
/// Defines an event receiver that is notified when a test is discovered during the discovery phase.
/// </summary>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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 <see cref="RetryAttribute"/> and <see cref="TimeoutAttribute"/> implement this interface.
/// </para>
/// <para>
/// The <see cref="DiscoveredTestContext"/> parameter provides methods to modify the test's
/// configuration, such as setting retry limits, timeouts, and custom properties.
/// </para>
/// <para>
/// The <see cref="IEventReceiver.Order"/> property can be used to control the execution order
/// when multiple implementations of this interface exist.
/// </para>
/// </remarks>
/// <example>
/// <code>
/// 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;
/// }
/// }
/// </code>
/// </example>
public interface ITestDiscoveryEventReceiver : IEventReceiver
{
/// <summary>
/// Called when a test is discovered
/// Called when a test is discovered during the test discovery phase.
/// </summary>
/// <param name="context">The discovered test context, which provides methods to modify the test's
/// configuration such as retry limits, timeouts, and skip conditions.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask OnTestDiscovered(DiscoveredTestContext context);
}
26 changes: 24 additions & 2 deletions TUnit.Core/Interfaces/ITestEndEventReceiver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,35 @@ namespace TUnit.Core.Interfaces;
using TUnit.Core.Enums;

/// <summary>
/// Simplified interface for test end event receivers
/// Defines an event receiver that is notified when a test has completed execution.
/// </summary>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>
/// This event fires regardless of whether the test passed, failed, or threw an exception.
/// The test result information is available through the <paramref name="context"/> parameter.
/// </para>
/// <para>
/// On .NET 8.0+, the <see cref="Stage"/> property controls whether the receiver runs
/// before or after instance-level <c>[After(Test)]</c> hooks. The default is
/// <see cref="EventReceiverStage.Late"/> for backward compatibility.
/// </para>
/// <para>
/// The <see cref="IEventReceiver.Order"/> property can be used to control the execution order
/// when multiple implementations of this interface exist.
/// </para>
/// </remarks>
public interface ITestEndEventReceiver : IEventReceiver
{
/// <summary>
/// Called when a test ends
/// Called when a test has completed execution.
/// </summary>
/// <param name="context">The test context containing information about the completed test, including its result.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask OnTestEnd(TestContext context);

/// <summary>
Expand Down
28 changes: 28 additions & 0 deletions TUnit.Core/Interfaces/ITestFinder.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
namespace TUnit.Core.Interfaces;

/// <summary>
/// Defines a service for finding and retrieving test contexts by type or method signature.
/// </summary>
/// <remarks>
/// <para>
/// This interface is used internally by the TUnit framework to locate tests for
/// dependency resolution (via <c>[DependsOn]</c>) and other scenarios where tests
/// need to be looked up at runtime.
/// </para>
/// <para>
/// 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.
/// </para>
/// </remarks>
public interface ITestFinder
{
/// <summary>
/// Gets all test contexts for tests defined in the specified class type.
/// </summary>
/// <param name="classType">The type of the test class to search.</param>
/// <returns>An enumerable of test contexts for all tests in the specified class.</returns>
IEnumerable<TestContext> GetTests(Type classType);

/// <summary>
/// Gets test contexts matching a specific method name and parameter signature.
/// </summary>
/// <param name="testName">The name of the test method.</param>
/// <param name="methodParameterTypes">The types of the test method parameters.</param>
/// <param name="classType">The type of the test class containing the method.</param>
/// <param name="classParameterTypes">The types of the class constructor parameters.</param>
/// <param name="classArguments">The class constructor argument values used to match a specific class instance.</param>
/// <returns>An array of matching test contexts.</returns>
TestContext[] GetTestsByNameAndParameters(string testName, IEnumerable<Type> methodParameterTypes,
Type classType, IEnumerable<Type> classParameterTypes, IEnumerable<object?> classArguments);
}
Loading
Loading