Skip to content

Commit cfd76ba

Browse files
Evangelinkgithub-actions[bot]CopilotCopilot
authored
[Test Improver] Add unit tests for RetryAttribute (#7838)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 66c4738 commit cfd76ba

1 file changed

Lines changed: 175 additions & 0 deletions

File tree

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
#pragma warning disable MSTESTEXP // Experimental API
5+
6+
using AwesomeAssertions;
7+
8+
using TestFramework.ForTestingMSTest;
9+
10+
namespace Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests.Attributes;
11+
12+
/// <summary>
13+
/// Tests for <see cref="RetryAttribute"/> constructor validation and retry execution logic.
14+
/// </summary>
15+
public class RetryAttributeTests : TestContainer
16+
{
17+
public void Constructor_WhenMaxRetryAttemptsIsZero_ThrowsArgumentOutOfRangeException()
18+
{
19+
Action act = static () => _ = new RetryAttribute(0);
20+
act.Should().Throw<ArgumentOutOfRangeException>()
21+
.WithParameterName("maxRetryAttempts");
22+
}
23+
24+
public void Constructor_WhenMaxRetryAttemptsIsNegative_ThrowsArgumentOutOfRangeException()
25+
{
26+
Action act = static () => _ = new RetryAttribute(-1);
27+
act.Should().Throw<ArgumentOutOfRangeException>()
28+
.WithParameterName("maxRetryAttempts");
29+
}
30+
31+
public void Constructor_WhenMaxRetryAttemptsIsOne_SetsMaxRetryAttempts()
32+
{
33+
var attribute = new RetryAttribute(1);
34+
attribute.MaxRetryAttempts.Should().Be(1);
35+
}
36+
37+
public void Constructor_WhenMaxRetryAttemptsIsPositive_SetsMaxRetryAttempts()
38+
{
39+
var attribute = new RetryAttribute(5);
40+
attribute.MaxRetryAttempts.Should().Be(5);
41+
}
42+
43+
public void BackoffType_DefaultsToConstant()
44+
{
45+
var attribute = new RetryAttribute(2);
46+
attribute.BackoffType.Should().Be(DelayBackoffType.Constant);
47+
}
48+
49+
public void BackoffType_WhenSetToInvalidValue_ThrowsArgumentOutOfRangeException()
50+
{
51+
var attribute = new RetryAttribute(2);
52+
Action act = () => attribute.BackoffType = (DelayBackoffType)99;
53+
act.Should().Throw<ArgumentOutOfRangeException>()
54+
.WithParameterName("value");
55+
}
56+
57+
public void BackoffType_WhenSetToExponential_Succeeds()
58+
{
59+
RetryAttribute attribute = new(2) { BackoffType = DelayBackoffType.Exponential };
60+
attribute.BackoffType.Should().Be(DelayBackoffType.Exponential);
61+
}
62+
63+
public void MillisecondsDelayBetweenRetries_DefaultsToZero()
64+
{
65+
var attribute = new RetryAttribute(2);
66+
attribute.MillisecondsDelayBetweenRetries.Should().Be(0);
67+
}
68+
69+
public async Task ExecuteAsync_WhenTestPassesOnFirstRetry_StopsRetryingEarly()
70+
{
71+
var attribute = new RetryAttribute(maxRetryAttempts: 5);
72+
int callCount = 0;
73+
74+
var passResult = new TestResult { Outcome = UnitTestOutcome.Passed };
75+
var failResult = new TestResult { Outcome = UnitTestOutcome.Failed };
76+
77+
TestResult[] firstRunResults = [failResult];
78+
var context = new RetryContext(
79+
() =>
80+
{
81+
callCount++;
82+
return Task.FromResult(new[] { passResult });
83+
},
84+
firstRunResults);
85+
86+
RetryResult result = await attribute.ExecuteAsync(context);
87+
88+
callCount.Should().Be(1);
89+
result.TryGetLast().Should().ContainSingle()
90+
.Which.Outcome.Should().Be(UnitTestOutcome.Passed);
91+
}
92+
93+
public async Task ExecuteAsync_WhenAllRetriesFail_ExecutesExactlyMaxRetryAttemptsTimes()
94+
{
95+
var attribute = new RetryAttribute(maxRetryAttempts: 3);
96+
int callCount = 0;
97+
98+
var failResult = new TestResult { Outcome = UnitTestOutcome.Failed };
99+
TestResult[] firstRunResults = [failResult];
100+
101+
var context = new RetryContext(
102+
() =>
103+
{
104+
callCount++;
105+
return Task.FromResult(new[] { new TestResult { Outcome = UnitTestOutcome.Failed } });
106+
},
107+
firstRunResults);
108+
109+
await attribute.ExecuteAsync(context);
110+
111+
callCount.Should().Be(3);
112+
}
113+
114+
public async Task ExecuteAsync_WhenAllRetriesFail_ResultContainsLastAttemptOutcome()
115+
{
116+
var attribute = new RetryAttribute(maxRetryAttempts: 2);
117+
int callCount = 0;
118+
119+
TestResult[] firstRunResults = [new TestResult { Outcome = UnitTestOutcome.Failed }];
120+
var context = new RetryContext(
121+
() =>
122+
{
123+
callCount++;
124+
UnitTestOutcome outcome = callCount == 2 ? UnitTestOutcome.Timeout : UnitTestOutcome.Failed;
125+
return Task.FromResult(new[] { new TestResult { Outcome = outcome } });
126+
},
127+
firstRunResults);
128+
129+
RetryResult result = await attribute.ExecuteAsync(context);
130+
131+
result.TryGetLast().Should().ContainSingle()
132+
.Which.Outcome.Should().Be(UnitTestOutcome.Timeout);
133+
}
134+
135+
public async Task ExecuteAsync_WhenTestPassesAfterSeveralFailures_StopsAtFirstSuccess()
136+
{
137+
var attribute = new RetryAttribute(maxRetryAttempts: 5);
138+
int callCount = 0;
139+
140+
TestResult[] firstRunResults = [new TestResult { Outcome = UnitTestOutcome.Failed }];
141+
var context = new RetryContext(
142+
() =>
143+
{
144+
callCount++;
145+
UnitTestOutcome outcome = callCount < 3 ? UnitTestOutcome.Failed : UnitTestOutcome.Passed;
146+
return Task.FromResult(new[] { new TestResult { Outcome = outcome } });
147+
},
148+
firstRunResults);
149+
150+
RetryResult result = await attribute.ExecuteAsync(context);
151+
152+
callCount.Should().Be(3);
153+
result.TryGetLast().Should().ContainSingle()
154+
.Which.Outcome.Should().Be(UnitTestOutcome.Passed);
155+
}
156+
157+
public async Task ExecuteAsync_WhenResultIsInconclusive_StopsRetrying()
158+
{
159+
var attribute = new RetryAttribute(maxRetryAttempts: 5);
160+
int callCount = 0;
161+
162+
TestResult[] firstRunResults = [new TestResult { Outcome = UnitTestOutcome.Failed }];
163+
var context = new RetryContext(
164+
() =>
165+
{
166+
callCount++;
167+
return Task.FromResult(new[] { new TestResult { Outcome = UnitTestOutcome.Inconclusive } });
168+
},
169+
firstRunResults);
170+
171+
await attribute.ExecuteAsync(context);
172+
173+
callCount.Should().Be(1);
174+
}
175+
}

0 commit comments

Comments
 (0)