diff --git a/src/Testing/src/xunit/OSMinVersionAttribute.cs b/src/Testing/src/xunit/OSMinVersionAttribute.cs
new file mode 100644
index 0000000000..3bef606ee6
--- /dev/null
+++ b/src/Testing/src/xunit/OSMinVersionAttribute.cs
@@ -0,0 +1,81 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.AspNetCore.Testing
+{
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)]
+ public class OSMinVersionAttribute : Attribute, ITestCondition
+ {
+ private readonly OperatingSystems _targetOS;
+ private readonly Version _minVersion;
+ private readonly OperatingSystems _currentOS;
+ private readonly Version _currentVersion;
+ private readonly bool _skip;
+
+ ///
+ /// Used to indicate the minimum version a test can run on for the given operating system.
+ /// Also add to skip other operating systems.
+ ///
+ /// The OS to check for a version. Only Windows is currently supported.
+ /// The minimum OS version NOT to skip.
+ public OSMinVersionAttribute(OperatingSystems targetOS, string minVersion) :
+ this(targetOS, Version.Parse(minVersion), GetCurrentOS(), GetCurrentOSVersion())
+ {
+ }
+
+ // to enable unit testing
+ internal OSMinVersionAttribute(OperatingSystems targetOS, Version minVersion, OperatingSystems currentOS, Version currentVersion)
+ {
+ if (targetOS != OperatingSystems.Windows)
+ {
+ throw new NotImplementedException(targetOS.ToString());
+ }
+
+ _targetOS = targetOS;
+ _minVersion = minVersion;
+ _currentOS = currentOS;
+ _currentVersion = currentVersion;
+
+ _skip = _targetOS == _currentOS && _minVersion > _currentVersion;
+ SkipReason = $"The test cannot run on this operating system version '{currentVersion}'.";
+ }
+
+ // Since a test would be excuted only if 'IsMet' is true, return false if we want to skip
+ public bool IsMet => !_skip;
+
+ public string SkipReason { get; set; }
+
+ static private OperatingSystems GetCurrentOS()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return OperatingSystems.Windows;
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ return OperatingSystems.Linux;
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ return OperatingSystems.MacOSX;
+ }
+ throw new PlatformNotSupportedException();
+ }
+
+ static private Version GetCurrentOSVersion()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return Environment.OSVersion.Version;
+ }
+ else
+ {
+ // Not implmeneted, but this will still be called before the OS check happens so don't throw.
+ return new Version(0, 0);
+ }
+ }
+ }
+}
diff --git a/src/Testing/src/xunit/WindowsVersions.cs b/src/Testing/src/xunit/WindowsVersions.cs
index d89da44de3..d0ef86d1a8 100644
--- a/src/Testing/src/xunit/WindowsVersions.cs
+++ b/src/Testing/src/xunit/WindowsVersions.cs
@@ -3,6 +3,9 @@
namespace Microsoft.AspNetCore.Testing
{
+ ///
+ /// https://en.wikipedia.org/wiki/Windows_10_version_history
+ ///
public static class WindowsVersions
{
public const string Win7 = "6.1";
@@ -14,5 +17,20 @@ namespace Microsoft.AspNetCore.Testing
public const string Win81 = "6.3";
public const string Win10 = "10.0";
+
+ ///
+ /// 1803, RS4, 17134
+ ///
+ public const string Win10_RS4 = "10.0.17134";
+
+ ///
+ /// 1909, 19H2, 18363
+ ///
+ public const string Win10_19H2 = "10.0.18363";
+
+ ///
+ /// _, 20H2, 18990
+ ///
+ public const string Win10_20H1 = "10.0.18990";
}
}
diff --git a/src/Testing/test/OSMinVersionAttributeTest.cs b/src/Testing/test/OSMinVersionAttributeTest.cs
new file mode 100644
index 0000000000..f156607e8b
--- /dev/null
+++ b/src/Testing/test/OSMinVersionAttributeTest.cs
@@ -0,0 +1,77 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Testing
+{
+ public class OSMinVersionAttributeTest
+ {
+ [Fact]
+ public void Linux_ThrowsNotImplemeneted()
+ {
+ Assert.Throws(() => new OSMinVersionAttribute(OperatingSystems.Linux, "2.5"));
+ }
+
+ [Fact]
+ public void Mac_ThrowsNotImplemeneted()
+ {
+ Assert.Throws(() => new OSMinVersionAttribute(OperatingSystems.MacOSX, "2.5"));
+ }
+
+ [Fact]
+ public void WindowsOrLinux_ThrowsNotImplemeneted()
+ {
+ Assert.Throws(() => new OSMinVersionAttribute(OperatingSystems.Linux | OperatingSystems.Windows, "2.5"));
+ }
+
+ [Fact]
+ public void DoesNotSkip_LaterVersions()
+ {
+ var osSkipAttribute = new OSMinVersionAttribute(
+ OperatingSystems.Windows,
+ new Version("2.0"),
+ OperatingSystems.Windows,
+ new Version("2.5"));
+
+ Assert.True(osSkipAttribute.IsMet);
+ }
+
+ [Fact]
+ public void DoesNotSkip_SameVersion()
+ {
+ var osSkipAttribute = new OSMinVersionAttribute(
+ OperatingSystems.Windows,
+ new Version("2.5"),
+ OperatingSystems.Windows,
+ new Version("2.5"));
+
+ Assert.True(osSkipAttribute.IsMet);
+ }
+
+ [Fact]
+ public void Skip_EarlierVersion()
+ {
+ var osSkipAttribute = new OSMinVersionAttribute(
+ OperatingSystems.Windows,
+ new Version("3.0"),
+ OperatingSystems.Windows,
+ new Version("2.5"));
+
+ Assert.False(osSkipAttribute.IsMet);
+ }
+
+ [Fact]
+ public void DoesNotSkip_WhenOnlyVersionsMatch()
+ {
+ var osSkipAttribute = new OSMinVersionAttribute(
+ OperatingSystems.Windows,
+ new Version("2.5"),
+ OperatingSystems.Linux,
+ new Version("2.5"));
+
+ Assert.True(osSkipAttribute.IsMet);
+ }
+ }
+}
diff --git a/src/Testing/test/OSMinVersionTest.cs b/src/Testing/test/OSMinVersionTest.cs
new file mode 100644
index 0000000000..30233823e2
--- /dev/null
+++ b/src/Testing/test/OSMinVersionTest.cs
@@ -0,0 +1,73 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.InteropServices;
+using Microsoft.Win32;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Testing
+{
+ public class OSMinVersionTest
+ {
+ [ConditionalFact]
+ [OSMinVersion(OperatingSystems.Windows, WindowsVersions.Win8)]
+ public void RunTest_Win8DoesNotRunOnWin7()
+ {
+ Assert.False(
+ RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
+ Environment.OSVersion.Version.ToString().StartsWith("6.1"),
+ "Test should not be running on Win7 or Win2008R2.");
+ }
+
+ [ConditionalTheory]
+ [OSMinVersion(OperatingSystems.Windows, WindowsVersions.Win8)]
+ [InlineData(1)]
+ public void RunTheory_Win8DoesNotRunOnWin7(int arg)
+ {
+ Assert.False(
+ RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
+ Environment.OSVersion.Version.ToString().StartsWith("6.1"),
+ "Test should not be running on Win7 or Win2008R2.");
+ }
+
+ [ConditionalFact]
+ [OSMinVersion(OperatingSystems.Windows, WindowsVersions.Win10_RS4)]
+ [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)]
+ public void RunTest_Win10_RS4()
+ {
+ Assert.True(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
+ var versionKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
+ Assert.NotNull(versionKey);
+ var currentVersion = (string)versionKey.GetValue("CurrentBuildNumber");
+ Assert.NotNull(currentVersion);
+ Assert.True(17134 <= int.Parse(currentVersion));
+ }
+
+ [ConditionalFact]
+ [OSMinVersion(OperatingSystems.Windows, WindowsVersions.Win10_19H2)]
+ [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)]
+ public void RunTest_Win10_19H2()
+ {
+ Assert.True(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
+ var versionKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
+ Assert.NotNull(versionKey);
+ var currentVersion = (string)versionKey.GetValue("CurrentBuildNumber");
+ Assert.NotNull(currentVersion);
+ Assert.True(18363 <= int.Parse(currentVersion));
+ }
+ }
+
+ [OSMinVersion(OperatingSystems.Windows, WindowsVersions.Win8)]
+ public class OSMinVersionClassTest
+ {
+ [ConditionalFact]
+ public void TestSkipClass_Win8DoesNotRunOnWin7()
+ {
+ Assert.False(
+ RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
+ Environment.OSVersion.Version.ToString().StartsWith("6.1"),
+ "Test should not be running on Win7 or Win2008R2.");
+ }
+ }
+}