Skip to content

Commit 16c0cc3

Browse files
Johnny A. dos Santosvslee
authored andcommitted
Add attributes to ignore method on certain conditions (#493)
1 parent 658c5e3 commit 16c0cc3

File tree

6 files changed

+209
-26
lines changed

6 files changed

+209
-26
lines changed

tests/ExchangeSharpTests/CryptoUtilityTests.cs

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -226,32 +226,47 @@ public void ConvertInvariantTest()
226226
}
227227
}
228228

229-
[TestMethod]
229+
[ConditionalTestMethod]
230+
[PlatformSpecificTest(
231+
~TestPlatforms.OSX,
232+
"Has an issue on MacOS. See https://github.com/dotnet/corefx/issues/42607"
233+
)]
230234
public async Task RateGate()
231235
{
232-
const int timesPerPeriod = 1;
233-
const int ms = 100;
234-
const int loops = 5;
235-
double msMax = (double)ms * 1.5;
236-
double msMin = (double)ms * (1.0 / 1.5);
237-
RateGate gate = new RateGate(timesPerPeriod, TimeSpan.FromMilliseconds(ms));
238-
if (!(await gate.WaitToProceedAsync(0)))
239-
{
240-
throw new APIException("Rate gate should have allowed immediate access to first attempt");
241-
}
242-
for (int i = 0; i < loops; i++)
243-
{
244-
Stopwatch timer = Stopwatch.StartNew();
245-
await gate.WaitToProceedAsync();
246-
timer.Stop();
247-
248-
if (i > 0)
249-
{
250-
// check for too much elapsed time with a little fudge
251-
Assert.IsTrue(timer.Elapsed.TotalMilliseconds <= msMax, "Rate gate took too long to wait in between calls: " + timer.Elapsed.TotalMilliseconds + "ms");
252-
Assert.IsTrue(timer.Elapsed.TotalMilliseconds >= msMin, "Rate gate took too little to wait in between calls: " + timer.Elapsed.TotalMilliseconds + "ms");
253-
}
254-
}
236+
const int timesPerPeriod = 1;
237+
const int ms = 100;
238+
const int loops = 5;
239+
const double msMax = (double) ms * 1.5;
240+
const double msMin = (double) ms * (1.0 / 1.5);
241+
var gate = new RateGate(timesPerPeriod, TimeSpan.FromMilliseconds(ms));
242+
243+
var entered = await gate.WaitToProceedAsync(0);
244+
if (!entered)
245+
{
246+
throw new APIException("Rate gate should have allowed immediate access to first attempt");
247+
}
248+
249+
for (var i = 0; i < loops; i++)
250+
{
251+
var timer = Stopwatch.StartNew();
252+
await gate.WaitToProceedAsync();
253+
timer.Stop();
254+
255+
if (i <= 0)
256+
{
257+
continue;
258+
}
259+
260+
// check for too much elapsed time with a little fudge
261+
Assert.IsTrue(
262+
timer.Elapsed.TotalMilliseconds <= msMax,
263+
"Rate gate took too long to wait in between calls: " + timer.Elapsed.TotalMilliseconds + "ms"
264+
);
265+
Assert.IsTrue(
266+
timer.Elapsed.TotalMilliseconds >= msMin,
267+
"Rate gate took too little to wait in between calls: " + timer.Elapsed.TotalMilliseconds + "ms"
268+
);
269+
}
255270
}
256271
}
257-
}
272+
}

tests/ExchangeSharpTests/ExchangeSharpTests.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
<PackageReference Include="MSTest.TestFramework" Version="1.2.1" />
2626
<PackageReference Include="NSubstitute" Version="3.1.0" />
2727
<PackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.10" />
28-
<PackageReference Include="xunit.analyzers" Version="0.10.0" />
2928
</ItemGroup>
3029

