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
35 changes: 32 additions & 3 deletions TUnit.Core/Attributes/TestMetadata/SkipAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ public async ValueTask OnTestRegistered(TestRegisteredContext context)
{
if (await ShouldSkip(context))
{
// Store skip reason directly on TestContext
context.TestContext.SkipReason = Reason;
context.TestContext.Metadata.TestDetails.ClassInstance = SkippedTestInstance.Instance;
context.SetSkipped(GetSkipReason(context));
}
}

Expand All @@ -75,4 +73,35 @@ public async ValueTask OnTestRegistered(TestRegisteredContext context)
/// The default implementation always returns true, meaning the test will always be skipped.
/// </remarks>
public virtual Task<bool> ShouldSkip(TestRegisteredContext context) => Task.FromResult(true);

/// <summary>
/// Gets the skip reason for the test.
/// </summary>
/// <param name="context">The test context containing information about the test being registered.</param>
/// <returns>The reason why the test should be skipped.</returns>
/// <remarks>
/// Can be overridden in derived classes to provide dynamic skip reasons based on runtime information.
/// This allows including contextual information (e.g., device names, environment variables) in skip messages.
///
/// The default implementation returns the <see cref="Reason"/> property value.
/// </remarks>
/// <example>
/// <code>
/// public class SkipOnDeviceAttribute : SkipAttribute
/// {
/// private readonly string _deviceName;
///
/// public SkipOnDeviceAttribute(string deviceName) : base("Device-specific skip")
/// {
/// _deviceName = deviceName;
/// }
///
/// protected override string GetSkipReason(TestRegisteredContext context)
/// {
/// return $"Test '{context.TestName}' is not supported on device '{_deviceName}'";
/// }
/// }
/// </code>
/// </example>
protected virtual string GetSkipReason(TestRegisteredContext context) => Reason;
}
11 changes: 11 additions & 0 deletions TUnit.Core/Contexts/TestRegisteredContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,15 @@ public void SetParallelLimiter(IParallelLimit parallelLimit)
{
TestContext.Parallelism.SetLimiter(parallelLimit);
}

/// <summary>
/// Marks the test as skipped with the specified reason.
/// This can only be called during the test registration phase.
/// </summary>
/// <param name="reason">The reason why the test is being skipped</param>
public void SetSkipped(string reason)
{
TestContext.SkipReason = reason;
TestContext.Metadata.TestDetails.ClassInstance = SkippedTestInstance.Instance;
}
}
49 changes: 49 additions & 0 deletions TUnit.Engine.Tests/DynamicSkipReasonTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Shouldly;
using TUnit.Engine.Tests.Enums;

namespace TUnit.Engine.Tests;

