diff --git a/MfGames.Locking.sln.DotSettings b/MfGames.Locking.sln.DotSettings index 731bb55..1421790 100644 --- a/MfGames.Locking.sln.DotSettings +++ b/MfGames.Locking.sln.DotSettings @@ -1365,4 +1365,5 @@ using(DataAccessAdapter dataAccessAdapter = new DataAccessAdapter(ConnectionStri True 2.0 InCSharpStatement + True diff --git a/src/MfGames.Locking.Tests/LockAction.cs b/src/MfGames.Locking.Tests/LockAction.cs new file mode 100644 index 0000000..2b8b235 --- /dev/null +++ b/src/MfGames.Locking.Tests/LockAction.cs @@ -0,0 +1,14 @@ +namespace MfGames.Locking.Tests +{ + /// + /// The various states for testing lock logic. + /// + internal enum LockAction + { + BeforeLock, + + InLock, + + AfterLock, + } +} diff --git a/src/MfGames.Locking.Tests/LockTests.cs b/src/MfGames.Locking.Tests/LockTests.cs index 0ef0324..387952b 100644 --- a/src/MfGames.Locking.Tests/LockTests.cs +++ b/src/MfGames.Locking.Tests/LockTests.cs @@ -3,318 +3,223 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using KellermanSoftware.CompareNetObjects; + using Xunit; -using Xunit.Abstractions; +using Xunit.Sdk; namespace MfGames.Locking.Tests { public class LockTests { - public static readonly TimeSpan ProcessTime = - TimeSpan.FromMilliseconds(50); - - private readonly List events; - private readonly ReaderWriterLockSlim locker; - private readonly ITestOutputHelper output; + private readonly List<(int, LockAction)> sequenceRecord; - private readonly DateTime start; + private readonly object syncLock; - public LockTests(ITestOutputHelper output) + public LockTests() { - this.output = output; - this.start = DateTime.UtcNow; + this.syncLock = new object(); + this.sequenceRecord = new List<(int, LockAction)>(); this.locker = new ReaderWriterLockSlim(); - this.events = new List(); - } - - private TimeSpan Elapsed => DateTime.UtcNow - this.start; - - [Fact] - public void BasicDataGatheringWorks() - { - this.Report(ProcessTime, 1); - this.Verify(); } [Fact] public void ReadBlocksWrite() { Task.WaitAll( - Task.Run( - () => this.ReportInReadLock( - ProcessTime * 0, - ProcessTime * 3, - 1)), - Task.Run( - () => this.ReportInWriteLock( - ProcessTime * 1, - ProcessTime * 1, - 2))); + Task.Run(() => this.TestRead(1, 5, 1)), + Task.Run(() => this.TestWrite(2, 2, 2))); - this.Verify(); + this.VerifySequenceRecord( + (1, LockAction.BeforeLock), + (1, LockAction.InLock), + (2, LockAction.BeforeLock), + (1, LockAction.AfterLock), + (2, LockAction.InLock), + (2, LockAction.AfterLock)); } [Fact] public void ReadsDoNotBlockReads() { Task.WaitAll( - Task.Run( - () => this.ReportInReadLock( - ProcessTime, - ProcessTime * 5, - 3)), - Task.Run( - () => this.ReportInReadLock( - ProcessTime * 2, - ProcessTime, - 1)), - Task.Run( - () => this.ReportInReadLock( - ProcessTime * 2, - ProcessTime * 2, - 2))); + Task.Run(() => this.TestRead(1, 7, 1)), + Task.Run(() => this.TestRead(2, 1, 2)), + Task.Run(() => this.TestRead(4, 1, 3))); - this.Verify(); - } - - [Fact] - public void ThreadedDataGatheringWorks() - { - Task.WaitAll( - Task.Run( - () => this.ReportInReadLock(ProcessTime, ProcessTime, 1))); - - this.Verify(); + 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 void UpgradableBlocksUpgradable() { Task.WaitAll( - Task.Run( - () => this.ReportInUpgradableLock( - ProcessTime, - ProcessTime * 5, - 1)), - Task.Run( - () => this.ReportInUpgradableLock( - ProcessTime * 3, - ProcessTime, - 2)), - Task.Run( - () => this.ReportInUpgradableLock( - ProcessTime * 2, - ProcessTime * 2, - 3))); + Task.Run(() => this.TestUpgradable(1, 6, 1)), + Task.Run(() => this.TestUpgradable(2, 2, 2)), + Task.Run(() => this.TestUpgradable(4, 1, 3))); - this.Verify(); + 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 void UpgradableDoesNotBlockReads() { Task.WaitAll( - Task.Run( - () => this.ReportInUpgradableLock( - ProcessTime, - ProcessTime * 5, - 3)), - Task.Run( - () => this.ReportInReadLock( - ProcessTime * 2, - ProcessTime, - 1)), - Task.Run( - () => this.ReportInReadLock( - ProcessTime * 2, - ProcessTime * 2, - 2))); + Task.Run(() => this.TestUpgradable(1, 6, 1)), + Task.Run(() => this.TestRead(2, 1, 2)), + Task.Run(() => this.TestRead(4, 1, 3))); - this.Verify(); + 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 void WriteBlockRead() { Task.WaitAll( - Task.Run( - () => this.ReportInWriteLock( - ProcessTime, - ProcessTime * 3, - 1)), - Task.Run( - () => this.ReportInReadLock( - ProcessTime * 2, - ProcessTime, - 2))); + Task.Run(() => this.TestWrite(1, 6, 1)), + Task.Run(() => this.TestRead(2, 1, 2))); - this.Verify(); + this.VerifySequenceRecord( + (1, LockAction.BeforeLock), + (1, LockAction.InLock), + (2, LockAction.BeforeLock), + (1, LockAction.AfterLock), + (2, LockAction.InLock), + (2, LockAction.AfterLock)); } [Fact] public void WriteBlockReads() { Task.WaitAll( - Task.Run( - () => this.ReportInWriteLock( - ProcessTime, - ProcessTime * 5, - 1)), - Task.Run( - () => this.ReportInReadLock( - ProcessTime * 2, - ProcessTime, - 2)), - Task.Run( - () => this.ReportInReadLock( - ProcessTime * 2, - ProcessTime * 2, - 3))); + Task.Run(() => this.TestWrite(1, 6, 1)), + Task.Run(() => this.TestRead(2, 1, 2))); - this.Verify(); + this.VerifySequenceRecord( + (1, LockAction.BeforeLock), + (1, LockAction.InLock), + (2, LockAction.BeforeLock), + (1, LockAction.AfterLock), + (2, LockAction.InLock), + (2, LockAction.AfterLock)); } - private void AddEvent(int sequence) + private void RecordSequenceAction(int sequence, LockAction action) { - // This is a monitor lock on the slim, just to give us a second lock. - lock (this.locker) + lock (this.syncLock) { - this.output.WriteLine( - this.Elapsed + ": " + sequence + " => adding sequence"); - this.events.Add(sequence); + this.sequenceRecord.Add((sequence, action)); } } - /// - /// Waits a short period of time and then injects the sequence into the event list. - /// - private void Report( - TimeSpan processTime, + private void Test( + Func getLock, + int settleTocks, + int waitTocks, int sequence) { - this.output.WriteLine( - this.Elapsed + ": " + sequence + " => starting process"); - Thread.Sleep(processTime); - this.output.WriteLine( - this.Elapsed + ": " + sequence + " => finished process"); - this.AddEvent(sequence); - } + Thread.Sleep(50 * settleTocks); - private void ReportInReadLock( - TimeSpan settleTime, - TimeSpan processTime, - int sequence) - { - this.output.WriteLine( - this.Elapsed - + ": " - + sequence - + " => entering read lock: " - + sequence); + this.RecordSequenceAction(sequence, LockAction.BeforeLock); - Thread.Sleep(settleTime); - - this.output.WriteLine( - this.Elapsed - + ": " - + sequence - + " => locking read lock: " - + sequence); - - using (new ReadLock(this.locker)) + using (getLock(this.locker)) { - this.output.WriteLine( - this.Elapsed - + ": " - + sequence - + " => starting read lock: " - + sequence); + Thread.Sleep(10); - this.Report(processTime, sequence); + this.RecordSequenceAction(sequence, LockAction.InLock); + + Thread.Sleep(50 * waitTocks); + + this.RecordSequenceAction(sequence, LockAction.AfterLock); } } - private void ReportInUpgradableLock( - TimeSpan settleTime, - TimeSpan processTime, + private void TestRead( + int settleTocks, + int waitTocks, int sequence) { - this.output.WriteLine( - this.Elapsed - + ": " - + sequence - + " => entering upgradable lock: " - + sequence); - - Thread.Sleep(settleTime); - - this.output.WriteLine( - this.Elapsed - + ": " - + sequence - + " => locking upgradable lock: " - + sequence); - - using (new UpgradableLock(this.locker)) - { - this.output.WriteLine( - this.Elapsed - + ": " - + sequence - + " => starting upgradable lock: " - + sequence); - - this.Report(processTime, sequence); - } + this.Test( + x => new ReadLock(x), + settleTocks, + waitTocks, + sequence); } - private void ReportInWriteLock( - TimeSpan settleTime, - TimeSpan processTime, + private void TestUpgradable( + int settleTocks, + int waitTocks, int sequence) { - this.output.WriteLine( - this.Elapsed - + ": " - + sequence - + " => entering write lock: " - + sequence); - - Thread.Sleep(settleTime); - - this.output.WriteLine( - this.Elapsed - + ": " - + sequence - + " => locking write lock: " - + sequence); - - using (new WriteLock(this.locker)) - { - this.output.WriteLine( - this.Elapsed - + ": " - + sequence - + " => starting write lock: " - + sequence); - - this.Report(processTime, sequence); - } + this.Test( + x => new UpgradableLock(x), + settleTocks, + waitTocks, + sequence); } - /// - /// Verifies the sequence of events. - /// - private void Verify() + private void TestWrite( + int settleTocks, + int waitTocks, + int sequence) { - this.output.WriteLine( - "Final Sequence: " + string.Join(", ", this.events)); + this.Test( + x => new WriteLock(x), + settleTocks, + waitTocks, + sequence); + } - for (int i = 1; i <= this.events.Count; i++) + private void VerifySequenceRecord( + params (int, LockAction BeforeLock)[] values) + { + lock (this.syncLock) { - Assert.Equal(i, this.events[i - 1]); + var compareLogic = new CompareLogic + { + Config = + { + IgnoreObjectTypes = true, + MaxDifferences = 2, + } + }; + ComparisonResult result = compareLogic.Compare( + values, + this.sequenceRecord); + + if (!result.AreEqual) + { + throw new XunitException(result.DifferencesString); + } } } } diff --git a/src/MfGames.Locking.Tests/MfGames.Locking.Tests.csproj b/src/MfGames.Locking.Tests/MfGames.Locking.Tests.csproj index 3388d22..5acc94e 100644 --- a/src/MfGames.Locking.Tests/MfGames.Locking.Tests.csproj +++ b/src/MfGames.Locking.Tests/MfGames.Locking.Tests.csproj @@ -1,12 +1,12 @@ - netcoreapp2.1 - + net5 false + all