11using 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