Skip to content

Commit 8c1abbe

Browse files
authored
decorators for atomic value factories (#155)
* outline * factory tests * afs tests * factor out eventbase * test afc * test afa * test afsa * saaf tests * event proxy tests * dedupe * seal
1 parent 9a5e655 commit 8c1abbe

20 files changed

+1407
-195
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using BitFaster.Caching.Lru;
7+
using BitFaster.Caching.Synchronized;
8+
using FluentAssertions;
9+
using Xunit;
10+
11+
namespace BitFaster.Caching.UnitTests
12+
{
13+
public class CacheEventProxyBaseTests
14+
{
15+
private TestCacheEvents<int, int> testCacheEvents;
16+
private EventProxy<int, int> eventProxy;
17+
18+
private List<ItemRemovedEventArgs<int, int>> removedItems = new();
19+
20+
public CacheEventProxyBaseTests()
21+
{
22+
this.testCacheEvents = new TestCacheEvents<int, int>();
23+
this.eventProxy = new EventProxy<int, int>(this.testCacheEvents);
24+
}
25+
26+
[Fact]
27+
public void EventsAreEnabled()
28+
{
29+
this.testCacheEvents.IsEnabled = true;
30+
31+
this.eventProxy.IsEnabled.Should().BeTrue();
32+
}
33+
34+
[Fact]
35+
public void WhenEventHandlerIsRegisteredItIsFired()
36+
{
37+
this.eventProxy.ItemRemoved += OnItemRemoved;
38+
39+
this.testCacheEvents.Fire(1, new AtomicFactory<int, int>(1), ItemRemovedReason.Removed);
40+
41+
this.removedItems.First().Key.Should().Be(1);
42+
}
43+
44+
[Fact]
45+
public void WhenEventHandlerIsAddedThenRemovedItIsNotFired()
46+
{
47+
this.eventProxy.ItemRemoved += OnItemRemoved;
48+
this.eventProxy.ItemRemoved -= OnItemRemoved;
49+
50+
this.testCacheEvents.Fire(1, new AtomicFactory<int, int>(1), ItemRemovedReason.Removed);
51+
52+
this.removedItems.Count.Should().Be(0);
53+
}
54+
55+
[Fact]
56+
public void WhenTwoEventHandlersAddedThenOneRemovedEventIsFired()
57+
{
58+
this.eventProxy.ItemRemoved += OnItemRemoved;
59+
this.eventProxy.ItemRemoved += OnItemRemovedThrow;
60+
this.eventProxy.ItemRemoved -= OnItemRemovedThrow;
61+
62+
this.testCacheEvents.Fire(1, new AtomicFactory<int, int>(1), ItemRemovedReason.Removed);
63+
64+
this.removedItems.First().Key.Should().Be(1);
65+
}
66+
67+
private void OnItemRemoved(object sender, ItemRemovedEventArgs<int, int> e)
68+
{
69+
this.removedItems.Add(e);
70+
}
71+
72+
private void OnItemRemovedThrow(object sender, ItemRemovedEventArgs<int, int> e)
73+
{
74+
throw new Exception("Should never happen");
75+
}
76+
77+
private class TestCacheEvents<K, V> : ICacheEvents<K, AtomicFactory<K, V>>
78+
{
79+
public bool IsEnabled { get; set; }
80+
81+
public event EventHandler<ItemRemovedEventArgs<K, AtomicFactory<K, V>>> ItemRemoved;
82+
83+
public void Fire(K key, AtomicFactory<K, V> value, ItemRemovedReason reason)
84+
{
85+
ItemRemoved?.Invoke(this, new ItemRemovedEventArgs<K, AtomicFactory<K, V>>(key, value, reason));
86+
}
87+
}
88+
89+
private class EventProxy<K, V> : CacheEventProxyBase<K, AtomicFactory<K, V>, V>
90+
{
91+
public EventProxy(ICacheEvents<K, AtomicFactory<K, V>> inner)
92+
: base(inner)
93+
{
94+
}
95+
96+
protected override ItemRemovedEventArgs<K, V> TranslateOnRemoved(ItemRemovedEventArgs<K, AtomicFactory<K, V>> inner)
97+
{
98+
return new ItemRemovedEventArgs<K, V>(inner.Key, inner.Value.ValueIfCreated, inner.Reason);
99+
}
100+
}
101+
}
102+
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using BitFaster.Caching.Lru;
7+
using FluentAssertions;
8+
using Xunit;
9+
10+
namespace BitFaster.Caching.UnitTests
11+
{
12+
public abstract class ScopedCacheTestBase
13+
{
14+
protected const int capacity = 6;
15+
protected readonly IScopedCache<int, Disposable> cache;
16+
17+
protected List<ItemRemovedEventArgs<int, Scoped<Disposable>>> removedItems = new();
18+
19+
protected ScopedCacheTestBase(IScopedCache<int, Disposable> cache)
20+
{
21+
this.cache = cache;
22+
}
23+
24+
[Fact]
25+
public void WhenCreatedCapacityPropertyWrapsInnerCache()
26+
{
27+
this.cache.Capacity.Should().Be(capacity);
28+
}
29+
30+
[Fact]
31+
public void WhenItemIsAddedCountIsCorrect()
32+
{
33+
this.cache.Count.Should().Be(0);
34+
35+
this.cache.AddOrUpdate(1, new Disposable());
36+
37+
this.cache.Count.Should().Be(1);
38+
}
39+
40+
[Fact]
41+
public void WhenItemIsAddedThenLookedUpMetricsAreCorrect()
42+
{
43+
this.cache.AddOrUpdate(1, new Disposable());
44+
this.cache.ScopedTryGet(1, out var lifetime);
45+
46+
this.cache.Metrics.Misses.Should().Be(0);
47+
this.cache.Metrics.Hits.Should().Be(1);
48+
}
49+
50+
[Fact]
51+
public void WhenEventHandlerIsRegisteredItIsFired()
52+
{
53+
this.cache.Events.ItemRemoved += OnItemRemoved;
54+
55+
this.cache.AddOrUpdate(1, new Disposable());
56+
this.cache.TryRemove(1);
57+
58+
this.removedItems.First().Key.Should().Be(1);
59+
}
60+
61+
[Fact]
62+
public void WhenKeyDoesNotExistAddOrUpdateAddsNewItem()
63+
{
64+
var d = new Disposable();
65+
this.cache.AddOrUpdate(1, d);
66+
67+
this.cache.ScopedTryGet(1, out var lifetime).Should().BeTrue();
68+
lifetime.Value.Should().Be(d);
69+
}
70+
71+
[Fact]
72+
public void WhenKeyExistsAddOrUpdateUpdatesExistingItem()
73+
{
74+
var d1 = new Disposable();
75+
var d2 = new Disposable();
76+
this.cache.AddOrUpdate(1, d1);
77+
this.cache.AddOrUpdate(1, d2);
78+
79+
this.cache.ScopedTryGet(1, out var lifetime).Should().BeTrue();
80+
lifetime.Value.Should().Be(d2);
81+
}
82+
83+
[Fact]
84+
public void WhenItemUpdatedOldValueIsAliveUntilLifetimeCompletes()
85+
{
86+
var d1 = new Disposable();
87+
var d2 = new Disposable();
88+
89+
// start a lifetime on 1
90+
this.cache.AddOrUpdate(1, d1);
91+
this.cache.ScopedTryGet(1, out var lifetime1).Should().BeTrue();
92+
93+
using (lifetime1)
94+
{
95+
// replace 1
96+
this.cache.AddOrUpdate(1, d2);
97+
98+
// cache reflects replacement
99+
this.cache.ScopedTryGet(1, out var lifetime2).Should().BeTrue();
100+
lifetime2.Value.Should().Be(d2);
101+
102+
d1.IsDisposed.Should().BeFalse();
103+
}
104+
105+
d1.IsDisposed.Should().BeTrue();
106+
}
107+
108+
[Fact]
109+
public void WhenClearedItemsAreDisposed()
110+
{
111+
var d = new Disposable();
112+
this.cache.AddOrUpdate(1, d);
113+
114+
this.cache.Clear();
115+
116+
d.IsDisposed.Should().BeTrue();
117+
}
118+
119+
[Fact]
120+
public void WhenItemExistsTryGetReturnsLifetime()
121+
{
122+
this.cache.AddOrUpdate(1, new Disposable());
123+
this.cache.ScopedTryGet(1, out var lifetime).Should().BeTrue();
124+
125+
lifetime.Should().NotBeNull();
126+
}
127+
128+
[Fact]
129+
public void WhenItemDoesNotExistTryGetReturnsFalse()
130+
{
131+
this.cache.ScopedTryGet(1, out var lifetime).Should().BeFalse();
132+
}
133+
134+
[Fact]
135+
public void WhenCacheContainsValuesTrim1RemovesColdestValue()
136+
{
137+
this.cache.AddOrUpdate(0, new Disposable());
138+
this.cache.AddOrUpdate(1, new Disposable());
139+
this.cache.AddOrUpdate(2, new Disposable());
140+
141+
this.cache.Trim(1);
142+
143+
this.cache.ScopedTryGet(0, out var lifetime).Should().BeFalse();
144+
}
145+
146+
[Fact]
147+
public void WhenKeyDoesNotExistTryRemoveReturnsFalse()
148+
{
149+
this.cache.TryRemove(1).Should().BeFalse();
150+
}
151+
152+
[Fact]
153+
public void WhenKeyExistsTryRemoveReturnsTrue()
154+
{
155+
this.cache.AddOrUpdate(1, new Disposable());
156+
this.cache.TryRemove(1).Should().BeTrue();
157+
}
158+
159+
[Fact]
160+
public void WhenKeyDoesNotExistTryUpdateReturnsFalse()
161+
{
162+
this.cache.TryUpdate(1, new Disposable()).Should().BeFalse();
163+
}
164+
165+
[Fact]
166+
public void WhenKeyExistsTryUpdateReturnsTrue()
167+
{
168+
this.cache.AddOrUpdate(1, new Disposable());
169+
170+
this.cache.TryUpdate(1, new Disposable()).Should().BeTrue();
171+
}
172+
173+
protected void OnItemRemoved(object sender, ItemRemovedEventArgs<int, Scoped<Disposable>> e)
174+
{
175+
this.removedItems.Add(e);
176+
}
177+
}
178+
}

0 commit comments

Comments
 (0)