Skip to content

Commit

Permalink
Unit tests for custom code, fixed bugs, custom event buffer implemena…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
nasimsaleh committed Jun 28, 2024
1 parent 380f311 commit 8c8a508
Show file tree
Hide file tree
Showing 10 changed files with 1,103 additions and 202 deletions.
2 changes: 2 additions & 0 deletions .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ README.md

src/SchematicHQ.Client.Test/TestCache.cs
src/SchematicHQ.Client.Test/TestEventBuffer.cs
src/SchematicHQ.Client.Test/TestClient.cs
src/SchematicHQ.Client.Test/TestLogger.cs
src/SchematicHQ.Client/Cache.cs
src/SchematicHQ.Client/Core/ClientOptionsCustom.cs
src/SchematicHQ.Client/EventBuffer.cs
Expand Down
12 changes: 7 additions & 5 deletions src/SchematicHQ.Client.Test/SchematicHQ.Client.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2"/>
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/>
<PackageReference Include="coverlet.collector" Version="3.2.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="Moq.Contrib.HttpClient" Version="1.4.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
<PackageReference Include="coverlet.collector" Version="3.2.0" />
</ItemGroup>

<ItemGroup>
Expand Down
175 changes: 144 additions & 31 deletions src/SchematicHQ.Client.Test/TestCache.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using NUnit.Framework;
using System;
using System.Threading.Tasks;
using System.Data.SqlTypes;

#nullable enable

