Skip to content

Commit b4a5a03

Browse files
authored
Align IAsyncCache.TryRemove overloads with ConcurrentDictionary (#473)
Provide functional parity with ConcurrentDictionary for IAsyncCache.TryRemove, following the implementation for ICache in PR #394.
1 parent fe1a27a commit b4a5a03

File tree

6 files changed

+315
-117
lines changed

6 files changed

+315
-117
lines changed

BitFaster.Caching.UnitTests/Atomic/AsyncAtomicFactoryTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,58 @@ public async Task WhenCallersRunConcurrentlyResultIsFromWinner()
112112
(await second).Should().Be(result);
113113

114114
winnerCount.Should().Be(1);
115+
}
116+
117+
[Fact]
118+
public void WhenValueNotCreatedHashCodeIsZero()
119+
{
120+
new AsyncAtomicFactory<int, int>()
121+
.GetHashCode()
122+
.Should().Be(0);
123+
}
124+
125+
[Fact]
126+
public void WhenValueCreatedHashCodeIsValueHashCode()
127+
{
128+
new AsyncAtomicFactory<int, int>(1)
129+
.GetHashCode()
130+
.Should().Be(1);
131+
}
132+
133+
[Fact]
134+
public void WhenValueNotCreatedEqualsFalse()
135+
{
136+
var a = new AsyncAtomicFactory<int, int>();
137+
var b = new AsyncAtomicFactory<int, int>();
138+
139+
a.Equals(b).Should().BeFalse();
140+
}
141+
142+
[Fact]
143+
public void WhenOtherValueNotCreatedEqualsFalse()
144+
{
145+
var a = new AsyncAtomicFactory<int, int>(1);
146+
var b = new AsyncAtomicFactory<int, int>();
147+
148+
a.Equals(b).Should().BeFalse();
149+
}
150+
151+
[Fact]
152+
public void WhenArgNullEqualsFalse()
153+
{
154+
new AsyncAtomicFactory<int, int>(1)
155+
.Equals(null)
156+
.Should().BeFalse();
157+
}
158+
159+
[Fact]
160+
public void WhenArgObjectValuesAreSameEqualsTrue()
161+
{
162+
object other = new AsyncAtomicFactory<int, int>(1);
163+
164+
new AsyncAtomicFactory<int, int>(1)
165+
.Equals(other)
166+
.Should().BeTrue();
115167
}
116168
}
117169
}

