Skip to content

Commit af4b90c

Browse files
[Testing] Updated FlakyTestAttribute to allow retries (#27772)
* Updated FlakyTestAttribute to allow retries * Updated attribute comment based on feedback * Loggin attempt with TestExecutionContext.CurrentResult * Avoid static var * [housekeeping] Automated PR to fix formatting errors on update-flakytestattribute * Fix build errors --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 2083350 commit af4b90c

File tree

1 file changed

+84
-7
lines changed

1 file changed

+84
-7
lines changed
Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,90 @@
11
using NUnit.Framework;
2+
using NUnit.Framework.Interfaces;
3+
using NUnit.Framework.Internal;
4+
using NUnit.Framework.Internal.Commands;
25

3-
namespace Microsoft.Maui.TestCases.Tests;
4-
5-
public class FlakyTestAttribute : IgnoreAttribute
6+
namespace Microsoft.Maui.TestCases.Tests
67
{
7-
public FlakyTestAttribute() : base(nameof(FlakyTestAttribute))
8-
{
9-
}
10-
public FlakyTestAttribute(string reason) : base(reason)
8+
/// <summary>
9+
/// Custom NUnit attribute to mark a test as flaky, allowing retries (by default 2).
10+
/// If after the retries the test fails, can ignore it.
11+
/// Note: This attribute should be used temporarily until the test is changed.
12+
/// </summary>
13+
/// <example>
14+
/// <code>
15+
/// [FlakyTest("Description with details of the test that sometimes fails.", retryCount: 2, ignore: true)]
16+
/// </code>
17+
/// </example>
18+
internal class FlakyTestAttribute : Attribute, IWrapTestMethod, IWrapSetUpTearDown
1119
{
20+
readonly string _ignoreMessage;
21+
readonly int _retryCount;
22+
readonly bool _ignore;
23+
24+
public FlakyTestAttribute(string message, int retryCount = 2, bool ignore = true)
25+
{
26+
_ignoreMessage = message;
27+
_retryCount = retryCount;
28+
_ignore = ignore;
29+
}
30+
31+
public ActionTargets Targets => ActionTargets.Suite | ActionTargets.Test;
32+
33+
public TestCommand Wrap(TestCommand command)
34+
{
35+
return new CustomRetryCommand(command, _ignoreMessage, _retryCount, _ignore);
36+
}
37+
38+
public class CustomRetryCommand : DelegatingTestCommand
39+
{
40+
readonly string _ignoreMessage;
41+
readonly int _retryCount;
42+
readonly bool _ignore;
43+
44+
int _failedAttempts = 0;
45+
46+
public CustomRetryCommand(TestCommand innerCommand, string ignoreMessage, int retryCount, bool ignore)
47+
: base(innerCommand)
48+
{
49+
_ignoreMessage = ignoreMessage;
50+
_retryCount = retryCount;
51+
_ignore = ignore;
52+
}
53+
54+
public override TestResult Execute(TestExecutionContext context)
55+
{
56+
int count = _retryCount;
57+
while (count-- > 0)
58+
{
59+
context.CurrentResult = innerCommand.Execute(context);
60+
var results = context.CurrentResult.ResultState;
61+
62+
if (results.Equals(ResultState.Error)
63+
|| results.Equals(ResultState.Failure)
64+
|| results.Equals(ResultState.SetUpError)
65+
|| results.Equals(ResultState.SetUpFailure)
66+
|| results.Equals(ResultState.TearDownError)
67+
|| results.Equals(ResultState.ChildFailure)
68+
|| results.Equals(ResultState.Cancelled))
69+
{
70+
_failedAttempts++;
71+
TestExecutionContext.CurrentContext.OutWriter.WriteLine("Test Failed on attempt #" + _failedAttempts);
72+
}
73+
else
74+
{
75+
TestExecutionContext.CurrentContext.OutWriter.WriteLine("Test Passed on attempt #" + (_failedAttempts + 1));
76+
break;
77+
}
78+
}
79+
80+
// If want to ignore and all retry attempts fail, ignore the test with the provided message.
81+
if (_ignore && _failedAttempts == _retryCount)
82+
{
83+
context.CurrentResult.SetResult(ResultState.Ignored, _ignoreMessage);
84+
}
85+
86+
return context.CurrentResult;
87+
}
88+
}
1289
}
1390
}

0 commit comments

Comments
 (0)