Skip to content

Commit

Permalink
Add skipping tests based on the [SupportedOSPlatform] attribute
Browse files Browse the repository at this point in the history
Adding support for `SupportedOSPlatform` is a great because this attributes suppresses the CA1416 code analysis warning.

This feature was requested in xunit/xunit#2820 but was not implemented in xUnit.net
  • Loading branch information
0xced committed Nov 28, 2024
1 parent 8bc417a commit 34c92ae
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 1 deletion.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,23 @@ public void TestFunctionalityWhichIsNotSupportedOnSomePlatforms()
}
```

### The [SupportedOSPlatform] attribute

Since version 1.5, `Xunit.SkippableFact` understands the `SupportedOSPlatform` attribute to skip tests on unsupported platforms.

```csharp
[SkippableFact, SupportedOSPlatform("Windows")]
public void TestCngKey()
{
var key = CngKey.Create(CngAlgorithm.Sha256);
Assert.NotNull(key);
}
```

Without `[SupportedOSPlatform("Windows")` the [CA1416](CA1416) code analysis warning would trigger:
> This call site is reachable on all platforms. 'CngKey. Create(CngAlgorithm)' is only supported on: 'windows'.
Adding `[SupportedOSPlatform("Windows")` both suppresses this platform compatibility warning and skips the test when running on Linux or macOS.

[NuPkg]: https://www.nuget.org/packages/Xunit.SkippableFact
[CA1416]: https://learn.microsoft.com/en-gb/dotnet/fundamentals/code-analysis/quality-rules/ca1416
6 changes: 6 additions & 0 deletions src/Xunit.SkippableFact/Sdk/SkippableFactTestCase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,10 @@ public override void Deserialize(IXunitSerializationInfo data)
base.Deserialize(data);
this.SkippingExceptionNames = data.GetValue<string[]>(nameof(this.SkippingExceptionNames));
}

/// <inheritdoc/>
protected override string GetSkipReason(IAttributeInfo factAttribute)
{
return this.TestMethod.GetPlatformSkipReason() ?? base.GetSkipReason(factAttribute);
}
}
6 changes: 6 additions & 0 deletions src/Xunit.SkippableFact/Sdk/SkippableTheoryTestCase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,10 @@ public override void Deserialize(IXunitSerializationInfo data)
base.Deserialize(data);
this.SkippingExceptionNames = data.GetValue<string[]>(nameof(this.SkippingExceptionNames));
}

/// <inheritdoc/>
protected override string GetSkipReason(IAttributeInfo factAttribute)
{
return this.TestMethod.GetPlatformSkipReason() ?? base.GetSkipReason(factAttribute);
}
}
47 changes: 47 additions & 0 deletions src/Xunit.SkippableFact/Sdk/TestMethodExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the Microsoft Public License (Ms-PL). See LICENSE.txt file in the project root for full license information.

using System.Runtime.InteropServices;
using Xunit.Abstractions;

namespace Xunit.Sdk;

/// <summary>
/// Extensions methods on <see cref="ITestMethod"/>.
/// </summary>
internal static class TestMethodExtensions
{
/// <summary>
/// Assesses whether the test method can run on the current platform by looking at the <c>[SupportedOSPlatform]</c> attributes on the test method and on the test class.
/// </summary>
/// <param name="testMethod">The <see cref="ITestMethod"/>.</param>
/// <returns>A description of the supported platforms if the test can not run on the current platform or <see langword="null"/> if the test can run on the current platform.</returns>
public static string? GetPlatformSkipReason(this ITestMethod testMethod)
{
#if NET462
return null;
#else
var platforms = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
AddPlatforms(platforms, testMethod.Method.GetCustomAttributes("System.Runtime.Versioning.SupportedOSPlatformAttribute"));
AddPlatforms(platforms, testMethod.Method.Type.GetCustomAttributes("System.Runtime.Versioning.SupportedOSPlatformAttribute"));

if (platforms.Count == 0 || platforms.Any(platform => RuntimeInformation.IsOSPlatform(OSPlatform.Create(platform))))
{
return null;
}

string platformsDescription = platforms.Count == 1 ? platforms.First() : string.Join(", ", platforms.Reverse().Skip(1).Reverse()) + " and " + platforms.Last();
return $"Only supported on {platformsDescription}";
#endif
}

#if !NET462
private static void AddPlatforms(HashSet<string> platforms, IEnumerable<IAttributeInfo> supportedPlatformAttributes)
{
foreach (IAttributeInfo supportedPlatformAttribute in supportedPlatformAttributes)
{
platforms.Add(supportedPlatformAttribute.GetNamedArgument<string>("PlatformName"));
}
}
#endif
}
43 changes: 42 additions & 1 deletion test/Xunit.SkippableFact.Tests/SampleTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the Microsoft Public License (Ms-PL). See LICENSE.txt file in the project root for full license information.

using System;
using System.Runtime.Versioning;

namespace Xunit.SkippableFact.Tests;

Expand Down Expand Up @@ -76,4 +76,45 @@ public void SkipInsideAssertThrows()
throw new Exception();
}));
}

#if NET5_0_OR_GREATER
[SkippableFact, SupportedOSPlatform("Linux")]
public void LinuxOnly()
{
Assert.True(OperatingSystem.IsLinux(), "This should only run on Linux");
}

[SkippableFact, SupportedOSPlatform("macOS")]
public void MacOsOnly()
{
Assert.True(OperatingSystem.IsMacOS(), "This should only run on macOS");
}

[SkippableFact, SupportedOSPlatform("Windows")]
public void WindowsOnly()
{
Assert.True(OperatingSystem.IsWindows(), "This should only run on Windows");
}

[SkippableFact, SupportedOSPlatform("Android"), SupportedOSPlatform("Browser")]
public void AndroidAndBrowserFact()
{
Assert.True(OperatingSystem.IsAndroid() || OperatingSystem.IsBrowser(), "This should only run on Android and Browser");
}

[SkippableTheory, SupportedOSPlatform("Android"), SupportedOSPlatform("Browser")]
[InlineData(1)]
[InlineData(2)]
public void AndroidAndBrowserTheory(int number)
{
_ = number;
Assert.True(OperatingSystem.IsAndroid() || OperatingSystem.IsBrowser(), "This should only run on Android and Browser");
}

[SkippableFact, SupportedOSPlatform("Android"), SupportedOSPlatform("Browser"), SupportedOSPlatform("Wasi")]
public void AndroidAndBrowserAndWasiOnly()
{
Assert.True(OperatingSystem.IsAndroid() || OperatingSystem.IsBrowser() || OperatingSystem.IsWasi(), "This should only run on Android, Browser and Wasi");
}
#endif
}

0 comments on commit 34c92ae

Please sign in to comment.