diff --git a/tests/ExchangeSharpTests/CryptoUtilityTests.cs b/tests/ExchangeSharpTests/CryptoUtilityTests.cs
index d53577f6..a0db5809 100644
--- a/tests/ExchangeSharpTests/CryptoUtilityTests.cs
+++ b/tests/ExchangeSharpTests/CryptoUtilityTests.cs
@@ -226,32 +226,47 @@ public void ConvertInvariantTest()
}
}
- [TestMethod]
+ [ConditionalTestMethod]
+ [PlatformSpecificTest(
+ ~TestPlatforms.OSX,
+ "Has an issue on MacOS. See https://github.com/dotnet/corefx/issues/42607"
+ )]
public async Task RateGate()
{
- const int timesPerPeriod = 1;
- const int ms = 100;
- const int loops = 5;
- double msMax = (double)ms * 1.5;
- double msMin = (double)ms * (1.0 / 1.5);
- RateGate gate = new RateGate(timesPerPeriod, TimeSpan.FromMilliseconds(ms));
- if (!(await gate.WaitToProceedAsync(0)))
- {
- throw new APIException("Rate gate should have allowed immediate access to first attempt");
- }
- for (int i = 0; i < loops; i++)
- {
- Stopwatch timer = Stopwatch.StartNew();
- await gate.WaitToProceedAsync();
- timer.Stop();
-
- if (i > 0)
- {
- // check for too much elapsed time with a little fudge
- Assert.IsTrue(timer.Elapsed.TotalMilliseconds <= msMax, "Rate gate took too long to wait in between calls: " + timer.Elapsed.TotalMilliseconds + "ms");
- Assert.IsTrue(timer.Elapsed.TotalMilliseconds >= msMin, "Rate gate took too little to wait in between calls: " + timer.Elapsed.TotalMilliseconds + "ms");
- }
- }
+ const int timesPerPeriod = 1;
+ const int ms = 100;
+ const int loops = 5;
+ const double msMax = (double) ms * 1.5;
+ const double msMin = (double) ms * (1.0 / 1.5);
+ var gate = new RateGate(timesPerPeriod, TimeSpan.FromMilliseconds(ms));
+
+ var entered = await gate.WaitToProceedAsync(0);
+ if (!entered)
+ {
+ throw new APIException("Rate gate should have allowed immediate access to first attempt");
+ }
+
+ for (var i = 0; i < loops; i++)
+ {
+ var timer = Stopwatch.StartNew();
+ await gate.WaitToProceedAsync();
+ timer.Stop();
+
+ if (i <= 0)
+ {
+ continue;
+ }
+
+ // check for too much elapsed time with a little fudge
+ Assert.IsTrue(
+ timer.Elapsed.TotalMilliseconds <= msMax,
+ "Rate gate took too long to wait in between calls: " + timer.Elapsed.TotalMilliseconds + "ms"
+ );
+ Assert.IsTrue(
+ timer.Elapsed.TotalMilliseconds >= msMin,
+ "Rate gate took too little to wait in between calls: " + timer.Elapsed.TotalMilliseconds + "ms"
+ );
+ }
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/ExchangeSharpTests/ExchangeSharpTests.csproj b/tests/ExchangeSharpTests/ExchangeSharpTests.csproj
index d4a90fea..ef79bc19 100644
--- a/tests/ExchangeSharpTests/ExchangeSharpTests.csproj
+++ b/tests/ExchangeSharpTests/ExchangeSharpTests.csproj
@@ -25,7 +25,6 @@
-
diff --git a/tests/ExchangeSharpTests/Utility/ConditionalTestMethod.cs b/tests/ExchangeSharpTests/Utility/ConditionalTestMethod.cs
new file mode 100644
index 00000000..d240cd4d
--- /dev/null
+++ b/tests/ExchangeSharpTests/Utility/ConditionalTestMethod.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.VisualStudio.TestTools.UnitTesting
+{
+ ///
+ /// An extension to the [TestMethod] attribute. It walks the method hierarchy looking
+ /// for an [IgnoreIf] attribute. If one or more are found, they are each evaluated, if the attribute
+ /// returns `true`, evaluation is short-circuited, and the test method is skipped.
+ ///
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ public class ConditionalTestMethodAttribute : TestMethodAttribute
+ {
+ public override TestResult[] Execute(ITestMethod testMethod)
+ {
+ var ignoreAttributes = FindAttributes(testMethod);
+
+ // Evaluate each attribute, and skip if one returns `true`
+ foreach (var ignoreAttribute in ignoreAttributes)
+ {
+ if (!ignoreAttribute.ShouldIgnore(testMethod))
+ continue;
+
+ var message =
+ "Test not executed. " +
+ (string.IsNullOrWhiteSpace(ignoreAttribute.Message)
+ ? $"Conditionally ignored by {ignoreAttribute.GetType().Name}."
+ : ignoreAttribute.Message);
+
+ return new[]
+ {
+ new TestResult
+ {
+ Outcome = UnitTestOutcome.Inconclusive,
+ TestFailureException = new AssertInconclusiveException(message)
+ }
+ };
+ }
+
+ return base.Execute(testMethod);
+ }
+
+ private IEnumerable FindAttributes(ITestMethod testMethod)
+ {
+ // Look for an [IgnoreIf] on the method, including any virtuals this method overrides
+ var ignoreAttributes = new List();
+
+ ignoreAttributes.AddRange(testMethod.GetAttributes(inherit: true));
+
+ return ignoreAttributes;
+ }
+ }
+}
diff --git a/tests/ExchangeSharpTests/Utility/IgnoreIfAttribute.cs b/tests/ExchangeSharpTests/Utility/IgnoreIfAttribute.cs
new file mode 100644
index 00000000..e2abd462
--- /dev/null
+++ b/tests/ExchangeSharpTests/Utility/IgnoreIfAttribute.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.VisualStudio.TestTools.UnitTesting
+{
+ ///
+ /// An extension to the [Ignore] attribute. Instead of using test lists / test categories to conditionally
+ /// skip tests, allow a [TestClass] or [TestMethod] to specify a method to run. If the member returns
+ /// `true` the test method will be skipped. The "ignore criteria" method or property must be `static`, return a single
+ /// `bool` value, and not accept any parameters.
+ ///
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ public class IgnoreIfAttribute : Attribute
+ {
+ public string IgnoreCriteriaMemberName { get; }
+
+ public string Message { get; }
+
+ public IgnoreIfAttribute(string ignoreCriteriaMemberName, string message = null)
+ {
+ IgnoreCriteriaMemberName = ignoreCriteriaMemberName;
+ Message = message;
+ }
+
+ internal virtual bool ShouldIgnore(ITestMethod testMethod)
+ {
+ try
+ {
+ // Search for the method or prop specified by name in this class or any parent classes.
+ var searchFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy |
+ BindingFlags.Static;
+ Debug.Assert(testMethod.MethodInfo.DeclaringType != null,
+ "testMethod.MethodInfo.DeclaringType != null");
+ var member = testMethod.MethodInfo.DeclaringType.GetMember(IgnoreCriteriaMemberName, searchFlags)
+ .FirstOrDefault();
+
+ switch (member)
+ {
+ case MethodInfo method:
+ return (bool) method.Invoke(null, null);
+ case PropertyInfo prop:
+ return (bool) prop.GetValue(null);
+ default:
+ throw new ArgumentOutOfRangeException(nameof(member));
+ }
+ }
+ catch (Exception e)
+ {
+ var message =
+ $"Conditional ignore bool returning method/prop {IgnoreCriteriaMemberName} not found. Ensure the method/prop is in the same class as the test method, marked as `static`, returns a `bool`, and doesn't accept any parameters.";
+ throw new ArgumentException(message, e);
+ }
+ }
+ }
+}
diff --git a/tests/ExchangeSharpTests/Utility/PlatformSpecificTest.cs b/tests/ExchangeSharpTests/Utility/PlatformSpecificTest.cs
new file mode 100644
index 00000000..5e7460ca
--- /dev/null
+++ b/tests/ExchangeSharpTests/Utility/PlatformSpecificTest.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Runtime.InteropServices;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.VisualStudio.TestTools.UnitTesting
+{
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ public class PlatformSpecificTestAttribute : IgnoreIfAttribute
+ {
+ public static OSPlatform NetBSD { get; } = OSPlatform.Create("NETBSD");
+
+ public TestPlatforms FlagPlatform { get; }
+
+ public PlatformSpecificTestAttribute(TestPlatforms flagPlatform, string message = null)
+ : base(null, message)
+ {
+ FlagPlatform = flagPlatform;
+ }
+
+ internal override bool ShouldIgnore(ITestMethod testMethod)
+ {
+ var shouldRun = false;
+
+ if (FlagPlatform.HasFlag(TestPlatforms.Any))
+ return true;
+ if (FlagPlatform.HasFlag(TestPlatforms.Windows))
+ shouldRun = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ if (FlagPlatform.HasFlag(TestPlatforms.Linux))
+ shouldRun = shouldRun || RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+ if (FlagPlatform.HasFlag(TestPlatforms.OSX))
+ shouldRun = shouldRun || RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
+ if (FlagPlatform.HasFlag(TestPlatforms.FreeBSD))
+ shouldRun = shouldRun || RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD);
+ if (FlagPlatform.HasFlag(TestPlatforms.NetBSD))
+ shouldRun = shouldRun || RuntimeInformation.IsOSPlatform(NetBSD);
+
+ return !shouldRun;
+ }
+ }
+}
diff --git a/tests/ExchangeSharpTests/Utility/TestPlatforms.cs b/tests/ExchangeSharpTests/Utility/TestPlatforms.cs
new file mode 100644
index 00000000..e573c8f4
--- /dev/null
+++ b/tests/ExchangeSharpTests/Utility/TestPlatforms.cs
@@ -0,0 +1,17 @@
+using System;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.VisualStudio.TestTools.UnitTesting
+{
+ [Flags]
+ public enum TestPlatforms
+ {
+ Windows = 1,
+ Linux = 2,
+ OSX = 4,
+ FreeBSD = 8,
+ NetBSD = 16,
+ AnyUnix = FreeBSD | Linux | NetBSD | OSX,
+ Any = ~0
+ }
+}