3130
<ItemGroup>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
// ReSharper disable once CheckNamespace
5+
namespace Microsoft.VisualStudio.TestTools.UnitTesting
6+
{
7+
/// <summary>
8+
/// An extension to the [TestMethod] attribute. It walks the method hierarchy looking
9+
/// for an [IgnoreIf] attribute. If one or more are found, they are each evaluated, if the attribute
10+
/// returns `true`, evaluation is short-circuited, and the test method is skipped.
11+
/// </summary>
12+
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
13+
public class ConditionalTestMethodAttribute : TestMethodAttribute
14+
{
15+
public override TestResult[] Execute(ITestMethod testMethod)
16+
{
17+
var ignoreAttributes = FindAttributes(testMethod);
18+
19+
// Evaluate each attribute, and skip if one returns `true`
20+
foreach (var ignoreAttribute in ignoreAttributes)
21+
{
22+
if (!ignoreAttribute.ShouldIgnore(testMethod))
23+
continue;
24+
25+
var message =
26+
"Test not executed. " +
27+
(string.IsNullOrWhiteSpace(ignoreAttribute.Message)
28+
? $"Conditionally ignored by {ignoreAttribute.GetType().Name}."
29+
: ignoreAttribute.Message);
30+
31+
return new[]
32+
{
33+
new TestResult
34+
{
35+
Outcome = UnitTestOutcome.Inconclusive,
36+
TestFailureException = new AssertInconclusiveException(message)
37+
}
38+
};
39+
}
40+
41+
return base.Execute(testMethod);
42+
}
43+
44+
private IEnumerable<IgnoreIfAttribute> FindAttributes(ITestMethod testMethod)
45+
{
46+
// Look for an [IgnoreIf] on the method, including any virtuals this method overrides
47+
var ignoreAttributes = new List<IgnoreIfAttribute>();
48+
49+
ignoreAttributes.AddRange(testMethod.GetAttributes<IgnoreIfAttribute>(inherit: true));
50+
51+
return ignoreAttributes;
52+
}
53+
}
54+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Linq;
4+
using System.Reflection;
5+
6+
// ReSharper disable once CheckNamespace
7+
namespace Microsoft.VisualStudio.TestTools.UnitTesting
8+
{
9+
/// <summary>
10+
/// An extension to the [Ignore] attribute. Instead of using test lists / test categories to conditionally
11+
/// skip tests, allow a [TestClass] or [TestMethod] to specify a method to run. If the member returns
12+
/// `true` the test method will be skipped. The "ignore criteria" method or property must be `static`, return a single
13+
/// `bool` value, and not accept any parameters.
14+
/// </summary>
15+
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
16+
public class IgnoreIfAttribute : Attribute
17+
{
18+
public string IgnoreCriteriaMemberName { get; }
19+
20+
public string Message { get; }
21+
22+
public IgnoreIfAttribute(string ignoreCriteriaMemberName, string message = null)
23+
{
24+
IgnoreCriteriaMemberName = ignoreCriteriaMemberName;
25+
Message = message;
26+
}
27+
28+
internal virtual bool ShouldIgnore(ITestMethod testMethod)
29+
{
30+
try
31+
{
32+
// Search for the method or prop specified by name in this class or any parent classes.
33+
var searchFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy |
34+
BindingFlags.Static;
35+
Debug.Assert(testMethod.MethodInfo.DeclaringType != null,
36+
"testMethod.MethodInfo.DeclaringType != null");
37+
var member = testMethod.MethodInfo.DeclaringType.GetMember(IgnoreCriteriaMemberName, searchFlags)
38+
.FirstOrDefault();
39+
40+
switch (member)
41+
{
42+
case MethodInfo method:
43+
return (bool) method.Invoke(null, null);
44+
case PropertyInfo prop:
45+
return (bool) prop.GetValue(null);
46+
default:
47+
throw new ArgumentOutOfRangeException(nameof(member));
48+
}
49+
}
50+
catch (Exception e)
51+
{
52+
var message =
53+
$"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.";
54+
throw new ArgumentException(message, e);
55+
}
56+
}
57+
}
58+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
// ReSharper disable once CheckNamespace
5+
namespace Microsoft.VisualStudio.TestTools.UnitTesting
6+
{
7+
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
8+
public class PlatformSpecificTestAttribute : IgnoreIfAttribute
9+
{
10+
public static OSPlatform NetBSD { get; } = OSPlatform.Create("NETBSD");
11+
12+
public TestPlatforms FlagPlatform { get; }
13+
14+
public PlatformSpecificTestAttribute(TestPlatforms flagPlatform, string message = null)
15+
: base(null, message)
16+
{
17+
FlagPlatform = flagPlatform;
18+
}
19+
20+
internal override bool ShouldIgnore(ITestMethod testMethod)
21+
{
22+
var shouldRun = false;
23+
24+
if (FlagPlatform.HasFlag(TestPlatforms.Any))
25+
return true;
26+
if (FlagPlatform.HasFlag(TestPlatforms.Windows))
27+
shouldRun = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
28+
if (FlagPlatform.HasFlag(TestPlatforms.Linux))
29+
shouldRun = shouldRun || RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
30+
if (FlagPlatform.HasFlag(TestPlatforms.OSX))
31+
shouldRun = shouldRun || RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
32+
if (FlagPlatform.HasFlag(TestPlatforms.FreeBSD))
33+
shouldRun = shouldRun || RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD);
34+
if (FlagPlatform.HasFlag(TestPlatforms.NetBSD))
35+
shouldRun = shouldRun || RuntimeInformation.IsOSPlatform(NetBSD);
36+
37+
return !shouldRun;
38+
}
39+
}
40+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
3+
// ReSharper disable once CheckNamespace
4+
namespace Microsoft.VisualStudio.TestTools.UnitTesting
5+
{
6+
[Flags]
7+
public enum TestPlatforms
8+
{
9+
Windows = 1,
10+
Linux = 2,
11+
OSX = 4,
12+
FreeBSD = 8,
13+
NetBSD = 16,
14+
AnyUnix = FreeBSD | Linux | NetBSD | OSX,
15+
Any = ~0
16+
}
17+
}

0 commit comments

Comments
 (0)