Skip to content

Commit 52f0850

Browse files
Adopt TestAccessor pattern for MemoryCache
1 parent 8ffc1c7 commit 52f0850

File tree

3 files changed

+69
-54
lines changed

3 files changed

+69
-54
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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+
6+
namespace Microsoft.CodeAnalysis.Razor.Utilities;
7+
8+
internal partial class MemoryCache<TKey, TValue>
9+
where TKey : notnull
10+
where TValue : class
11+
{
12+
internal TestAccessor GetTestAccessor() => new(this);
13+
14+
internal ref struct TestAccessor(MemoryCache<TKey, TValue> instance)
15+
{
16+
public event Action Compacted
17+
{
18+
add => instance._compactedHandler += value;
19+
remove => instance._compactedHandler -= value;
20+
}
21+
22+
public readonly bool TryGetLastAccess(TKey key, out DateTime result)
23+
{
24+
if (instance._dict.TryGetValue(key, out var value))
25+
{
26+
result = value.LastAccess;
27+
return true;
28+
}
29+
30+
result = default;
31+
return false;
32+
}
33+
}
34+
}

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Utilities/MemoryCache`2.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.Razor.Utilities;
1111

1212
// We've created our own MemoryCache here, ideally we would use the one in Microsoft.Extensions.Caching.Memory,
1313
// but until we update O# that causes an Assembly load problem.
14-
internal class MemoryCache<TKey, TValue>
14+
internal partial class MemoryCache<TKey, TValue>
1515
where TKey : notnull
1616
where TValue : class
1717
{
@@ -23,6 +23,8 @@ internal class MemoryCache<TKey, TValue>
2323
private readonly object _compactLock;
2424
private readonly int _sizeLimit;
2525

26+
private Action? _compactedHandler;
27+
2628
public MemoryCache(int sizeLimit = DefaultSizeLimit, int concurrencyLevel = DefaultConcurrencyLevel)
2729
{
2830
_sizeLimit = sizeLimit;
@@ -70,6 +72,8 @@ protected virtual void Compact()
7072
{
7173
_dict.Remove(kvps[i].Key);
7274
}
75+
76+
_compactedHandler?.Invoke();
7377
}
7478

7579
protected class CacheEntry

src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Utilities/MemoryCacheTest.cs

Lines changed: 30 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.Globalization;
76
using System.Linq;
87
using System.Threading;
98
using System.Threading.Tasks;
@@ -20,7 +19,7 @@ public class MemoryCacheTest(ITestOutputHelper testOutput) : ToolingTestBase(tes
2019
public async Task ConcurrentSets_DoesNotThrow()
2120
{
2221
// Arrange
23-
var cache = new TestMemoryCache();
22+
var cache = new MemoryCache<string, IReadOnlyList<int>>();
2423
var entries = Enumerable.Range(0, 500);
2524
var repeatCount = 4;
2625

@@ -30,7 +29,7 @@ public async Task ConcurrentSets_DoesNotThrow()
3029
{
3130
// 2 is an arbitrarily low number, we're just trying to emulate concurrency
3231
await Task.Delay(2);
33-
cache.Set(entry.ToString(CultureInfo.InvariantCulture), Array.Empty<uint>());
32+
cache.Set(entry.ToString(), value: []);
3433
});
3534

3635
// Act & Assert
@@ -40,27 +39,28 @@ public async Task ConcurrentSets_DoesNotThrow()
4039
[Fact]
4140
public void LastAccessIsUpdated()
4241
{
43-
var cache = new TestMemoryCache();
44-
var key = GetKey();
45-
var value = new List<uint>();
42+
var cache = new MemoryCache<string, IReadOnlyList<int>>();
43+
var cacheAccessor = cache.GetTestAccessor();
4644

47-
cache.Set(key, value);
48-
var oldAccessTime = cache.GetAccessTime(key);
45+
var key = GetNewKey();
46+
47+
cache.Set(key, value: []);
48+
Assert.True(cacheAccessor.TryGetLastAccess(key, out var oldAccessTime));
4949

5050
Thread.Sleep(millisecondsTimeout: 10);
5151

5252
cache.TryGetValue(key, out _);
53-
var newAccessTime = cache.GetAccessTime(key);
53+
Assert.True(cacheAccessor.TryGetLastAccess(key, out var newAccessTime));
5454

5555
Assert.True(newAccessTime > oldAccessTime, "New AccessTime should be greater than old");
5656
}
5757

5858
[Fact]
5959
public void BasicAdd()
6060
{
61-
var cache = new TestMemoryCache();
62-
var key = GetKey();
63-
var value = new List<uint> { 1, 2, 3 };
61+
var cache = new MemoryCache<string, IReadOnlyList<int>>();
62+
var key = GetNewKey();
63+
var value = new List<int> { 1, 2, 3 };
6464

6565
cache.Set(key, value);
6666

@@ -72,64 +72,41 @@ public void BasicAdd()
7272
[Fact]
7373
public void Compaction()
7474
{
75-
var cache = new TestMemoryCache();
76-
var sizeLimit = TestMemoryCache.SizeLimit;
75+
const int SizeLimit = 10;
76+
77+
var cache = new MemoryCache<string, IReadOnlyList<int>>(SizeLimit);
78+
var cacheAccessor = cache.GetTestAccessor();
7779

78-
for (var i = 0; i < sizeLimit; i++)
80+
var wasCompacted = false;
81+
cacheAccessor.Compacted += () => wasCompacted = true;
82+
83+
for (var i = 0; i < SizeLimit; i++)
7984
{
80-
var key = GetKey();
81-
var value = new List<uint> { (uint)i };
82-
cache.Set(key, value);
83-
Assert.False(cache._wasCompacted, "It got compacted early.");
85+
cache.Set(GetNewKey(), [i]);
86+
Assert.False(wasCompacted, "It got compacted early.");
8487
}
8588

86-
cache.Set(GetKey(), new List<uint> { (uint)sizeLimit + 1 });
87-
Assert.True(cache._wasCompacted, "Compaction is not happening");
89+
cache.Set(GetNewKey(), [SizeLimit]);
90+
Assert.True(wasCompacted, "Compaction is not happening");
8891
}
8992

9093
[Fact]
9194
public void MissingKey()
9295
{
93-
var cache = new TestMemoryCache();
94-
var key = GetKey();
95-
96-
cache.TryGetValue(key, out var value);
96+
var cache = new MemoryCache<string, IReadOnlyList<int>>();
97+
var key = GetNewKey();
9798

98-
Assert.Null(value);
99+
Assert.False(cache.TryGetValue(key, out _));
99100
}
100101

101102
[Fact]
102103
public void NullKey()
103104
{
104-
var cache = new TestMemoryCache();
105+
var cache = new MemoryCache<string, IReadOnlyList<int>>();
105106

106107
Assert.Throws<ArgumentNullException>(() => cache.TryGetValue(key: null!, out var result));
107108
}
108109

109-
private static string GetKey()
110-
{
111-
return Guid.NewGuid().ToString();
112-
}
113-
114-
private class TestMemoryCache : MemoryCache<string, IReadOnlyList<uint>>
115-
{
116-
public static int SizeLimit = 10;
117-
public bool _wasCompacted = false;
118-
119-
public TestMemoryCache()
120-
: base(SizeLimit)
121-
{
122-
}
123-
124-
public DateTime GetAccessTime(string key)
125-
{
126-
return _dict[key].LastAccess;
127-
}
128-
129-
protected override void Compact()
130-
{
131-
_wasCompacted = true;
132-
base.Compact();
133-
}
134-
}
110+
private static string GetNewKey()
111+
=> Guid.NewGuid().ToString();
135112
}

0 commit comments

Comments
 (0)