mfgames-cil/tests/MfGames.Locking.Tests/LockTests.cs

211 lines
5.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using KellermanSoftware.CompareNetObjects;
using Xunit;
using Xunit.Sdk;
namespace MfGames.Locking.Tests;
public class LockTests
{
private readonly ReaderWriterLockSlim locker;
private readonly List<(int, LockAction)> sequenceRecord;
private readonly object syncLock;
public LockTests()
{
this.syncLock = new object();
this.sequenceRecord = new List<(int, LockAction)>();
this.locker = new ReaderWriterLockSlim();
}
[Fact]
public async Task ReadBlocksWrite()
{
await Task.WhenAll(
Task.Run(() => this.TestRead(1, 5, 1)),
Task.Run(() => this.TestWrite(2, 2, 2))
);
this.VerifySequenceRecord(
(1, LockAction.BeforeLock),
(1, LockAction.InLock),
(2, LockAction.BeforeLock),
(1, LockAction.AfterLock),
(2, LockAction.InLock),
(2, LockAction.AfterLock)
);
}
[Fact]
public async Task ReadsDoNotBlockReads()
{
await Task.WhenAll(
Task.Run(() => this.TestRead(1, 8, 1)),
Task.Run(() => this.TestRead(2, 1, 2)),
Task.Run(() => this.TestRead(4, 1, 3))
);
this.VerifySequenceRecord(
(1, LockAction.BeforeLock),
(1, LockAction.InLock),
(2, LockAction.BeforeLock),
(2, LockAction.InLock),
(2, LockAction.AfterLock),
(3, LockAction.BeforeLock),
(3, LockAction.InLock),
(3, LockAction.AfterLock),
(1, LockAction.AfterLock)
);
}
[Fact]
public async Task UpgradableBlocksUpgradable()
{
await Task.WhenAll(
Task.Run(() => this.TestUpgradable(1, 10, 1)),
Task.Run(() => this.TestUpgradable(2, 2, 2)),
Task.Run(() => this.TestUpgradable(5, 1, 3))
);
this.VerifySequenceRecord(
(1, LockAction.BeforeLock),
(1, LockAction.InLock),
(2, LockAction.BeforeLock),
(3, LockAction.BeforeLock),
(1, LockAction.AfterLock),
(2, LockAction.InLock),
(2, LockAction.AfterLock),
(3, LockAction.InLock),
(3, LockAction.AfterLock)
);
}
[Fact]
public async Task UpgradableDoesNotBlockReads()
{
await Task.WhenAll(
Task.Run(() => this.TestUpgradable(1, 6, 1)),
Task.Run(() => this.TestRead(2, 1, 2)),
Task.Run(() => this.TestRead(4, 1, 3))
);
this.VerifySequenceRecord(
(1, LockAction.BeforeLock),
(1, LockAction.InLock),
(2, LockAction.BeforeLock),
(2, LockAction.InLock),
(2, LockAction.AfterLock),
(3, LockAction.BeforeLock),
(3, LockAction.InLock),
(3, LockAction.AfterLock),
(1, LockAction.AfterLock)
);
}
[Fact]
public async Task WriteBlockRead()
{
await Task.WhenAll(
Task.Run(() => this.TestWrite(1, 6, 1)),
Task.Run(() => this.TestRead(2, 1, 2))
);
this.VerifySequenceRecord(
(1, LockAction.BeforeLock),
(1, LockAction.InLock),
(2, LockAction.BeforeLock),
(1, LockAction.AfterLock),
(2, LockAction.InLock),
(2, LockAction.AfterLock)
);
}
[Fact]
public async Task WriteBlockReads()
{
await Task.WhenAll(
Task.Run(() => this.TestWrite(1, 6, 1)),
Task.Run(() => this.TestRead(2, 1, 2))
);
this.VerifySequenceRecord(
(1, LockAction.BeforeLock),
(1, LockAction.InLock),
(2, LockAction.BeforeLock),
(1, LockAction.AfterLock),
(2, LockAction.InLock),
(2, LockAction.AfterLock)
);
}
private void RecordSequenceAction(int sequence, LockAction action)
{
lock (this.syncLock)
{
this.sequenceRecord.Add((sequence, action));
}
}
private void Test(
Func<ReaderWriterLockSlim, IDisposable> getLock,
int settleTocks,
int waitTocks,
int sequence
)
{
const int TocksInMilliseconds = 200;
Thread.Sleep(TocksInMilliseconds * settleTocks);
this.RecordSequenceAction(sequence, LockAction.BeforeLock);
using (getLock(this.locker))
{
Thread.Sleep(100);
this.RecordSequenceAction(sequence, LockAction.InLock);
Thread.Sleep(TocksInMilliseconds * waitTocks);
this.RecordSequenceAction(sequence, LockAction.AfterLock);
}
}
private void TestRead(int settleTocks, int waitTocks, int sequence)
{
this.Test(x => new ReadLock(x), settleTocks, waitTocks, sequence);
}
private void TestUpgradable(int settleTocks, int waitTocks, int sequence)
{
this.Test(x => new UpgradableLock(x), settleTocks, waitTocks, sequence);
}
private void TestWrite(int settleTocks, int waitTocks, int sequence)
{
this.Test(x => new WriteLock(x), settleTocks, waitTocks, sequence);
}
private void VerifySequenceRecord(params (int, LockAction BeforeLock)[] values)
{
lock (this.syncLock)
{
var compareLogic = new CompareLogic
{
Config = { IgnoreObjectTypes = true, MaxDifferences = 2, }
};
ComparisonResult result = compareLogic.Compare(values, this.sequenceRecord);
if (!result.AreEqual)
{
throw new XunitException(result.DifferencesString);
}
}
}
}