Skip to content

Commit 315f64e

Browse files
authored
Retry time tests (#582)
1 parent 36a7d57 commit 315f64e

File tree

9 files changed

+251
-98
lines changed

9 files changed

+251
-98
lines changed

BitFaster.Caching.UnitTests/Lfu/ConcurrentTLfuTests.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Runtime.InteropServices;
33
using BitFaster.Caching.Lfu;
44
using BitFaster.Caching.Scheduler;
5+
using BitFaster.Caching.UnitTests.Retry;
56
using FluentAssertions;
67
using Xunit;
78

@@ -119,7 +120,7 @@ public void WhenItemIsNotExpiredItIsNotRemoved()
119120
lfu.TryGet(1, out var value).Should().BeTrue();
120121
}
121122

122-
[Fact]
123+
[RetryFact]
123124
public void WhenItemIsExpiredItIsRemoved()
124125
{
125126
Timed.Execute(
@@ -137,7 +138,7 @@ public void WhenItemIsExpiredItIsRemoved()
137138
);
138139
}
139140

140-
[Fact]
141+
[RetryFact]
141142
public void WhenItemIsExpiredItIsRemoved2()
142143
{
143144
Timed.Execute(
@@ -158,7 +159,7 @@ public void WhenItemIsExpiredItIsRemoved2()
158159
);
159160
}
160161

161-
[Fact]
162+
[RetryFact]
162163
public void WhenItemIsUpdatedTtlIsExtended()
163164
{
164165
Timed.Execute(

BitFaster.Caching.UnitTests/Lru/ConcurrentLruAfterAccessTests.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Runtime.InteropServices;
44
using BitFaster.Caching.Lru;
5+
using BitFaster.Caching.UnitTests.Retry;
56
using FluentAssertions;
67
using Xunit;
78

@@ -53,7 +54,7 @@ public void WhenItemIsNotExpiredItIsNotRemoved()
5354
lru.TryGet(1, out var value).Should().BeTrue();
5455
}
5556

56-
[Fact]
57+
[RetryFact]
5758
public void WhenItemIsExpiredItIsRemoved()
5859
{
5960
Timed.Execute(
@@ -71,7 +72,7 @@ public void WhenItemIsExpiredItIsRemoved()
7172
);
7273
}
7374

74-
[Fact]
75+
[RetryFact]
7576
public void WhenItemIsUpdatedTtlIsExtended()
7677
{
7778
Timed.Execute(
@@ -93,7 +94,7 @@ public void WhenItemIsUpdatedTtlIsExtended()
9394
// Using async/await makes this very unstable due to xunit
9495
// running new tests on the yielding thread. Using sleep
9596
// forces the test to stay on the same thread.
96-
[Fact]
97+
[RetryFact]
9798
public void WhenItemIsReadTtlIsExtended()
9899
{
99100
var lru = new ConcurrentLruBuilder<int, string>()
@@ -160,7 +161,7 @@ public void WhenValueEvictedItemRemovedEventIsFired()
160161
removedItems[1].Reason.Should().Be(ItemRemovedReason.Evicted);
161162
}
162163

163-
[Fact]
164+
[RetryFact]
164165
public void WhenItemsAreExpiredExpireRemovesExpiredItems()
165166
{
166167
Timed.Execute(
@@ -194,7 +195,7 @@ public void WhenItemsAreExpiredExpireRemovesExpiredItems()
194195
);
195196
}
196197

197-
[Fact]
198+
[RetryFact]
198199
public void WhenCacheHasExpiredAndFreshItemsExpireRemovesOnlyExpiredItems()
199200
{
200201
Timed.Execute(
@@ -225,7 +226,7 @@ public void WhenCacheHasExpiredAndFreshItemsExpireRemovesOnlyExpiredItems()
225226
);
226227
}
227228

228-
[Fact]
229+
[RetryFact]
229230
public void WhenItemsAreExpiredTrimRemovesExpiredItems()
230231
{
231232
Timed.Execute(

BitFaster.Caching.UnitTests/Lru/ConcurrentLruAfterDiscreteTests.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Runtime.InteropServices;
44
using BitFaster.Caching.Lru;
5+
using BitFaster.Caching.UnitTests.Retry;
56
using FluentAssertions;
67
using Xunit;
78

@@ -55,7 +56,7 @@ public void WhenKeyExistsTryGetTimeToExpireReturnsExpiryTime()
5556
expiry.Should().BeCloseTo(TestExpiryCalculator<int, string>.DefaultTimeToExpire.ToTimeSpan(), delta);
5657
}
5758

58-
[Fact]
59+
[RetryFact]
5960
public void WhenItemIsExpiredItIsRemoved()
6061
{
6162
Timed.Execute(
@@ -73,7 +74,7 @@ public void WhenItemIsExpiredItIsRemoved()
7374
);
7475
}
7576

76-
[Fact]
77+
[RetryFact]
7778
public void WhenItemIsUpdatedTtlIsExtended()
7879
{
7980
Timed.Execute(
@@ -92,7 +93,7 @@ public void WhenItemIsUpdatedTtlIsExtended()
9293
);
9394
}
9495

95-
[Fact]
96+
[RetryFact]
9697
public void WhenItemIsReadTtlIsExtended()
9798
{
9899
expiryCalculator.ExpireAfterCreate = (_, _) => Duration.FromMilliseconds(100);
@@ -160,7 +161,7 @@ public void WhenValueEvictedItemRemovedEventIsFired()
160161
removedItems[1].Reason.Should().Be(ItemRemovedReason.Evicted);
161162
}
162163

163-
[Fact]
164+
[RetryFact]
164165
public void WhenItemsAreExpiredExpireRemovesExpiredItems()
165166
{
166167
Timed.Execute(
@@ -194,7 +195,7 @@ public void WhenItemsAreExpiredExpireRemovesExpiredItems()
194195
);
195196
}
196197

197-
[Fact]
198+
[RetryFact]
198199
public void WhenCacheHasExpiredAndFreshItemsExpireRemovesOnlyExpiredItems()
199200
{
200201
Timed.Execute(
@@ -225,7 +226,7 @@ public void WhenCacheHasExpiredAndFreshItemsExpireRemovesOnlyExpiredItems()
225226
);
226227
}
227228

228-
[Fact]
229+
[RetryFact]
229230
public void WhenItemsAreExpiredTrimRemovesExpiredItems()
230231
{
231232
Timed.Execute(

BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using Xunit;
66
using System.Runtime.InteropServices;
7+
using BitFaster.Caching.UnitTests.Retry;
78

89
namespace BitFaster.Caching.UnitTests.Lru
910
{
@@ -55,7 +56,7 @@ public void WhenItemIsNotExpiredItIsNotRemoved()
5556
lru.TryGet(1, out var value).Should().BeTrue();
5657
}
5758

58-
[Fact]
59+
[RetryFact]
5960
public void WhenItemIsExpiredItIsRemoved()
6061
{
6162
Timed.Execute(
@@ -73,7 +74,7 @@ public void WhenItemIsExpiredItIsRemoved()
7374
);
7475
}
7576

76-
[Fact]
77+
[RetryFact]
7778
public void WhenItemIsUpdatedTtlIsExtended()
7879
{
7980
Timed.Execute(
@@ -133,7 +134,7 @@ public void WhenItemRemovedEventIsUnregisteredEventIsNotFired()
133134
removedItems.Count.Should().Be(0);
134135
}
135136

136-
[Fact]
137+
[RetryFact]
137138
public void WhenItemsAreExpiredExpireRemovesExpiredItems()
138139
{
139140
Timed.Execute(
@@ -169,7 +170,7 @@ public void WhenItemsAreExpiredExpireRemovesExpiredItems()
169170
);
170171
}
171172

172-
[Fact]
173+
[RetryFact]
173174
public void WhenExpiredItemsAreTrimmedCacheMarkedCold()
174175
{
175176
Timed.Execute(
@@ -211,7 +212,7 @@ public void WhenExpiredItemsAreTrimmedCacheMarkedCold()
211212
);
212213
}
213214

214-
[Fact]
215+
[RetryFact]
215216
public void WhenCacheHasExpiredAndFreshItemsExpireRemovesOnlyExpiredItems()
216217
{
217218
Timed.Execute(
@@ -245,7 +246,7 @@ public void WhenCacheHasExpiredAndFreshItemsExpireRemovesOnlyExpiredItems()
245246
);
246247
}
247248

248-
[Fact]
249+
[RetryFact]
249250
public void WhenItemsAreExpiredTrimRemovesExpiredItems()
250251
{
251252
Timed.Execute(
@@ -272,7 +273,7 @@ public void WhenItemsAreExpiredTrimRemovesExpiredItems()
272273
);
273274
}
274275

275-
[Fact]
276+
[RetryFact]
276277
public void WhenItemsAreExpiredCountFiltersExpiredItems()
277278
{
278279
Timed.Execute(
@@ -293,7 +294,7 @@ public void WhenItemsAreExpiredCountFiltersExpiredItems()
293294
);
294295
}
295296

296-
[Fact]
297+
[RetryFact]
297298
public void WhenItemsAreExpiredEnumerateFiltersExpiredItems()
298299
{
299300
Timed.Execute(
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Xunit.Abstractions;
7+
using Xunit.Sdk;
8+
9+
namespace BitFaster.Caching.UnitTests.Retry
10+
{
11+
public class DelayedMessageBus : IMessageBus
12+
{
13+
private readonly IMessageBus innerBus;
14+
private readonly List<IMessageSinkMessage> messages = new List<IMessageSinkMessage>();
15+
16+
public DelayedMessageBus(IMessageBus innerBus)
17+
{
18+
this.innerBus = innerBus;
19+
}
20+
21+
public bool QueueMessage(IMessageSinkMessage message)
22+
{
23+
// Technically speaking, this lock isn't necessary in our case, because we know we're using this
24+
// message bus for a single test (so there's no possibility of parallelism). However, it's good
25+
// practice when something might be used where parallel messages might arrive, so it's here in
26+
// this sample.
27+
lock (messages)
28+
messages.Add(message);
29+
30+
// No way to ask the inner bus if they want to cancel without sending them the message, so
31+
// we just go ahead and continue always.
32+
return true;
33+
}
34+
35+
public void Dispose()
36+
{
37+
foreach (var message in messages)
38+
innerBus.QueueMessage(message);
39+
}
40+
}
41+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Xunit.Sdk;
2+
using Xunit;
3+
4+
namespace BitFaster.Caching.UnitTests.Retry
5+
{
6+
[XunitTestCaseDiscoverer("RetryFactExample.RetryFactDiscoverer", "RetryFactExample")]
7+
public class RetryFactAttribute : FactAttribute
8+
{
9+
/// <summary>
10+
/// Number of retries allowed for a failed test. If unset (or set less than 1), will
11+
/// default to 3 attempts.
12+
/// </summary>
13+
public int MaxRetries { get; set; }
14+
}
15+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.Collections.Generic;
2+
using Xunit.Abstractions;
3+
using Xunit.Sdk;
4+
5+
namespace BitFaster.Caching.UnitTests.Retry
6+
{
7+
public class RetryFactDiscoverer : IXunitTestCaseDiscoverer
8+
{
9+
readonly IMessageSink diagnosticMessageSink;
10+
11+
public RetryFactDiscoverer(IMessageSink diagnosticMessageSink)
12+
{
13+
this.diagnosticMessageSink = diagnosticMessageSink;
14+
}
15+
16+
public IEnumerable<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute)
17+
{
18+
var maxRetries = factAttribute.GetNamedArgument<int>("MaxRetries");
19+
if (maxRetries < 1)
20+
maxRetries = 3;
21+
22+
yield return new RetryTestCase(diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, maxRetries);
23+
}
24+
}
25+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System;
2+
using System.ComponentModel;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using Xunit.Abstractions;
6+
using Xunit.Sdk;
7+
8+
namespace BitFaster.Caching.UnitTests.Retry
9+
{
10+
[Serializable]
11+
public class RetryTestCase : XunitTestCase
12+
{
13+
private int maxRetries;
14+
15+
[EditorBrowsable(EditorBrowsableState.Never)]
16+
[Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
17+
public RetryTestCase() { }
18+
19+
public RetryTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay testMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod, int maxRetries, object[] testMethodArguments = null)
20+
: base(diagnosticMessageSink, testMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments: testMethodArguments)
21+
{
22+
this.maxRetries = maxRetries;
23+
}
24+
25+
// This method is called by the xUnit test framework classes to run the test case. We will do the
26+
// loop here, forwarding on to the implementation in XunitTestCase to do the heavy lifting. We will
27+
// continue to re-run the test until the aggregator has an error (meaning that some internal error
28+
// condition happened), or the test runs without failure, or we've hit the maximum number of tries.
29+
public override async Task<RunSummary> RunAsync(IMessageSink diagnosticMessageSink,
30+
IMessageBus messageBus,
31+
object[] constructorArguments,
32+
ExceptionAggregator aggregator,
33+
CancellationTokenSource cancellationTokenSource)
34+
{
35+
var runCount = 0;
36+
37+
while (true)
38+
{
39+
// This is really the only tricky bit: we need to capture and delay messages (since those will
40+
// contain run status) until we know we've decided to accept the final result;
41+
var delayedMessageBus = new DelayedMessageBus(messageBus);
42+
43+
var summary = await base.RunAsync(diagnosticMessageSink, delayedMessageBus, constructorArguments, aggregator, cancellationTokenSource);
44+
if (aggregator.HasExceptions || summary.Failed == 0 || ++runCount >= maxRetries)
45+
{
46+
delayedMessageBus.Dispose(); // Sends all the delayed messages
47+
return summary;
48+
}
49+
50+
diagnosticMessageSink.OnMessage(new DiagnosticMessage("Execution of '{0}' failed (attempt #{1}), retrying...", DisplayName, runCount));
51+
}
52+
}
53+
54+
public override void Serialize(IXunitSerializationInfo data)
55+
{
56+
base.Serialize(data);
57+
58+
data.AddValue("MaxRetries", maxRetries);
59+
}
60+
61+
public override void Deserialize(IXunitSerializationInfo data)
62+
{
63+
base.Deserialize(data);
64+
65+
maxRetries = data.GetValue<int>("MaxRetries");
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)