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