Skip to content

Commit a6bb610

Browse files
brantburnettemilienbev
authored andcommitted
NCBC-4131: Address flaky unit test on Task.WhenAnySuccessful
Motivation ---------- The unit test on Task.WhenAnySuccessful is flaky because it is affected by race conditions. Modifications ------------- Rewrite the test to not use truly async logic and Task.Delay, instead controlling TaskCompletionSource objects directly. Also, don't require that the first successful task be returned, merely one of the successful tasks. Results ------- The unit test is more consistent, especially on resource constrained systems like build agents. Change-Id: I62a6df807f31db4deac4b128afe390eef437b771 Reviewed-on: https://review.couchbase.org/c/couchbase-net-client/+/239337 Tested-by: Build Bot <build@couchbase.com> Reviewed-by: Jeffry Morris <jeffrymorris@gmail.com>
1 parent 5d3d82f commit a6bb610

1 file changed

Lines changed: 46 additions & 23 deletions

File tree

tests/Couchbase.UnitTests/Utils/TaskHelpersTests.cs

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,56 @@ namespace Couchbase.UnitTests.Utils;
1010

1111
public class TaskHelpersTests
1212
{
13-
[Theory(Skip = "Skipping because this is a flaky test and we shouldn't care which task is returned.")]
14-
[InlineData("TaskFive", new[] {true, true, true, true, false})]
15-
[InlineData("TaskFour", new[] {true, true, true, false, true})]
16-
[InlineData("TaskTwo", new[] {true, false, true, false, true})]
17-
public static async Task WhenAnySuccessful_Should_Return_First_Successful_Task(string expectedId, bool[] throws)
13+
public enum TaskResult
1814
{
19-
var taskOne = WaitOrThrow("TaskOne", 1, throws[0]);
20-
var taskTwo = WaitOrThrow("TaskTwo", 2, throws[1]);
21-
var taskThree = WaitOrThrow("TaskThree", 3, throws[2]);
22-
var taskFour = WaitOrThrow("TaskFour", 4, throws[3]);
23-
var taskFive = WaitOrThrow("TaskFive", 5, throws[4]);
15+
Success,
16+
Failure,
17+
NeverCompletes
18+
}
19+
20+
[Theory]
21+
[InlineData(new[] { TaskResult.Failure, TaskResult.Failure, TaskResult.Failure, TaskResult.Failure, TaskResult.Success })]
22+
[InlineData(new[] { TaskResult.Failure, TaskResult.Failure, TaskResult.Failure, TaskResult.Success, TaskResult.Failure })]
23+
[InlineData(new[] { TaskResult.Failure, TaskResult.Success, TaskResult.Failure, TaskResult.Success, TaskResult.Failure })]
24+
[InlineData(new[] { TaskResult.NeverCompletes, TaskResult.NeverCompletes, TaskResult.NeverCompletes, TaskResult.NeverCompletes, TaskResult.Success })]
25+
[InlineData(new[] { TaskResult.NeverCompletes, TaskResult.NeverCompletes, TaskResult.NeverCompletes, TaskResult.Success, TaskResult.NeverCompletes })]
26+
[InlineData(new[] { TaskResult.NeverCompletes, TaskResult.Success, TaskResult.NeverCompletes, TaskResult.Success, TaskResult.NeverCompletes })]
27+
public static async Task WhenAnySuccessful_Should_Return_A_Successful_Task(TaskResult[] taskResults)
28+
{
29+
// true = success, false = failure, null = never completes
30+
31+
TaskCompletionSource<string>[] taskCompletionSources = taskResults
32+
.Select(p => new TaskCompletionSource<string>())
33+
.ToArray();
34+
35+
// Just in case, don't let this test run forever
36+
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
37+
38+
var completeTask = TaskHelpers.WhenAnySuccessful(taskCompletionSources.Select(p => p.Task), cts.Token);
39+
40+
for (var i=0; i< taskResults.Length; i++)
41+
{
42+
var taskResult = taskResults[i];
43+
if (taskResult == TaskResult.Success)
44+
{
45+
taskCompletionSources[i].SetResult($"{i}");
46+
}
47+
else if (taskResult == TaskResult.Failure)
48+
{
49+
taskCompletionSources[i].SetException(new Exception("intentional failure " + i));
50+
}
51+
}
2452

25-
var taskList = new[] { taskOne, taskTwo, taskThree , taskFour, taskFive};
53+
var firstSuccessful = await completeTask
54+
#if NET6_0_OR_GREATER
55+
.WaitAsync(cts.Token); // Extra safeguard against a stuck test
56+
#else
57+
;
58+
#endif
2659

27-
var firstSuccessful = await TaskHelpers.WhenAnySuccessful(taskList, CancellationToken.None);
60+
var index = int.Parse(firstSuccessful);
2861

29-
Assert.Equal(expectedId, firstSuccessful);
62+
Assert.Equal(TaskResult.Success, taskResults[index]);
3063
}
3164

3265
[Fact]
@@ -93,14 +126,4 @@ public void WhenAnySuccessful_AllFailed_ShouldThrowAggregate()
93126
_ = Assert.ThrowsAsync<AggregateException>(() => whenAnySuccessful);
94127
#pragma warning restore xUnit2021
95128
}
96-
97-
private static async Task<string> WaitOrThrow(string id, int seconds, bool throws)
98-
{
99-
await Task.Delay(TimeSpan.FromSeconds(seconds));
100-
if (throws)
101-
{
102-
throw new Exception($"Task {id} threw.");
103-
}
104-
return id;
105-
}
106129
}

0 commit comments

Comments
 (0)