Files
adas-core/adas-core.Test/Services/InMemoryLockProviderTest.cs
T

122 lines
3.4 KiB
C#

using System.Collections.Concurrent;
using System.Reflection;
using adas_core.Application.Services.Caching;
namespace adas_core.Test.Services;
[TestFixture]
public class InMemoryLockProviderTest
{
private InMemoryLockProvider _provider = null!;
[SetUp]
public void SetUp()
{
_provider = new InMemoryLockProvider();
}
private ConcurrentDictionary<string, SemaphoreSlim> GetLocks()
{
var field = typeof(InMemoryLockProvider)
.GetField("_locks", BindingFlags.NonPublic | BindingFlags.Instance);
return (ConcurrentDictionary<string, SemaphoreSlim>)field!.GetValue(_provider)!;
}
#region TC-50
[Test]
public async Task AcquireAsync_ReturnsTrue_AndCreatesEntryInLocksDictionary()
{
var result = await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1));
Assert.That(result, Is.True);
var locks = GetLocks();
Assert.That(locks.ContainsKey("key"), Is.True);
}
#endregion
#region TC-51
[Test]
public async Task AcquireAsync_ReturnsFalse_WhenSemaphoreOccupiedAndTimeoutExpires()
{
await _provider.AcquireAsync("key", TimeSpan.FromSeconds(5));
var result = await _provider.AcquireAsync("key", TimeSpan.FromMilliseconds(100));
Assert.That(result, Is.False);
await _provider.ReleaseAsync("key");
var locks = GetLocks();
Assert.That(locks["key"].CurrentCount, Is.EqualTo(1));
}
#endregion
#region TC-52
[Test]
public async Task ReleaseAsync_MakesSemaphoreAvailableForNextAcquire()
{
await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1));
await _provider.ReleaseAsync("key");
var result = await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1));
Assert.That(result, Is.True);
var locks = GetLocks();
Assert.That(locks["key"].CurrentCount, Is.EqualTo(0));
await _provider.ReleaseAsync("key");
Assert.That(locks["key"].CurrentCount, Is.EqualTo(1));
}
#endregion
#region TC-53
[Test]
public async Task ReleaseAsync_DoesNotThrow_WhenCalledWithoutPriorAcquire()
{
await _provider.ReleaseAsync("nonexistent-key");
await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1));
await _provider.ReleaseAsync("key");
await _provider.ReleaseAsync("key");
}
#endregion
#region TC-54
[Test]
public async Task AcquireAsync_IsThreadSafe_OnlyOneHolderAtATime_SingleSemaphorePerKey()
{
const string key = "same-key";
const int threadCount = 10;
var acquiredCount = 0;
var currentHolders = 0;
var tasks = Enumerable.Range(0, threadCount).Select(_ => Task.Run(async () =>
{
var acquired = await _provider.AcquireAsync(key, TimeSpan.FromSeconds(10));
Assert.That(acquired, Is.True);
var current = Interlocked.Increment(ref currentHolders);
Assert.That(current, Is.EqualTo(1));
Interlocked.Increment(ref acquiredCount);
await Task.Delay(5);
Interlocked.Decrement(ref currentHolders);
await _provider.ReleaseAsync(key);
})).ToArray();
await Task.WhenAll(tasks);
Assert.That(acquiredCount, Is.EqualTo(threadCount));
var locks = GetLocks();
Assert.That(locks.ContainsKey(key), Is.True);
Assert.That(locks.Keys.Count(k => k == key), Is.EqualTo(1));
}
#endregion
}