BitFaster.Caching.UnitTests/Atomic/AtomicFactoryAsyncCacheTests.cs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@ namespace BitFaster.Caching.UnitTests.Atomic
1515
public class AtomicFactoryAsyncCacheTests
1616
{
1717
private const int capacity = 6;
18-
private readonly AtomicFactoryAsyncCache<int, int> cache = new(new ConcurrentLru<int, AsyncAtomicFactory<int, int>>(capacity));
18+
private readonly ConcurrentLru<int, AsyncAtomicFactory<int, int>> innerCache = new(capacity);
19+
private readonly AtomicFactoryAsyncCache<int, int> cache;
1920

2021
private List<ItemRemovedEventArgs<int, int>> removedItems = new();
2122
private List<ItemUpdatedEventArgs<int, int>> updatedItems = new();
2223

24+
public AtomicFactoryAsyncCacheTests()
25+
{
26+
cache = new(innerCache);
27+
}
28+
2329
[Fact]
2430
public void WhenInnerCacheIsNullCtorThrows()
2531
{
@@ -250,8 +256,53 @@ public async Task WhenFactoryThrowsEmptyKeyIsNotEnumerable()
250256
catch { }
251257

252258
cache.Keys.Count().Should().Be(0);
259+
}
260+
261+
// backcompat: remove conditional compile
262+
#if NETCOREAPP3_0_OR_GREATER
263+
[Fact]
264+
public void WhenRemovedValueIsReturned()
265+
{
266+
this.cache.AddOrUpdate(1, 1);
267+
this.cache.TryRemove(1, out var value);
268+
269+
value.Should().Be(1);
253270
}
254271

272+
[Fact]
273+
public void WhenNotRemovedValueIsDefault()
274+
{
275+
this.cache.AddOrUpdate(1, 1);
276+
this.cache.TryRemove(2, out var value);
277+
278+
value.Should().Be(0);
279+
}
280+
281+
[Fact]
282+
public void WhenRemoveKeyValueAndValueDoesntMatchDontRemove()
283+
{
284+
this.cache.AddOrUpdate(1, 1);
285+
this.cache.TryRemove(new KeyValuePair<int, int>(1, 2)).Should().BeFalse();
286+
}
287+
288+
[Fact]
289+
public void WhenRemoveKeyValueAndValueDoesMatchThenRemove()
290+
{
291+
this.cache.AddOrUpdate(1, 1);
292+
this.cache.TryRemove(new KeyValuePair<int, int>(1, 1)).Should().BeTrue();
293+
}
294+
295+
[Fact]
296+
public void WhenRemoveKeyValueAndValueIsNotCreatedDoesNotRemove()
297+
{
298+
// seed the inner cache with an not yet created value
299+
this.innerCache.AddOrUpdate(1, new AsyncAtomicFactory<int, int>());
300+
301+
// try to remove with the default value (0)
302+
this.cache.TryRemove(new KeyValuePair<int, int>(1, 0)).Should().BeFalse();
303+
}
304+
#endif
305+
255306
private void OnItemRemoved(object sender, ItemRemovedEventArgs<int, int> e)
256307
{
257308
this.removedItems.Add(e);
Lines changed: 136 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,141 @@
1-

2-
using System;
1+

2+
using System;
33
using System.Collections.Generic;
4-
using System.Threading.Tasks;
5-
using FluentAssertions;
6-
using Moq;
7-
using Xunit;
8-
9-
namespace BitFaster.Caching.UnitTests
10-
{
11-
// Tests for interface default implementations.
12-
public class CacheTests
13-
{
14-
// backcompat: remove conditional compile
15-
#if NETCOREAPP3_0_OR_GREATER
16-
[Fact]
17-
public void WhenCacheInterfaceDefaultGetOrAddFallback()
18-
{
19-
var cache = new Mock<ICache<int, int>>();
20-
cache.CallBase = true;
21-
22-
Func<int, Func<int, int>, int> evaluate = (k, f) => f(k);
23-
cache.Setup(c => c.GetOrAdd(It.IsAny<int>(), It.IsAny<Func<int, int>>())).Returns(evaluate);
24-
25-
cache.Object.GetOrAdd(
26-
1,
27-
(k, a) => k + a,
28-
2).Should().Be(3);
4+
using System.Threading.Tasks;
5+
using FluentAssertions;
6+
using Moq;
7+
using Xunit;
8+
9+
namespace BitFaster.Caching.UnitTests
10+
{
11+
// Tests for interface default implementations.
12+
public class CacheTests
13+
{
14+
// backcompat: remove conditional compile
15+
#if NETCOREAPP3_0_OR_GREATER
16+
[Fact]
17+
public void WhenCacheInterfaceDefaultGetOrAddFallback()
18+
{
19+
var cache = new Mock<ICache<int, int>>();
20+
cache.CallBase = true;
21+
22+
Func<int, Func<int, int>, int> evaluate = (k, f) => f(k);
23+
cache.Setup(c => c.GetOrAdd(It.IsAny<int>(), It.IsAny<Func<int, int>>())).Returns(evaluate);
24+
25+
cache.Object.GetOrAdd(
26+
1,
27+
(k, a) => k + a,
28+
2).Should().Be(3);
29+
}
30+
31+
[Fact]
32+
public void WhenCacheInterfaceDefaultTryRemoveKeyThrows()
33+
{
34+
var cache = new Mock<ICache<int, int>>();
35+
cache.CallBase = true;
36+
37+
Action tryRemove = () => { cache.Object.TryRemove(1, out var value); };
38+
39+
tryRemove.Should().Throw<NotSupportedException>();
40+
}
41+
42+
[Fact]
43+
public void WhenCacheInterfaceDefaultTryRemoveKeyValueThrows()
44+
{
45+
var cache = new Mock<ICache<int, int>>();
46+
cache.CallBase = true;
47+
48+
Action tryRemove = () => { cache.Object.TryRemove(new KeyValuePair<int, int>(1, 1)); };
49+
50+
tryRemove.Should().Throw<NotSupportedException>();
51+
}
52+
53+
[Fact]
54+
public async Task WhenAsyncCacheInterfaceDefaultGetOrAddFallback()
55+
{
56+
var cache = new Mock<IAsyncCache<int, int>>();
57+
cache.CallBase = true;
58+
59+
Func<int, Func<int, Task<int>>, ValueTask<int>> evaluate = (k, f) => new ValueTask<int>(f(k));
60+
cache.Setup(c => c.GetOrAddAsync(It.IsAny<int>(), It.IsAny<Func<int, Task<int>>>())).Returns(evaluate);
61+
62+
var r = await cache.Object.GetOrAddAsync(
63+
1,
64+
(k, a) => Task.FromResult(k + a),
65+
2);
66+
67+
r.Should().Be(3);
68+
}
69+
70+
[Fact]
71+
public void WhenAsyncCacheInterfaceDefaultTryRemoveKeyThrows()
72+
{
73+
var cache = new Mock<IAsyncCache<int, int>>();
74+
cache.CallBase = true;
75+
76+
Action tryRemove = () => { cache.Object.TryRemove(1, out var value); };
77+
78+
tryRemove.Should().Throw<NotSupportedException>();
79+
}
80+
81+
[Fact]
82+
public void WhenAsyncCacheInterfaceDefaultTryRemoveKeyValueThrows()
83+
{
84+
var cache = new Mock<IAsyncCache<int, int>>();
85+
cache.CallBase = true;
86+
87+
Action tryRemove = () => { cache.Object.TryRemove(new KeyValuePair<int, int>(1, 1)); };
88+
89+
tryRemove.Should().Throw<NotSupportedException>();
2990
}
3091

31-
[Fact]
32-
public void WhenCacheInterfaceDefaultTryRemoveKeyThrows()
33-
{
34-
var cache = new Mock<ICache<int, int>>();
35-
cache.CallBase = true;
36-
37-
Action tryRemove = () => { cache.Object.TryRemove(1, out var value); };
38-
39-
tryRemove.Should().Throw<NotSupportedException>();
92+
[Fact]
93+
public void WhenScopedCacheInterfaceDefaultGetOrAddFallback()
94+
{
95+
var cache = new Mock<IScopedCache<int, Disposable>>();
96+
cache.CallBase = true;
97+
98+
Func<int, Func<int, Scoped<Disposable>>, Lifetime<Disposable>> evaluate = (k, f) =>
99+
{
100+
var scope = f(k);
101+
scope.TryCreateLifetime(out var lifetime).Should().BeTrue();
102+
return lifetime;
103+
};
104+
105+
cache.Setup(c => c.ScopedGetOrAdd(It.IsAny<int>(), It.IsAny<Func<int, Scoped<Disposable>>>())).Returns(evaluate);
106+
107+
var l = cache.Object.ScopedGetOrAdd(
108+
1,
109+
(k, a) => new Scoped<Disposable>(new Disposable(k + a)),
110+
2);
111+
112+
l.Value.State.Should().Be(3);
40113
}
41114

42-
[Fact]
43-
public void WhenCacheInterfaceDefaultTryRemoveKeyValueThrows()
44-
{
45-
var cache = new Mock<ICache<int, int>>();
46-
cache.CallBase = true;
47-
48-
Action tryRemove = () => { cache.Object.TryRemove(new KeyValuePair<int, int>(1, 1)); };
49-
50-
tryRemove.Should().Throw<NotSupportedException>();
51-
}
52-
53-
[Fact]
54-
public async Task WhenAsyncCacheInterfaceDefaultGetOrAddFallback()
55-
{
56-
var cache = new Mock<IAsyncCache<int, int>>();
57-
cache.CallBase = true;
58-
59-
Func<int, Func<int, Task<int>>, ValueTask<int>> evaluate = (k, f) => new ValueTask<int>(f(k));
60-
cache.Setup(c => c.GetOrAddAsync(It.IsAny<int>(), It.IsAny<Func<int, Task<int>>>())).Returns(evaluate);
61-
62-
var r = await cache.Object.GetOrAddAsync(
63-
1,
64-
(k, a) => Task.FromResult(k + a),
65-
2);
66-
67-
r.Should().Be(3);
68-
}
69-
70-
[Fact]
71-
public void WhenScopedCacheInterfaceDefaultGetOrAddFallback()
72-
{
73-
var cache = new Mock<IScopedCache<int, Disposable>>();
74-
cache.CallBase = true;
75-
76-
Func<int, Func<int, Scoped<Disposable>>, Lifetime<Disposable>> evaluate = (k, f) =>
77-
{
78-
var scope = f(k);
79-
scope.TryCreateLifetime(out var lifetime).Should().BeTrue();
80-
return lifetime;
81-
};
82-
83-
cache.Setup(c => c.ScopedGetOrAdd(It.IsAny<int>(), It.IsAny<Func<int, Scoped<Disposable>>>())).Returns(evaluate);
84-
85-
var l = cache.Object.ScopedGetOrAdd(
86-
1,
87-
(k, a) => new Scoped<Disposable>(new Disposable(k + a)),
88-
2);
89-
90-
l.Value.State.Should().Be(3);
91-
}
92-
93-
[Fact]
94-
public async Task WhenScopedAsyncCacheInterfaceDefaultGetOrAddFallback()
95-
{
96-
var cache = new Mock<IScopedAsyncCache<int, Disposable>>();
97-
cache.CallBase = true;
98-
99-
Func<int, Func<int, Task<Scoped<Disposable>>>, ValueTask<Lifetime<Disposable>>> evaluate = async (k, f) =>
100-
{
101-
var scope = await f(k);
102-
scope.TryCreateLifetime(out var lifetime).Should().BeTrue();
103-
return lifetime;
104-
};
105-
106-
cache
107-
.Setup(c => c.ScopedGetOrAddAsync(It.IsAny<int>(), It.IsAny<Func<int, Task<Scoped<Disposable>>>>()))
108-
.Returns(evaluate);
109-
110-
var lifetime = await cache.Object.ScopedGetOrAddAsync(
111-
1,
112-
(k, a) => Task.FromResult(new Scoped<Disposable>(new Disposable(k + a))),
113-
2);
114-
115-
lifetime.Value.State.Should().Be(3);
116-
}
117-
#endif
118-
}
119-
}
115+
[Fact]
116+
public async Task WhenScopedAsyncCacheInterfaceDefaultGetOrAddFallback()
117+
{
118+
var cache = new Mock<IScopedAsyncCache<int, Disposable>>();
119+
cache.CallBase = true;
120+
121+
Func<int, Func<int, Task<Scoped<Disposable>>>, ValueTask<Lifetime<Disposable>>> evaluate = async (k, f) =>
122+
{
123+
var scope = await f(k);
124+
scope.TryCreateLifetime(out var lifetime).Should().BeTrue();
125+
return lifetime;
126+
};
127+
128+
cache
129+
.Setup(c => c.ScopedGetOrAddAsync(It.IsAny<int>(), It.IsAny<Func<int, Task<Scoped<Disposable>>>>()))
130+
.Returns(evaluate);
131+
132+
var lifetime = await cache.Object.ScopedGetOrAddAsync(
133+
1,
134+
(k, a) => Task.FromResult(new Scoped<Disposable>(new Disposable(k + a))),
135+
2);
136+
137+
lifetime.Value.State.Should().Be(3);
138+
}
139+
#endif
140+
}
141+
}

0 commit comments

Comments
 (0)