Skip to content

Commit f2b9b03

Browse files
chore: remove test sleeps, fix flaky test (#194)
Signed-off-by: Todd Baert <[email protected]> Co-authored-by: Austin Drenski <[email protected]>
1 parent a790f78 commit f2b9b03

File tree

3 files changed

+122
-38
lines changed

3 files changed

+122
-38
lines changed

test/OpenFeature.Tests/OpenFeatureEventTests.cs

+45-38
Original file line numberDiff line numberDiff line change
@@ -76,34 +76,33 @@ public async Task API_Level_Event_Handlers_Should_Be_Registered()
7676
testProvider.SendEvent(ProviderEventTypes.ProviderError);
7777
testProvider.SendEvent(ProviderEventTypes.ProviderStale);
7878

79-
Thread.Sleep(1000);
80-
eventHandler
79+
await Utils.AssertUntilAsync(_ => eventHandler
8180
.Received()
8281
.Invoke(
8382
Arg.Is<ProviderEventPayload>(
8483
payload => payload.ProviderName == testProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderReady
85-
));
84+
)));
8685

87-
eventHandler
86+
await Utils.AssertUntilAsync(_ => eventHandler
8887
.Received()
8988
.Invoke(
9089
Arg.Is<ProviderEventPayload>(
9190
payload => payload.ProviderName == testProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderConfigurationChanged
92-
));
91+
)));
9392

94-
eventHandler
93+
await Utils.AssertUntilAsync(_ => eventHandler
9594
.Received()
9695
.Invoke(
9796
Arg.Is<ProviderEventPayload>(
9897
payload => payload.ProviderName == testProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderError
99-
));
98+
)));
10099

101-
eventHandler
100+
await Utils.AssertUntilAsync(_ => eventHandler
102101
.Received()
103102
.Invoke(
104103
Arg.Is<ProviderEventPayload>(
105104
payload => payload.ProviderName == testProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderStale
106-
));
105+
)));
107106
}
108107

109108
[Fact]
@@ -122,13 +121,12 @@ public async Task API_Level_Event_Handlers_Should_Be_Informed_About_Ready_State_
122121

123122
Api.Instance.AddHandler(ProviderEventTypes.ProviderReady, eventHandler);
124123

125-
Thread.Sleep(1000);
126-
eventHandler
124+
await Utils.AssertUntilAsync(_ => eventHandler
127125
.Received()
128126
.Invoke(
129127
Arg.Is<ProviderEventPayload>(
130128
payload => payload.ProviderName == testProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderReady
131-
));
129+
)));
132130
}
133131

134132
[Fact]
@@ -149,13 +147,12 @@ public async Task API_Level_Event_Handlers_Should_Be_Informed_About_Error_State_
149147

150148
Api.Instance.AddHandler(ProviderEventTypes.ProviderError, eventHandler);
151149

152-
Thread.Sleep(1000);
153-
eventHandler
150+
await Utils.AssertUntilAsync(_ => eventHandler
154151
.Received()
155152
.Invoke(
156153
Arg.Is<ProviderEventPayload>(
157154
payload => payload.ProviderName == testProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderError
158-
));
155+
)));
159156
}
160157

161158
[Fact]
@@ -175,13 +172,12 @@ public async Task API_Level_Event_Handlers_Should_Be_Informed_About_Stale_State_
175172

176173
Api.Instance.AddHandler(ProviderEventTypes.ProviderStale, eventHandler);
177174

178-
Thread.Sleep(1000);
179-
eventHandler
175+
await Utils.AssertUntilAsync(_ => eventHandler
180176
.Received()
181177
.Invoke(
182178
Arg.Is<ProviderEventPayload>(
183179
payload => payload.ProviderName == testProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderStale
184-
));
180+
)));
185181
}
186182

187183
[Fact]
@@ -207,9 +203,12 @@ public async Task API_Level_Event_Handlers_Should_Be_Exchangeable()
207203

208204
newTestProvider.SendEvent(ProviderEventTypes.ProviderConfigurationChanged);
209205

210-
Thread.Sleep(1000);
211-
eventHandler.Received(2).Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == testProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderReady));
212-
eventHandler.Received(2).Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == testProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderConfigurationChanged));
206+
await Utils.AssertUntilAsync(
207+
_ => eventHandler.Received(2).Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == testProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderReady))
208+
);
209+
await Utils.AssertUntilAsync(
210+
_ => eventHandler.Received(2).Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == testProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderConfigurationChanged))
211+
);
213212
}
214213

215214
[Fact]
@@ -257,8 +256,12 @@ public async Task API_Level_Event_Handlers_Should_Be_Executed_When_Other_Handler
257256
var testProvider = new TestProvider(fixture.Create<string>());
258257
await Api.Instance.SetProvider(testProvider);
259258

260-
failingEventHandler.Received().Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == testProvider.GetMetadata().Name));
261-
eventHandler.Received().Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == testProvider.GetMetadata().Name));
259+
await Utils.AssertUntilAsync(
260+
_ => failingEventHandler.Received().Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == testProvider.GetMetadata().Name))
261+
);
262+
await Utils.AssertUntilAsync(
263+
_ => eventHandler.Received().Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == testProvider.GetMetadata().Name))
264+
);
262265
}
263266

264267
[Fact]
@@ -305,10 +308,12 @@ public async Task Client_Level_Event_Handlers_Should_Be_Executed_When_Other_Hand
305308
var testProvider = new TestProvider();
306309
await Api.Instance.SetProvider(myClient.GetMetadata().Name, testProvider);
307310