public class DynamicSkipReasonTests(TestMode testMode) : InvokableTestBase(testMode)
{
[Test]
public async Task TestSkippedViaSetSkippedMethod_ShouldContainDynamicDeviceName()
{
await RunTestsWithFilter(
"/*/*/DynamicSkipReasonTests/TestSkippedViaSetSkippedMethod",
[
result => result.ResultSummary.Outcome.ShouldBe("Failed"),
result => result.ResultSummary.Counters.Total.ShouldBe(1),
result => result.ResultSummary.Counters.Passed.ShouldBe(0),
result => result.ResultSummary.Counters.Failed.ShouldBe(0),
result => result.ResultSummary.Counters.NotExecuted.ShouldBe(1)
]);
}

[Test]
public async Task TestSkippedViaGetSkipReasonOverride_ShouldContainDynamicDeviceName()
{
await RunTestsWithFilter(
"/*/*/DynamicSkipReasonTests/TestSkippedViaGetSkipReasonOverride",
[
result => result.ResultSummary.Outcome.ShouldBe("Failed"),
result => result.ResultSummary.Counters.Total.ShouldBe(1),
result => result.ResultSummary.Counters.Passed.ShouldBe(0),
result => result.ResultSummary.Counters.Failed.ShouldBe(0),
result => result.ResultSummary.Counters.NotExecuted.ShouldBe(1)
]);
}

[Test]
public async Task TestNotSkippedWhenConditionFalse_ShouldPass()
{
await RunTestsWithFilter(
"/*/*/DynamicSkipReasonTests/TestNotSkippedWhenConditionFalse",
[
result => result.ResultSummary.Outcome.ShouldBe("Completed"),
result => result.ResultSummary.Counters.Total.ShouldBe(1),
result => result.ResultSummary.Counters.Passed.ShouldBe(1),
result => result.ResultSummary.Counters.Failed.ShouldBe(0),
result => result.ResultSummary.Counters.NotExecuted.ShouldBe(0)
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,7 @@ namespace
public SkipAttribute(string reason) { }
public int Order { get; }
public string Reason { get; }
protected virtual string GetSkipReason(.TestRegisteredContext context) { }
public . OnTestRegistered(.TestRegisteredContext context) { }
public virtual .<bool> ShouldSkip(.TestRegisteredContext context) { }
}
Expand Down Expand Up @@ -1451,6 +1452,7 @@ namespace
public string TestName { get; }
public void SetHookExecutor(. executor) { }
public void SetParallelLimiter(. parallelLimit) { }
public void SetSkipped(string reason) { }
public void SetTestExecutor(. executor) { }
}
public class TestResult : <.TestResult>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,7 @@ namespace
public SkipAttribute(string reason) { }
public int Order { get; }
public string Reason { get; }
protected virtual string GetSkipReason(.TestRegisteredContext context) { }
public . OnTestRegistered(.TestRegisteredContext context) { }
public virtual .<bool> ShouldSkip(.TestRegisteredContext context) { }
}
Expand Down Expand Up @@ -1451,6 +1452,7 @@ namespace
public string TestName { get; }
public void SetHookExecutor(. executor) { }
public void SetParallelLimiter(. parallelLimit) { }
public void SetSkipped(string reason) { }
public void SetTestExecutor(. executor) { }
}
public class TestResult : <.TestResult>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,7 @@ namespace
public SkipAttribute(string reason) { }
public int Order { get; }
public string Reason { get; }
protected virtual string GetSkipReason(.TestRegisteredContext context) { }
public . OnTestRegistered(.TestRegisteredContext context) { }
public virtual .<bool> ShouldSkip(.TestRegisteredContext context) { }
}
Expand Down Expand Up @@ -1451,6 +1452,7 @@ namespace
public string TestName { get; }
public void SetHookExecutor(. executor) { }
public void SetParallelLimiter(. parallelLimit) { }
public void SetSkipped(string reason) { }
public void SetTestExecutor(. executor) { }
}
public class TestResult : <.TestResult>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,7 @@ namespace
public SkipAttribute(string reason) { }
public int Order { get; }
public string Reason { get; }
protected virtual string GetSkipReason(.TestRegisteredContext context) { }
public . OnTestRegistered(.TestRegisteredContext context) { }
public virtual .<bool> ShouldSkip(.TestRegisteredContext context) { }
}
Expand Down Expand Up @@ -1404,6 +1405,7 @@ namespace
public string TestName { get; }
public void SetHookExecutor(. executor) { }
public void SetParallelLimiter(. parallelLimit) { }
public void SetSkipped(string reason) { }
public void SetTestExecutor(. executor) { }
}
public class TestResult : <.TestResult>
Expand Down
93 changes: 93 additions & 0 deletions TUnit.TestProject/DynamicSkipReasonTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
namespace TUnit.TestProject;

using TUnit.Core.Interfaces;

public class DynamicSkipReasonTests
{
[Test]
[CustomSkipViaSetSkipped("TestDevice123")]
public void TestSkippedViaSetSkippedMethod()
{
throw new Exception("This test should have been skipped!");
}

[Test]
[CustomSkipViaGetSkipReason("CustomDevice456")]
public void TestSkippedViaGetSkipReasonOverride()
{
throw new Exception("This test should have been skipped!");
}

[Test]
[ConditionalSkipAttribute("AllowedDevice")]
public void TestNotSkippedWhenConditionFalse()
{
}
}

/// <summary>
/// Custom attribute that uses TestRegisteredContext.SetSkipped() to skip tests dynamically
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CustomSkipViaSetSkippedAttribute : Attribute, ITestRegisteredEventReceiver
{
private readonly string _deviceName;

public CustomSkipViaSetSkippedAttribute(string deviceName)
{
_deviceName = deviceName;
}

public int Order => int.MinValue;

public ValueTask OnTestRegistered(TestRegisteredContext context)
{
context.SetSkipped($"Test '{context.TestName}' is not supported on device '{_deviceName}'");
return default;
}
}

/// <summary>
/// Custom SkipAttribute that overrides GetSkipReason() to provide dynamic skip reasons
/// </summary>
public class CustomSkipViaGetSkipReasonAttribute : SkipAttribute
{
private readonly string _deviceName;

public CustomSkipViaGetSkipReasonAttribute(string deviceName)
: base("Device-specific skip")
{
_deviceName = deviceName;
}

protected override string GetSkipReason(TestRegisteredContext context)
{
return $"Test '{context.TestName}' skipped for device '{_deviceName}' via GetSkipReason override";
}
}

/// <summary>
/// Conditional skip attribute that only skips if device name is not in allowed list
/// </summary>
public class ConditionalSkipAttribute : SkipAttribute
{
private readonly string _deviceName;
private static readonly string[] AllowedDevices = ["AllowedDevice", "AnotherAllowedDevice"];

public ConditionalSkipAttribute(string deviceName)
: base("Device not allowed")
{
_deviceName = deviceName;
}

public override Task<bool> ShouldSkip(TestRegisteredContext context)
{
bool shouldSkip = !AllowedDevices.Contains(_deviceName);
return Task.FromResult(shouldSkip);
}

protected override string GetSkipReason(TestRegisteredContext context)
{
return $"Test '{context.TestName}' skipped because device '{_deviceName}' is not in allowed list";
}
}
Loading