diff --git a/README.md b/README.md index ff7cc1b..18b6fd3 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/Xunit.SkippableFact/Sdk/SkippableFactTestCase.cs b/src/Xunit.SkippableFact/Sdk/SkippableFactTestCase.cs index feb5e23..f81b9f5 100644 --- a/src/Xunit.SkippableFact/Sdk/SkippableFactTestCase.cs +++ b/src/Xunit.SkippableFact/Sdk/SkippableFactTestCase.cs @@ -88,4 +88,10 @@ public override void Deserialize(IXunitSerializationInfo data) base.Deserialize(data); this.SkippingExceptionNames = data.GetValue(nameof(this.SkippingExceptionNames)); } + + /// + protected override string GetSkipReason(IAttributeInfo factAttribute) + { + return this.TestMethod.GetPlatformSkipReason() ?? base.GetSkipReason(factAttribute); + } } diff --git a/src/Xunit.SkippableFact/Sdk/SkippableTheoryTestCase.cs b/src/Xunit.SkippableFact/Sdk/SkippableTheoryTestCase.cs index 09d5903..800ebeb 100644 --- a/src/Xunit.SkippableFact/Sdk/SkippableTheoryTestCase.cs +++ b/src/Xunit.SkippableFact/Sdk/SkippableTheoryTestCase.cs @@ -86,4 +86,10 @@ public override void Deserialize(IXunitSerializationInfo data) base.Deserialize(data); this.SkippingExceptionNames = data.GetValue(nameof(this.SkippingExceptionNames)); } + + /// + protected override string GetSkipReason(IAttributeInfo factAttribute) + { + return this.TestMethod.GetPlatformSkipReason() ?? base.GetSkipReason(factAttribute); + } } diff --git a/src/Xunit.SkippableFact/Sdk/TestMethodExtensions.cs b/src/Xunit.SkippableFact/Sdk/TestMethodExtensions.cs new file mode 100644 index 0000000..e17efcf --- /dev/null +++ b/src/Xunit.SkippableFact/Sdk/TestMethodExtensions.cs @@ -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; + +/// +/// Extensions methods on . +/// +internal static class TestMethodExtensions +{ + /// + /// Assesses whether the test method can run on the current platform by looking at the [SupportedOSPlatform] attributes on the test method and on the test class. + /// + /// The . + /// A description of the supported platforms if the test can not run on the current platform or if the test can run on the current platform. + public static string? GetPlatformSkipReason(this ITestMethod testMethod) + { +#if NET462 + return null; +#else + var platforms = new HashSet(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 platforms, IEnumerable supportedPlatformAttributes) + { + foreach (IAttributeInfo supportedPlatformAttribute in supportedPlatformAttributes) + { + platforms.Add(supportedPlatformAttribute.GetNamedArgument("PlatformName")); + } + } +#endif +} diff --git a/test/Xunit.SkippableFact.Tests/SampleTests.cs b/test/Xunit.SkippableFact.Tests/SampleTests.cs index fd1aa0d..63c95dd 100644 --- a/test/Xunit.SkippableFact.Tests/SampleTests.cs +++ b/test/Xunit.SkippableFact.Tests/SampleTests.cs @@ -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; @@ -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(), $"{nameof(this.LinuxOnly)} should only run on Linux"); + } + + [SkippableFact, SupportedOSPlatform("macOS")] + public void MacOsOnly() + { + Assert.True(OperatingSystem.IsMacOS(), $"{nameof(this.MacOsOnly)} should only run on macOS"); + } + + [SkippableFact, SupportedOSPlatform("Windows")] + public void WindowsOnly() + { + Assert.True(OperatingSystem.IsWindows(), $"{nameof(this.WindowsOnly)} should only run on Windows"); + } + + [SkippableFact, SupportedOSPlatform("Android"), SupportedOSPlatform("Browser")] + public void AndroidAndBrowserFact() + { + Assert.True(OperatingSystem.IsAndroid() || OperatingSystem.IsBrowser(), $"{nameof(this.AndroidAndBrowserFact)} 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(), $"{nameof(this.AndroidAndBrowserTheory)} 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(), $"{nameof(this.AndroidAndBrowserAndWasiOnly)} should only run on Android, Browser and Wasi"); + } +#endif }