308-
Thread.Sleep(1000);
309-
310-
failingEventHandler.Received().Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == testProvider.GetMetadata().Name));
311-
eventHandler.Received().Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == testProvider.GetMetadata().Name));
311+
await Utils.AssertUntilAsync(
312+
_ => failingEventHandler.Received().Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == testProvider.GetMetadata().Name))
313+
);
314+
await Utils.AssertUntilAsync(
315+
_ => eventHandler.Received().Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == testProvider.GetMetadata().Name))
316+
);
312317
}
313318

314319
[Fact]
@@ -368,10 +373,10 @@ public async Task Client_Level_Event_Handlers_Should_Be_Receive_Events_From_Name
368373

369374
defaultProvider.SendEvent(ProviderEventTypes.ProviderConfigurationChanged);
370375

371-
Thread.Sleep(1000);
372-
373376
// verify that the client received the event from the default provider as there is no named provider registered yet
374-
clientEventHandler.Received(1).Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == defaultProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderConfigurationChanged));
377+
await Utils.AssertUntilAsync(
378+
_ => clientEventHandler.Received(1).Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == defaultProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderConfigurationChanged))
379+
);
375380

376381
// set the other provider specifically for the client
377382
await Api.Instance.SetProvider(client.GetMetadata().Name, clientProvider);
@@ -380,12 +385,14 @@ public async Task Client_Level_Event_Handlers_Should_Be_Receive_Events_From_Name
380385
defaultProvider.SendEvent(ProviderEventTypes.ProviderConfigurationChanged);
381386
clientProvider.SendEvent(ProviderEventTypes.ProviderConfigurationChanged);
382387

383-
Thread.Sleep(1000);
384-
385388
// now the client should have received only the event from the named provider
386-
clientEventHandler.Received(1).Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == clientProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderConfigurationChanged));
389+
await Utils.AssertUntilAsync(
390+
_ => clientEventHandler.Received(1).Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == clientProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderConfigurationChanged))
391+
);
387392
// for the default provider, the number of received events should stay unchanged
388-
clientEventHandler.Received(1).Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == defaultProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderConfigurationChanged));
393+
await Utils.AssertUntilAsync(
394+
_ => clientEventHandler.Received(1).Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == defaultProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderConfigurationChanged))
395+
);
389396
}
390397

391398
[Fact]
@@ -431,15 +438,15 @@ public async Task Client_Level_Event_Handlers_Should_Be_Removable()
431438
await Api.Instance.SetProvider(myClient.GetMetadata().Name, testProvider);
432439

433440
// wait for the first event to be received
434-
Thread.Sleep(1000);
435-
myClient.RemoveHandler(ProviderEventTypes.ProviderReady, eventHandler);
441+
await Utils.AssertUntilAsync(_ => myClient.RemoveHandler(ProviderEventTypes.ProviderReady, eventHandler));
436442

437443
// send another event from the provider - this one should not be received
438444
testProvider.SendEvent(ProviderEventTypes.ProviderReady);
439445

440446
// wait a bit and make sure we only have received the first event, but nothing after removing the event handler
441-
Thread.Sleep(1000);
442-
eventHandler.Received(1).Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == testProvider.GetMetadata().Name));
447+
await Utils.AssertUntilAsync(
448+
_ => eventHandler.Received(1).Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == testProvider.GetMetadata().Name))
449+
);
443450
}
444451
}
445452
}

test/OpenFeature.Tests/TestUtils.cs

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
7+
internal class Utils
8+
{
9+
/// <summary>
10+
/// Repeatedly runs the supplied assertion until it doesn't throw, or the timeout is reached.
11+
/// </summary>
12+
/// <param name="assertionFunc">Function which makes an assertion</param>
13+
/// <param name="timeoutMillis">Timeout in millis (defaults to 1000)</param>
14+
/// <param name="pollIntervalMillis">Poll interval (defaults to 100</param>
15+
/// <returns></returns>
16+
public static async Task AssertUntilAsync(Action<CancellationToken> assertionFunc, int timeoutMillis = 1000, int pollIntervalMillis = 100)
17+
{
18+
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(default(CancellationToken)))
19+
{
20+
21+
cts.CancelAfter(timeoutMillis);
22+
23+
var exceptions = new List<Exception>();
24+
var message = "AssertUntilAsync timeout reached.";
25+
26+
while (!cts.IsCancellationRequested)
27+
{
28+
try
29+
{
30+
assertionFunc(cts.Token);
31+
return;
32+
}
33+
catch (TaskCanceledException) when (cts.IsCancellationRequested)
34+
{
35+
throw new AggregateException(message, exceptions);
36+
}
37+
catch (Exception e)
38+
{
39+
exceptions.Add(e);
40+
}
41+
42+
try
43+
{
44+
await Task.Delay(pollIntervalMillis, cts.Token).ConfigureAwait(false);
45+
}
46+
catch (TaskCanceledException)
47+
{
48+
throw new AggregateException(message, exceptions);
49+
}
50+
}
51+
throw new AggregateException(message, exceptions);
52+
}
53+
}
54+
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
4+
using OpenFeature.Model;
5+
using Xunit;
6+
7+
namespace OpenFeature.Tests
8+
{
9+
public class TestUtilsTest
10+
{
11+
[Fact]
12+
public async void Should_Fail_If_Assertion_Fails()
13+
{
14+
await Assert.ThrowsAnyAsync<Exception>(() => Utils.AssertUntilAsync(_ => Assert.True(1.Equals(2)), 100, 10)).ConfigureAwait(false);
15+
}
16+
17+
[Fact]
18+
public async void Should_Pass_If_Assertion_Fails()
19+
{
20+
await Utils.AssertUntilAsync(_ => Assert.True(1.Equals(1))).ConfigureAwait(false);
21+
}
22+
}
23+
}

0 commit comments

Comments
 (0)