Skip to content

Instantly share code, notes, and snippets.

@di97mni
Last active August 27, 2015 19:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save di97mni/f105ca512bbaafa249ba to your computer and use it in GitHub Desktop.
Save di97mni/f105ca512bbaafa249ba to your computer and use it in GitHub Desktop.
[NUnitAddin(Type = ExtensionType.Core, Description = "Retries an intermittently failing test.")]
public class RetryTestAddin : IAddin, ITestDecorator
{
public bool Install(IExtensionHost host)
{
var testDecorators = host.GetExtensionPoint("TestDecorators");
if (testDecorators == null) return false;
testDecorators.Install(this);
return true;
}
public Test Decorate(Test test, MemberInfo member)
{
var retryAttr = GetRetryAttribute(member, test);
if (retryAttr == null) return test;
if (test != null &&
test.GetType() == typeof(NUnitTestMethod) &&
((NUnitTestMethod)test).Method.
GetCustomAttributes(typeof(IgnoreAttribute), true).
Length == 0)
{
return new RetriedTestMethod(
(NUnitTestMethod)test,
retryAttr.Times,
retryAttr.RequiredPassCount);
}
if (!(test is ParameterizedMethodSuite)) return test;
var testMethodSuite = (ParameterizedMethodSuite)test;
System.Collections.IList newTests = new List<Test>();
foreach (Test childTest in testMethodSuite.Tests)
{
if (childTest != null &&
childTest.GetType() == typeof(NUnitTestMethod) &&
((NUnitTestMethod)childTest).Method.
GetCustomAttributes(typeof(IgnoreAttribute), true).
Length == 0)
{
var oldTest = (NUnitTestMethod)childTest;
var newTest = new RetriedTestMethod(
oldTest,
retryAttr.Times,
retryAttr.RequiredPassCount);
newTests.Add(newTest);
}
else
{
newTests.Add(childTest);
}
}
testMethodSuite.Tests.Clear();
foreach (Test newTest in newTests)
{
testMethodSuite.Add(newTest);
}
return testMethodSuite;
}
private static RetryAttribute GetRetryAttribute(MemberInfo member, Test testMethodSuite)
{
var retryAttr = GetRetryAttribute(member);
if (retryAttr == null && testMethodSuite.FixtureType != null)
{
retryAttr = GetRetryAttribute(testMethodSuite.FixtureType);
}
return retryAttr;
}
private static RetryAttribute GetRetryAttribute(MemberInfo member)
{
var attrs = member.GetCustomAttributes(typeof(RetryAttribute), true)
.Cast<RetryAttribute>();
return attrs.FirstOrDefault();
}
}
public class RetriedTestMethod : NUnitTestMethod
{
readonly int _requiredPassCount;
readonly int _tryCount;
public RetriedTestMethod(NUnitTestMethod test, int tryCount, int requiredPassCount)
: base(test.Method)
{
_tryCount = tryCount;
_requiredPassCount = requiredPassCount;
}
public override TestResult Run(EventListener listener, ITestFilter filter)
{
var successCount = 0;
TestResult failureResult = null;
for (var i = 0; i < _tryCount; i++)
{
if (i > 0)
{
Console.WriteLine(string.Format("ailed Retrying Test Case now...xRETRYx.. [{0}] of [{1}]", i + 1, _tryCount));
}
if (i > 1)
{
Console.WriteLine(string.Format("Pausing for retry for {0} seconds", i));
Thread.Sleep(i*2000); // increase the pause on each run (reason to allow CPU/db, etc to die off and start the tests fresh.)
}
var result = base.Run(listener, filter);
if (!TestFailed(result))
{
if (i == 0)
{
return result;
}
if (++successCount >= _requiredPassCount)
{
return result;
}
}
else
{
failureResult = result;
}
}
return failureResult;
}
private static bool TestFailed(TestResult result)
{
return result.ResultState == ResultState.Error || result.ResultState == ResultState.Failure;
}
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class RetryAttribute : Attribute
{
public RetryAttribute(int times = 5, int requiredPassCount = 1)
{
if (requiredPassCount > times)
{
throw new Exception("Required Pass Count must be lower or equal than the number of retries.");
}
Times = times;
RequiredPassCount = requiredPassCount;
}
public int RequiredPassCount { get; set; }
public int Times { get; set; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment