Skip to content

Commit 77453f1

Browse files
committed
1. stabilize tests - .NET 9 has different test concurrency
2. add async expiration tests (different code path needs exercise)
1 parent 4339048 commit 77453f1

File tree

2 files changed

+305
-16
lines changed

2 files changed

+305
-16
lines changed
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.CompilerServices;
6+
using System.Threading.Tasks;
7+
using Microsoft.Extensions.Caching.Distributed;
8+
using Microsoft.Extensions.Caching.Memory;
9+
using Xunit;
10+
11+
namespace Microsoft.Extensions.Caching.StackExchangeRedis;
12+
13+
public class TimeExpirationAsyncTests
14+
{
15+
private const string SkipReason = "TODO: Disabled due to CI failure. " +
16+
"These tests require Redis server to be started on the machine. Make sure to change the value of" +
17+
"\"RedisTestConfig.RedisPort\" accordingly.";
18+
19+
// async twin to ExceptionAssert.ThrowsArgumentOutOfRange
20+
static async Task ThrowsArgumentOutOfRangeAsync(Func<Task> test, string paramName, string message, object actualValue)
21+
{
22+
var ex = await Assert.ThrowsAsync<ArgumentOutOfRangeException>(test);
23+
if (paramName is not null)
24+
{
25+
Assert.Equal(paramName, ex.ParamName);
26+
}
27+
if (message is not null)
28+
{
29+
Assert.StartsWith(message, ex.Message); // can have "\r\nParameter name:" etc
30+
}
31+
if (actualValue is not null)
32+
{
33+
Assert.Equal(actualValue, ex.ActualValue);
34+
}
35+
}
36+
37+
[Fact(Skip = SkipReason)]
38+
public async Task AbsoluteExpirationInThePastThrows()
39+
{
40+
var cache = RedisTestConfig.CreateCacheInstance(GetType().Name);
41+
var key = await GetNameAndReset(cache);
42+
var value = new byte[1];
43+
44+
var expected = DateTimeOffset.Now - TimeSpan.FromMinutes(1);
45+
await ThrowsArgumentOutOfRangeAsync(
46+
async () =>
47+
{
48+
await cache.SetAsync(key, value, new DistributedCacheEntryOptions().SetAbsoluteExpiration(expected));
49+
},
50+
nameof(DistributedCacheEntryOptions.AbsoluteExpiration),
51+
"The absolute expiration value must be in the future.",
52+
expected);
53+
}
54+
55+
[Fact(Skip = SkipReason)]
56+
public async Task AbsoluteExpirationExpires()
57+
{
58+
var cache = RedisTestConfig.CreateCacheInstance(GetType().Name);
59+
var key = await GetNameAndReset(cache);
60+
var value = new byte[1];
61+
62+
await cache.SetAsync(key, value, new DistributedCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(1)));
63+
64+
byte[] result = await cache.GetAsync(key);
65+
Assert.Equal(value, result);
66+
67+
for (int i = 0; i < 4 && (result != null); i++)
68+
{
69+
await Task.Delay(TimeSpan.FromSeconds(0.5));
70+
result = await cache.GetAsync(key);
71+
}
72+
73+
Assert.Null(result);
74+
}
75+
76+
[Fact(Skip = SkipReason)]
77+
public async Task AbsoluteSubSecondExpirationExpiresImmediately()
78+
{
79+
var cache = RedisTestConfig.CreateCacheInstance(GetType().Name);
80+
var key = await GetNameAndReset(cache);
81+
var value = new byte[1];
82+
83+
await cache.SetAsync(key, value, new DistributedCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(0.25)));
84+
85+
var result = await cache.GetAsync(key);
86+
Assert.Null(result);
87+
}
88+
89+
[Fact(Skip = SkipReason)]
90+
public async Task NegativeRelativeExpirationThrows()
91+
{
92+
var cache = RedisTestConfig.CreateCacheInstance(GetType().Name);
93+
var key = await GetNameAndReset(cache);
94+
var value = new byte[1];
95+
96+
await ThrowsArgumentOutOfRangeAsync(async () =>
97+
{
98+
await cache.SetAsync(key, value, new DistributedCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(-1)));
99+
},
100+
nameof(DistributedCacheEntryOptions.AbsoluteExpirationRelativeToNow),
101+
"The relative expiration value must be positive.",
102+
TimeSpan.FromMinutes(-1));
103+
}
104+
105+
[Fact(Skip = SkipReason)]
106+
public async Task ZeroRelativeExpirationThrows()
107+
{
108+
var cache = RedisTestConfig.CreateCacheInstance(GetType().Name);
109+
var key = await GetNameAndReset(cache);
110+
var value = new byte[1];
111+
112+
await ThrowsArgumentOutOfRangeAsync(async () =>
113+
{
114+
await cache.SetAsync(key, value, new DistributedCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.Zero));
115+
},
116+
nameof(DistributedCacheEntryOptions.AbsoluteExpirationRelativeToNow),
117+
"The relative expiration value must be positive.",
118+
TimeSpan.Zero);
119+
}
120+
121+
[Fact(Skip = SkipReason)]
122+
public async Task RelativeExpirationExpires()
123+
{
124+
var cache = RedisTestConfig.CreateCacheInstance(GetType().Name);
125+
var key = await GetNameAndReset(cache);
126+
var value = new byte[1];
127+
128+
await cache.SetAsync(key, value, new DistributedCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(1)));
129+
130+
var result = await cache.GetAsync(key);
131+
Assert.Equal(value, result);
132+
133+
for (int i = 0; i < 4 && (result != null); i++)
134+
{
135+
await Task.Delay(TimeSpan.FromSeconds(0.5));
136+
result = await cache.GetAsync(key);
137+
}
138+
Assert.Null(result);
139+
}
140+
141+
[Fact(Skip = SkipReason)]
142+
public async Task RelativeSubSecondExpirationExpiresImmediately()
143+
{
144+
var cache = RedisTestConfig.CreateCacheInstance(GetType().Name);
145+
var key = await GetNameAndReset(cache);
146+
var value = new byte[1];
147+
148+
await cache.SetAsync(key, value, new DistributedCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(0.25)));
149+
150+
var result = await cache.GetAsync(key);
151+
Assert.Null(result);
152+
}
153+
154+
[Fact(Skip = SkipReason)]
155+
public async Task NegativeSlidingExpirationThrows()
156+
{
157+
var cache = RedisTestConfig.CreateCacheInstance(GetType().Name);
158+
var key = await GetNameAndReset(cache);
159+
var value = new byte[1];
160+
161+
await ThrowsArgumentOutOfRangeAsync(async () =>
162+
{
163+
await cache.SetAsync(key, value, new DistributedCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(-1)));
164+
}, nameof(DistributedCacheEntryOptions.SlidingExpiration), "The sliding expiration value must be positive.", TimeSpan.FromMinutes(-1));
165+
}
166+
167+
[Fact(Skip = SkipReason)]
168+
public async Task ZeroSlidingExpirationThrows()
169+
{
170+
var cache = RedisTestConfig.CreateCacheInstance(GetType().Name);
171+
var key = await GetNameAndReset(cache);
172+
var value = new byte[1];
173+
174+
await ThrowsArgumentOutOfRangeAsync(async () =>
175+
{
176+
await cache.SetAsync(key, value, new DistributedCacheEntryOptions().SetSlidingExpiration(TimeSpan.Zero));
177+
},
178+
nameof(DistributedCacheEntryOptions.SlidingExpiration),
179+
"The sliding expiration value must be positive.",
180+
TimeSpan.Zero);
181+
}
182+
183+
[Fact(Skip = SkipReason)]
184+
public async Task SlidingExpirationExpiresIfNotAccessed()
185+
{
186+
var cache = RedisTestConfig.CreateCacheInstance(GetType().Name);
187+
var key = await GetNameAndReset(cache);
188+
var value = new byte[1];
189+
190+
await cache.SetAsync(key, value, new DistributedCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromSeconds(1)));
191+
192+
var result = await cache.GetAsync(key);
193+
Assert.Equal(value, result);
194+
195+
await Task.Delay(TimeSpan.FromSeconds(3.5));
196+
197+
result = await cache.GetAsync(key);
198+
Assert.Null(result);
199+
}
200+
201+
[Fact(Skip = SkipReason)]
202+
public async Task SlidingSubSecondExpirationExpiresImmediately()
203+
{
204+
var cache = RedisTestConfig.CreateCacheInstance(GetType().Name);
205+
var key = await GetNameAndReset(cache);
206+
var value = new byte[1];
207+
208+
await cache.SetAsync(key, value, new DistributedCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromSeconds(0.25)));
209+
210+
var result = await cache.GetAsync(key);
211+
Assert.Null(result);
212+
}
213+
214+
[Fact(Skip = SkipReason)]
215+
public async Task SlidingExpirationRenewedByAccess()
216+
{
217+
var cache = RedisTestConfig.CreateCacheInstance(GetType().Name);
218+
var key = await GetNameAndReset(cache);
219+
var value = new byte[1];
220+
221+
await cache.SetAsync(key, value, new DistributedCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromSeconds(1)));
222+
223+
var result = await cache.GetAsync(key);
224+
Assert.Equal(value, result);
225+
226+
for (int i = 0; i < 5; i++)
227+
{
228+
await Task.Delay(TimeSpan.FromSeconds(0.5));
229+
230+
result = await cache.GetAsync(key);
231+
Assert.Equal(value, result);
232+
}
233+
234+
await Task.Delay(TimeSpan.FromSeconds(3));
235+
result = await cache.GetAsync(key);
236+
Assert.Null(result);
237+
}
238+
239+
[Fact(Skip = SkipReason)]
240+
public async Task SlidingExpirationRenewedByAccessUntilAbsoluteExpiration()
241+
{
242+
var cache = RedisTestConfig.CreateCacheInstance(GetType().Name);
243+
var key = await GetNameAndReset(cache);
244+
var value = new byte[1];
245+
246+
await cache.SetAsync(key, value, new DistributedCacheEntryOptions()
247+
.SetSlidingExpiration(TimeSpan.FromSeconds(1))
248+
.SetAbsoluteExpiration(TimeSpan.FromSeconds(3)));
249+
250+
var setTime = DateTime.Now;
251+
var result = await cache.GetAsync(key);
252+
Assert.Equal(value, result);
253+
254+
for (int i = 0; i < 5; i++)
255+
{
256+
await Task.Delay(TimeSpan.FromSeconds(0.5));
257+
258+
result = await cache.GetAsync(key);
259+
Assert.NotNull(result);
260+
Assert.Equal(value, result);
261+
}
262+
263+
while ((DateTime.Now - setTime).TotalSeconds < 4)
264+
{
265+
await Task.Delay(TimeSpan.FromSeconds(0.5));
266+
}
267+
268+
result = await cache.GetAsync(key);
269+
Assert.Null(result);
270+
}
271+
272+
static async Task<string> GetNameAndReset(IDistributedCache cache, [CallerMemberName] string caller = "")
273+
{
274+
await cache.RemoveAsync(caller);
275+
return caller;
276+
}
277+
}

0 commit comments

Comments
 (0)