Expand All @@ -9,53 +8,167 @@ namespace SchematicHQ.Client.Test
[TestFixture]
public class TestCache
{
static IEnumerable<TestCaseData> SeTAndGetTestCases
{
get
{
yield return new TestCaseData(new LocalCache<bool>(), true).SetName("TestSetAndGet_Boolean");
yield return new TestCaseData(new LocalCache<string>(), "test_string").SetName("TestSetAndGet_string");
yield return new TestCaseData(new LocalCache<List<string>>(), new List<string> { "test_string1", "test_string2" }).SetName("TestSetAndGet_RefType");
}
}

[Test, TestCaseSource(nameof(SeTAndGetTestCases))]
public void Get_ReturnsSetValue<T>(ICacheProvider<T> cache, T value)
{
string key = "test_key";

cache.Set(key: key, val: value);
var result = cache.Get(key);

Assert.That(result, Is.EqualTo(value));
}

[Test]
public void Test_DefaultTTL()
{
LocalCache<bool?> cacheProvider = new LocalCache<bool?>(maxItems: 1);
bool? expectedResult = true;
var key = "test_key";

cacheProvider.Set(key: key, val: expectedResult);
var existingResult = cacheProvider.Get(key);
Thread.Sleep(LocalCache<bool>.DEFAULT_CACHE_TTL + TimeSpan.FromMilliseconds(5));
var evictedResult = cacheProvider.Get(key);

Assert.That(existingResult, Is.EqualTo(expectedResult));
Assert.That(evictedResult, Is.Null);
}

[Test]
public void TestCacheGetAndSet()
public void Test_DefaultCapacity()
{
var cache = new LocalCache<string>(maxItems: 1000, ttl: 5000);
LocalCache<int?> cacheProvider = new LocalCache<int?>(ttl: TimeSpan.FromMinutes(10));
int? expectedResult = -1;
var key = "test_key";

cache.Set("key1", "value1");
Assert.AreEqual("value1", cache.Get("key1"));
cacheProvider.Set(key: key, val: expectedResult);
foreach (int i in Enumerable.Range(1, LocalCache<bool>.DEFAULT_CACHE_CAPACITY - 1))
{
cacheProvider.Set(key: i.ToString(), val: i);

cache.Set("key2", "value2", ttlOverride: 1);
Assert.AreEqual("value2", cache.Get("key2"));
Assert.That(cacheProvider.Get(key: key), Is.EqualTo(expectedResult));
}
cacheProvider.Set(key: "new_key", val: -2);
var evictedResult = cacheProvider.Get(1.ToString());

// Wait for the TTL to expire
Task.Delay(TimeSpan.FromSeconds(2)).Wait();
Assert.IsNull(cache.Get("key2"));
Assert.That(cacheProvider.Get(key: key), Is.EqualTo(expectedResult));
Assert.That(cacheProvider.Get(key: "new_key"), Is.EqualTo(-2));
Assert.That(evictedResult, Is.Null);
}

[Test]
public void TestCacheEviction()
public void Test_NotExistentKeyReturnsDefaultValue()
{
var cache = new LocalCache<string>(maxItems: 2, ttl: 5000);
LocalCache<INullable> cacheProvider = new LocalCache<INullable>(maxItems: 1);
Assert.That(cacheProvider.Get("non_existent_key"), Is.Null);
}

[Test]
public void Test_ConcurrentAccess()
{
int numberOfThreads = 10;
int cacheCapacity = 30;
LocalCache<int?> cacheProvider = new LocalCache<int?>(maxItems: cacheCapacity, ttl: TimeSpan.FromHours(5));
var tasks = new List<Task>();
var countdownEvent = new CountdownEvent(1);

for (int t = 0; t < numberOfThreads; t++)
{
int start = t * cacheCapacity + 1;
int end = start + cacheCapacity - 1;

tasks.Add(Task.Run(() =>
{
countdownEvent.Wait();
for (int i = start; i <= end; i++)
{
cacheProvider.Set(i.ToString(), i);
}
}));
}

cache.Set("key1", "a");
cache.Set("key2", "b");
countdownEvent.Signal();
Task.WaitAll(tasks.ToArray());

// Access key1, making it more recently used than key2
Assert.AreEqual("a", cache.Get("key1"));
List<int> cacheHitsIndices = new List<int>();

for (int i = 1; i <= numberOfThreads * cacheCapacity; i++)
{
if (cacheProvider.Get(i.ToString()) == i)
{
cacheHitsIndices.Add(i);
}
}

Assert.That(cacheHitsIndices.Count, Is.EqualTo(cacheCapacity));
Assert.That(
cacheHitsIndices[cacheCapacity - 1] - cacheHitsIndices[0] + 1,
Is.Not.EqualTo(cacheCapacity)
);
}

[Test]
public void Test_TTLOverride()
{
LocalCache<int?> cacheProvider = new LocalCache<int?>(maxItems: 1000, ttl: TimeSpan.FromHours(5));
var tasks = new List<Task>();
var countdownEvent = new CountdownEvent(1);
string key = "test_key";
int expectedValue = 5;
TimeSpan ttlOverride = TimeSpan.FromSeconds(3);

// Adding a new key should evict the least recently used key,
// which will now be key2
cache.Set("key3", "c");
tasks.Add(Task.Run(() =>
{
countdownEvent.Wait();
cacheProvider.Set(key: key, val: expectedValue, ttlOverride: ttlOverride);
}));
tasks.Add(Task.Run(() =>
{
countdownEvent.Wait();
Thread.Sleep(1000);
Assert.That(cacheProvider.Get(key), Is.EqualTo(expectedValue));
}));
tasks.Add(Task.Run(() =>
{
countdownEvent.Wait();
Thread.Sleep(ttlOverride + TimeSpan.FromMilliseconds(1));
Assert.That(cacheProvider.Get(key), Is.Null);
}));

Assert.IsNull(cache.Get("key2"));
Assert.AreEqual("a", cache.Get("key1"));
Assert.AreEqual("c", cache.Get("key3"));
countdownEvent.Signal();
Task.WaitAll(tasks.ToArray());
}

[Test]
public void TestCacheExpiration()
public void Test_EvictionByLastAccessed()
{
var cache = new LocalCache<string>(maxItems: 1000, ttl: 1000);
LocalCache<int?> cacheProvider = new LocalCache<int?>(maxItems: 10, ttl: TimeSpan.FromHours(5));
foreach (var i in Enumerable.Range(1, 10))
{
cacheProvider.Set(i.ToString(), i);
}

cache.Set("key1", "value1");
Assert.AreEqual("value1", cache.Get("key1"));
foreach (var i in Enumerable.Range(1, 10))
{
Assert.That(cacheProvider.Get(i.ToString()), Is.EqualTo(i));
}

// Wait for the TTL to expire
Task.Delay(TimeSpan.FromSeconds(2)).Wait();
Assert.IsNull(cache.Get("key1"));
foreach (var I in Enumerable.Range(1, 10))
{
cacheProvider.Set((I + 10).ToString(), -1);
Assert.That(cacheProvider.Get(I.ToString()), Is.Null);
}
}
}
}
}
Loading

0 comments on commit 8c8a508

Please sign in to comment.