diff --git a/src/Testing/ref/Microsoft.AspNetCore.Testing.net46.cs b/src/Testing/ref/Microsoft.AspNetCore.Testing.net46.cs
index 5fa7e9422e..a7191eb156 100644
--- a/src/Testing/ref/Microsoft.AspNetCore.Testing.net46.cs
+++ b/src/Testing/ref/Microsoft.AspNetCore.Testing.net46.cs
@@ -170,6 +170,13 @@ namespace Microsoft.AspNetCore.Testing
System.Threading.Tasks.Task OnTestStartAsync(Microsoft.AspNetCore.Testing.TestContext context, System.Threading.CancellationToken cancellationToken);
}
[System.AttributeUsageAttribute(System.AttributeTargets.Assembly | System.AttributeTargets.Class | System.AttributeTargets.Method, AllowMultiple=true)]
+ public partial class MaximumOSVersionAttribute : System.Attribute, Microsoft.AspNetCore.Testing.ITestCondition
+ {
+ public MaximumOSVersionAttribute(Microsoft.AspNetCore.Testing.OperatingSystems operatingSystem, string maxVersion) { }
+ public bool IsMet { get { throw null; } }
+ public string SkipReason { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
+ }
+ [System.AttributeUsageAttribute(System.AttributeTargets.Assembly | System.AttributeTargets.Class | System.AttributeTargets.Method, AllowMultiple=true)]
public partial class MinimumOSVersionAttribute : System.Attribute, Microsoft.AspNetCore.Testing.ITestCondition
{
public MinimumOSVersionAttribute(Microsoft.AspNetCore.Testing.OperatingSystems operatingSystem, string minVersion) { }
diff --git a/src/Testing/ref/Microsoft.AspNetCore.Testing.netstandard2.0.cs b/src/Testing/ref/Microsoft.AspNetCore.Testing.netstandard2.0.cs
index 5fa7e9422e..a7191eb156 100644
--- a/src/Testing/ref/Microsoft.AspNetCore.Testing.netstandard2.0.cs
+++ b/src/Testing/ref/Microsoft.AspNetCore.Testing.netstandard2.0.cs
@@ -170,6 +170,13 @@ namespace Microsoft.AspNetCore.Testing
System.Threading.Tasks.Task OnTestStartAsync(Microsoft.AspNetCore.Testing.TestContext context, System.Threading.CancellationToken cancellationToken);
}
[System.AttributeUsageAttribute(System.AttributeTargets.Assembly | System.AttributeTargets.Class | System.AttributeTargets.Method, AllowMultiple=true)]
+ public partial class MaximumOSVersionAttribute : System.Attribute, Microsoft.AspNetCore.Testing.ITestCondition
+ {
+ public MaximumOSVersionAttribute(Microsoft.AspNetCore.Testing.OperatingSystems operatingSystem, string maxVersion) { }
+ public bool IsMet { get { throw null; } }
+ public string SkipReason { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
+ }
+ [System.AttributeUsageAttribute(System.AttributeTargets.Assembly | System.AttributeTargets.Class | System.AttributeTargets.Method, AllowMultiple=true)]
public partial class MinimumOSVersionAttribute : System.Attribute, Microsoft.AspNetCore.Testing.ITestCondition
{
public MinimumOSVersionAttribute(Microsoft.AspNetCore.Testing.OperatingSystems operatingSystem, string minVersion) { }
diff --git a/src/Testing/src/xunit/MaximumOSVersionAttribute.cs b/src/Testing/src/xunit/MaximumOSVersionAttribute.cs
new file mode 100644
index 0000000000..82d59910be
--- /dev/null
+++ b/src/Testing/src/xunit/MaximumOSVersionAttribute.cs
@@ -0,0 +1,79 @@
+// 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
+{
+ ///
+ /// Skips a test if the OS is the given type (Windows) and the OS version is greater than specified.
+ /// E.g. Specifying Window 8 skips on Win 10, but not on Linux. Combine with OSSkipConditionAttribute as needed.
+ ///
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)]
+ public class MaximumOSVersionAttribute : Attribute, ITestCondition
+ {
+ private readonly OperatingSystems _targetOS;
+ private readonly Version _maxVersion;
+ private readonly OperatingSystems _currentOS;
+ private readonly Version _currentVersion;
+ private readonly bool _skip;
+
+ public MaximumOSVersionAttribute(OperatingSystems operatingSystem, string maxVersion) :
+ this(operatingSystem, Version.Parse(maxVersion), GetCurrentOS(), GetCurrentOSVersion())
+ {
+ }
+
+ // to enable unit testing
+ internal MaximumOSVersionAttribute(OperatingSystems targetOS, Version maxVersion, OperatingSystems currentOS, Version currentVersion)
+ {
+ if (targetOS != OperatingSystems.Windows)
+ {
+ throw new NotImplementedException("Max version support is only implemented for Windows.");
+ }
+ _targetOS = targetOS;
+ _maxVersion = maxVersion;
+ _currentOS = currentOS;
+ _currentVersion = currentVersion;
+
+ // Do not skip other OS's, Use OSSkipConditionAttribute or a separate MaximumOsVersionAttribute for that.
+ _skip = _targetOS == _currentOS && _maxVersion < _currentVersion;
+ SkipReason = $"This test requires {_targetOS} {_maxVersion} or earlier.";
+ }
+
+ // Since a test would be executed only if 'IsMet' is true, return false if we want to skip
+ public bool IsMet => !_skip;
+
+ public string SkipReason { get; set; }
+
+ private static 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 implemented, 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/test/MaximumOSVersionAttributeTest.cs b/src/Testing/test/MaximumOSVersionAttributeTest.cs
new file mode 100644
index 0000000000..ca2faa76e5
--- /dev/null
+++ b/src/Testing/test/MaximumOSVersionAttributeTest.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 MaximumOSVersionAttributeTest
+ {
+ [Fact]
+ public void Linux_ThrowsNotImplemeneted()
+ {
+ Assert.Throws(() => new MaximumOSVersionAttribute(OperatingSystems.Linux, "2.5"));
+ }
+
+ [Fact]
+ public void Mac_ThrowsNotImplemeneted()
+ {
+ Assert.Throws(() => new MaximumOSVersionAttribute(OperatingSystems.MacOSX, "2.5"));
+ }
+
+ [Fact]
+ public void WindowsOrLinux_ThrowsNotImplemeneted()
+ {
+ Assert.Throws(() => new MaximumOSVersionAttribute(OperatingSystems.Linux | OperatingSystems.Windows, "2.5"));
+ }
+
+ [Fact]
+ public void DoesNotSkip_EarlierVersions()
+ {
+ var osSkipAttribute = new MaximumOSVersionAttribute(
+ OperatingSystems.Windows,
+ new Version("2.5"),
+ OperatingSystems.Windows,
+ new Version("2.0"));
+
+ Assert.True(osSkipAttribute.IsMet);
+ }
+
+ [Fact]
+ public void DoesNotSkip_SameVersion()
+ {
+ var osSkipAttribute = new MaximumOSVersionAttribute(
+ OperatingSystems.Windows,
+ new Version("2.5"),
+ OperatingSystems.Windows,
+ new Version("2.5"));
+
+ Assert.True(osSkipAttribute.IsMet);
+ }
+
+ [Fact]
+ public void Skip_LaterVersion()
+ {
+ var osSkipAttribute = new MaximumOSVersionAttribute(
+ OperatingSystems.Windows,
+ new Version("2.5"),
+ OperatingSystems.Windows,
+ new Version("3.0"));
+
+ Assert.False(osSkipAttribute.IsMet);
+ }
+
+ [Fact]
+ public void DoesNotSkip_WhenOnlyVersionsMatch()
+ {
+ var osSkipAttribute = new MaximumOSVersionAttribute(
+ OperatingSystems.Windows,
+ new Version("2.5"),
+ OperatingSystems.Linux,
+ new Version("2.5"));
+
+ Assert.True(osSkipAttribute.IsMet);
+ }
+ }
+}
diff --git a/src/Testing/test/MaximumOSVersionTest.cs b/src/Testing/test/MaximumOSVersionTest.cs
new file mode 100644
index 0000000000..e9a0b08d99
--- /dev/null
+++ b/src/Testing/test/MaximumOSVersionTest.cs
@@ -0,0 +1,75 @@
+// 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
+{
+ [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)]
+ public class MaximumOSVersionTest
+ {
+ [ConditionalFact]
+ [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win7)]
+ public void RunTest_Win7DoesRunOnWin7()
+ {
+ Assert.True(
+ RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
+ Environment.OSVersion.Version.ToString().StartsWith("6.1"),
+ "Test should only be running on Win7 or Win2008R2.");
+ }
+
+ [ConditionalTheory]
+ [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win7)]
+ [InlineData(1)]
+ public void RunTheory_Win7DoesRunOnWin7(int arg)
+ {
+ Assert.True(
+ RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
+ Environment.OSVersion.Version.ToString().StartsWith("6.1"),
+ "Test should only be running on Win7 or Win2008R2.");
+ }
+
+ [ConditionalFact]
+ [MaximumOSVersion(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]
+ [MaximumOSVersion(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));
+ }
+ }
+
+ [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win7)]
+ [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)]
+ public class OSMaxVersionClassTest
+ {
+ [ConditionalFact]
+ public void TestSkipClass_Win7DoesRunOnWin7()
+ {
+ Assert.True(
+ RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
+ Environment.OSVersion.Version.ToString().StartsWith("6.1"),
+ "Test should only be running on Win7 or Win2008R2.");
+ }
+ }
+}