Skip to content

Commit 0f0e95f

Browse files
committed
feature: Added ExcludeOnAttribute and RunOnAttribute
as discussed in #2508
1 parent 00df8bb commit 0f0e95f

File tree

3 files changed

+214
-0
lines changed

3 files changed

+214
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System.Runtime.InteropServices;
2+
using TUnit.Core.Enums;
3+
4+
namespace TUnit.Core;
5+
6+
/// <summary>
7+
/// Attribute that excludes a test from running on specific operating systems.
8+
/// </summary>
9+
/// <param name="OperatingSystem">
10+
/// Defines the operating systems on which the test should not run.
11+
/// </param>
12+
/// <remarks>
13+
/// <para>
14+
/// The <see cref="ExcludeOnAttribute"/> is used to specify that a test should not run on certain operating systems.
15+
/// Tests with this attribute will be skipped on operating systems that match the specified criteria.
16+
/// </para>
17+
/// <para>
18+
/// You can specify multiple operating systems by combining the <see cref="OS"/> enum values with the bitwise OR operator.
19+
/// </para>
20+
/// </remarks>
21+
/// <example>
22+
/// <code>
23+
/// // Skip on Windows
24+
/// [Test, ExcludeOn(OS.Windows)]
25+
/// public void NonWindowsOnlyTest()
26+
/// {
27+
/// // This test will run on Linux and macOS, but not on Windows
28+
/// }
29+
///
30+
/// // Skip on both Windows and Linux
31+
/// [Test, ExcludeOn(OS.Windows | OS.Linux)]
32+
/// public void MacOsOnlyTest()
33+
/// {
34+
/// // This test will only run on macOS
35+
/// }
36+
///
37+
/// // Skip on all supported platforms (essentially always skips the test)
38+
/// [Test, ExcludeOn(OS.Windows | OS.Linux | OS.MacOs)]
39+
/// public void NeverRunTest()
40+
/// {
41+
/// // This test will not run on any supported platform
42+
/// }
43+
/// </code>
44+
/// </example>
45+
/// <seealso cref="SkipAttribute"/>
46+
/// <seealso cref="RunOnAttribute"/>
47+
/// <seealso cref="OS"/>
48+
public sealed class ExcludeOnAttribute(OS OperatingSystem) : SkipAttribute(GetReason(OperatingSystem))
49+
{
50+
/// <inheritdoc />
51+
public override Task<bool> ShouldSkip(BeforeTestContext context)
52+
{
53+
// Check if the current platform matches any of the excluded operating systems
54+
bool shouldSkip =
55+
(OperatingSystem.HasFlag(OS.Windows) && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
56+
#if NET
57+
// Only validate Linux and macOS on .NET 5+ where these OS flags are available
58+
|| (OperatingSystem.HasFlag(OS.Linux) && RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
59+
|| (OperatingSystem.HasFlag(OS.MacOs) && RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
60+
#endif
61+
;
62+
63+
// Return true if the test should be skipped (if we're on an excluded OS)
64+
return Task.FromResult(shouldSkip);
65+
}
66+
67+
private static string GetReason(OS operatingSystems)
68+
{
69+
var excludedOperatingSystems =
70+
#if NET
71+
Enum.GetValues<OS>()
72+
#else
73+
Enum.GetValues(typeof(OS)).Cast<OS>()
74+
#endif
75+
.Where(os => operatingSystems.HasFlag(os))
76+
.ToArray();
77+
78+
return $"The test is skipped because it is configured to not run on the current operating system: `{string.Join("`, `", excludedOperatingSystems)}`";
79+
}
80+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
using System.Runtime.InteropServices;
2+
using TUnit.Core.Enums;
3+
4+
namespace TUnit.Core;
5+
6+
/// <summary>
7+
/// Attribute that restricts a test to run only on specific operating systems.
8+
/// </summary>
9+
/// <param name="OperatingSystem">
10+
/// Defines the operating systems on which the test should run.
11+
/// </param>
12+
/// <remarks>
13+
/// <para>
14+
/// The <see cref="RunOnAttribute"/> is used to specify that a test should only run on certain operating systems.
15+
/// Tests with this attribute will be skipped on operating systems that do not match the specified criteria.
16+
/// </para>
17+
/// <para>
18+
/// You can specify multiple operating systems by combining the <see cref="OS"/> enum values with the bitwise OR operator.
19+
/// </para>
20+
/// </remarks>
21+
/// <example>
22+
/// <code>
23+
/// // Run only on Windows
24+
/// [Test, RunOn(OS.Windows)]
25+
/// public void WindowsOnlyTest()
26+
/// {
27+
/// // This test will only run on Windows
28+
/// }
29+
///
30+
/// // Run on both Windows and Linux
31+
/// [Test, RunOn(OS.Windows | OS.Linux)]
32+
/// public void WindowsAndLinuxTest()
33+
/// {
34+
/// // This test will run on Windows and Linux, but not on macOS
35+
/// }
36+
///
37+
/// // Run on all supported platforms
38+
/// [Test, RunOn(OS.Windows | OS.Linux | OS.MacOs)]
39+
/// public void AllPlatformsTest()
40+
/// {
41+
/// // This test will run on all supported platforms
42+
/// }
43+
/// </code>
44+
/// </example>
45+
/// <seealso cref="SkipAttribute"/>
46+
/// <seealso cref="OS"/>
47+
public sealed class RunOnAttribute(OS OperatingSystem) : SkipAttribute(GetReason(OperatingSystem))
48+
{
49+
/// <inheritdoc />
50+
public override Task<bool> ShouldSkip(BeforeTestContext context)
51+
{
52+
// Check if the current platform matches any of the allowed operating systems
53+
bool shouldRun =
54+
(OperatingSystem.HasFlag(OS.Windows) && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
55+
#if NET
56+
// Only validate Linux and macOS on .NET 5+ where these OS flags are available
57+
|| (OperatingSystem.HasFlag(OS.Linux) && RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
58+
|| (OperatingSystem.HasFlag(OS.MacOs) && RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
59+
#endif
60+
;
61+
62+
// Return true if the test should be skipped (opposite of shouldRun)
63+
return Task.FromResult(!shouldRun);
64+
}
65+
66+
private static string GetReason(OS operatingSystems)
67+
{
68+
var selectedOperatingSystems =
69+
#if NET
70+
Enum.GetValues<OS>()
71+
#else
72+
Enum.GetValues(typeof(OS)).Cast<OS>()
73+
#endif
74+
.Where(os => operatingSystems.HasFlag(os))
75+
.ToArray();
76+
77+
return $"The test is skipped because it is configured to run on the current operating system: `{string.Join("`, `", selectedOperatingSystems)}`";
78+
}
79+
}

TUnit.Core/Enums/OS.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
namespace TUnit.Core.Enums;
2+
3+
/// <summary>
4+
/// Represents operating systems that can be specified for test execution constraints.
5+
/// </summary>
6+
/// <remarks>
7+
/// <para>
8+
/// This enum is marked with the <see cref="FlagsAttribute"/>, which allows combining multiple operating systems
9+
/// using bitwise operations when used with attributes like <see cref="RunOnAttribute"/>.
10+
/// </para>
11+
/// <para>
12+
/// The primary use case is to restrict test execution to specific operating systems through
13+
/// the <see cref="RunOnAttribute"/>.
14+
/// </para>
15+
/// </remarks>
16+
/// <example>
17+
/// <code>
18+
/// // Specify a test should run only on Windows
19+
/// [RunOn(OS.Windows)]
20+
///
21+
/// // Specify a test should run on either Windows or Linux
22+
/// [RunOn(OS.Windows | OS.Linux)]
23+
///
24+
/// // Specify a test should run on all supported platforms
25+
/// [RunOn(OS.Windows | OS.Linux | OS.MacOs)]
26+
/// </code>
27+
/// </example>
28+
/// <seealso cref="RunOnAttribute"/>
29+
[Flags]
30+
public enum OS
31+
{
32+
/// <summary>
33+
/// Represents the Linux operating system.
34+
/// </summary>
35+
/// <remarks>
36+
/// Tests with this flag will be executed on Linux platforms when used with <see cref="RunOnAttribute"/>.
37+
/// </remarks>
38+
Linux = 1,
39+
40+
/// <summary>
41+
/// Represents the Windows operating system.
42+
/// </summary>
43+
/// <remarks>
44+
/// Tests with this flag will be executed on Windows platforms when used with <see cref="RunOnAttribute"/>.
45+
/// </remarks>
46+
Windows = 2,
47+
48+
/// <summary>
49+
/// Represents the macOS operating system.
50+
/// </summary>
51+
/// <remarks>
52+
/// Tests with this flag will be executed on macOS platforms when used with <see cref="RunOnAttribute"/>.
53+
/// </remarks>
54+
MacOs = 4
55+
}

0 commit comments

Comments
 (0)