test: cleaning up tests for locking

This commit is contained in:
Dylan R. E. Moonfire 2021-01-21 17:33:53 -06:00
parent 54d05c8485
commit 28898dbaff
4 changed files with 153 additions and 233 deletions

View file

@ -1365,4 +1365,5 @@ using(DataAccessAdapter dataAccessAdapter = new DataAccessAdapter(ConnectionStri
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F87CBA43E9CDCC41A45B39A2A2A25764/Scope/=2C285F182AC98D44B0B4F29D4D2149EC/@KeyIndexDefined">True</s:Boolean> <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F87CBA43E9CDCC41A45B39A2A2A25764/Scope/=2C285F182AC98D44B0B4F29D4D2149EC/@KeyIndexDefined">True</s:Boolean>
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F87CBA43E9CDCC41A45B39A2A2A25764/Scope/=2C285F182AC98D44B0B4F29D4D2149EC/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F87CBA43E9CDCC41A45B39A2A2A25764/Scope/=2C285F182AC98D44B0B4F29D4D2149EC/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F87CBA43E9CDCC41A45B39A2A2A25764/Scope/=2C285F182AC98D44B0B4F29D4D2149EC/Type/@EntryValue">InCSharpStatement</s:String> <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F87CBA43E9CDCC41A45B39A2A2A25764/Scope/=2C285F182AC98D44B0B4F29D4D2149EC/Type/@EntryValue">InCSharpStatement</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tocks/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary> </wpf:ResourceDictionary>

View file

@ -0,0 +1,14 @@
namespace MfGames.Locking.Tests
{
/// <summary>
/// The various states for testing lock logic.
/// </summary>
internal enum LockAction
{
BeforeLock,
InLock,
AfterLock,
}
}

View file

@ -3,318 +3,223 @@ using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using KellermanSoftware.CompareNetObjects;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Sdk;
namespace MfGames.Locking.Tests namespace MfGames.Locking.Tests
{ {
public class LockTests public class LockTests
{ {
public static readonly TimeSpan ProcessTime =
TimeSpan.FromMilliseconds(50);
private readonly List<int> events;
private readonly ReaderWriterLockSlim locker; 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.syncLock = new object();
this.start = DateTime.UtcNow; this.sequenceRecord = new List<(int, LockAction)>();
this.locker = new ReaderWriterLockSlim(); this.locker = new ReaderWriterLockSlim();
this.events = new List<int>();
}
private TimeSpan Elapsed => DateTime.UtcNow - this.start;
[Fact]
public void BasicDataGatheringWorks()
{
this.Report(ProcessTime, 1);
this.Verify();
} }
[Fact] [Fact]
public void ReadBlocksWrite() public void ReadBlocksWrite()
{ {
Task.WaitAll( Task.WaitAll(
Task.Run( Task.Run(() => this.TestRead(1, 5, 1)),
() => this.ReportInReadLock( Task.Run(() => this.TestWrite(2, 2, 2)));
ProcessTime * 0,
ProcessTime * 3,
1)),
Task.Run(
() => this.ReportInWriteLock(
ProcessTime * 1,
ProcessTime * 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] [Fact]
public void ReadsDoNotBlockReads() public void ReadsDoNotBlockReads()
{ {
Task.WaitAll( Task.WaitAll(
Task.Run( Task.Run(() => this.TestRead(1, 7, 1)),
() => this.ReportInReadLock( Task.Run(() => this.TestRead(2, 1, 2)),
ProcessTime, Task.Run(() => this.TestRead(4, 1, 3)));
ProcessTime * 5,
3)),
Task.Run(
() => this.ReportInReadLock(
ProcessTime * 2,
ProcessTime,
1)),
Task.Run(
() => this.ReportInReadLock(
ProcessTime * 2,
ProcessTime * 2,
2)));
this.Verify(); this.VerifySequenceRecord(
} (1, LockAction.BeforeLock),
(1, LockAction.InLock),
[Fact] (2, LockAction.BeforeLock),
public void ThreadedDataGatheringWorks() (2, LockAction.InLock),
{ (2, LockAction.AfterLock),
Task.WaitAll( (3, LockAction.BeforeLock),
Task.Run( (3, LockAction.InLock),
() => this.ReportInReadLock(ProcessTime, ProcessTime, 1))); (3, LockAction.AfterLock),
(1, LockAction.AfterLock));
this.Verify();
} }
[Fact] [Fact]
public void UpgradableBlocksUpgradable() public void UpgradableBlocksUpgradable()
{ {
Task.WaitAll( Task.WaitAll(
Task.Run( Task.Run(() => this.TestUpgradable(1, 6, 1)),
() => this.ReportInUpgradableLock( Task.Run(() => this.TestUpgradable(2, 2, 2)),
ProcessTime, Task.Run(() => this.TestUpgradable(4, 1, 3)));
ProcessTime * 5,
1)),
Task.Run(
() => this.ReportInUpgradableLock(
ProcessTime * 3,
ProcessTime,
2)),
Task.Run(
() => this.ReportInUpgradableLock(
ProcessTime * 2,
ProcessTime * 2,
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] [Fact]
public void UpgradableDoesNotBlockReads() public void UpgradableDoesNotBlockReads()
{ {
Task.WaitAll( Task.WaitAll(
Task.Run( Task.Run(() => this.TestUpgradable(1, 6, 1)),
() => this.ReportInUpgradableLock( Task.Run(() => this.TestRead(2, 1, 2)),
ProcessTime, Task.Run(() => this.TestRead(4, 1, 3)));
ProcessTime * 5,
3)),
Task.Run(
() => this.ReportInReadLock(
ProcessTime * 2,
ProcessTime,
1)),
Task.Run(
() => this.ReportInReadLock(
ProcessTime * 2,
ProcessTime * 2,
2)));
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] [Fact]
public void WriteBlockRead() public void WriteBlockRead()
{ {
Task.WaitAll( Task.WaitAll(
Task.Run( Task.Run(() => this.TestWrite(1, 6, 1)),
() => this.ReportInWriteLock( Task.Run(() => this.TestRead(2, 1, 2)));
ProcessTime,
ProcessTime * 3,
1)),
Task.Run(
() => this.ReportInReadLock(
ProcessTime * 2,
ProcessTime,
2)));
this.Verify(); this.VerifySequenceRecord(
(1, LockAction.BeforeLock),
(1, LockAction.InLock),
(2, LockAction.BeforeLock),
(1, LockAction.AfterLock),
(2, LockAction.InLock),
(2, LockAction.AfterLock));
} }
[Fact] [Fact]
public void WriteBlockReads() public void WriteBlockReads()
{ {
Task.WaitAll( Task.WaitAll(
Task.Run( Task.Run(() => this.TestWrite(1, 6, 1)),
() => this.ReportInWriteLock( Task.Run(() => this.TestRead(2, 1, 2)));
ProcessTime,
ProcessTime * 5,
1)),
Task.Run(
() => this.ReportInReadLock(
ProcessTime * 2,
ProcessTime,
2)),
Task.Run(
() => this.ReportInReadLock(
ProcessTime * 2,
ProcessTime * 2,
3)));
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.syncLock)
lock (this.locker)
{ {
this.output.WriteLine( this.sequenceRecord.Add((sequence, action));
this.Elapsed + ": " + sequence + " => adding sequence");
this.events.Add(sequence);
} }
} }
/// <summary> private void Test(
/// Waits a short period of time and then injects the sequence into the event list. Func<ReaderWriterLockSlim, IDisposable> getLock,
/// </summary> int settleTocks,
private void Report( int waitTocks,
TimeSpan processTime,
int sequence) int sequence)
{ {
this.output.WriteLine( Thread.Sleep(50 * settleTocks);
this.Elapsed + ": " + sequence + " => starting process");
Thread.Sleep(processTime);
this.output.WriteLine(
this.Elapsed + ": " + sequence + " => finished process");
this.AddEvent(sequence);
}
private void ReportInReadLock( this.RecordSequenceAction(sequence, LockAction.BeforeLock);
TimeSpan settleTime,
TimeSpan processTime,
int sequence)
{
this.output.WriteLine(
this.Elapsed
+ ": "
+ sequence
+ " => entering read lock: "
+ sequence);
Thread.Sleep(settleTime); using (getLock(this.locker))
this.output.WriteLine(
this.Elapsed
+ ": "
+ sequence
+ " => locking read lock: "
+ sequence);
using (new ReadLock(this.locker))
{ {
this.output.WriteLine( Thread.Sleep(10);
this.Elapsed
+ ": "
+ sequence
+ " => starting read lock: "
+ sequence);
this.Report(processTime, sequence); this.RecordSequenceAction(sequence, LockAction.InLock);
Thread.Sleep(50 * waitTocks);
this.RecordSequenceAction(sequence, LockAction.AfterLock);
} }
} }
private void ReportInUpgradableLock( private void TestRead(
TimeSpan settleTime, int settleTocks,
TimeSpan processTime, int waitTocks,
int sequence) int sequence)
{ {
this.output.WriteLine( this.Test(
this.Elapsed x => new ReadLock(x),
+ ": " settleTocks,
+ sequence waitTocks,
+ " => entering upgradable lock: " sequence);
+ 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);
}
} }
private void ReportInWriteLock( private void TestUpgradable(
TimeSpan settleTime, int settleTocks,
TimeSpan processTime, int waitTocks,
int sequence) int sequence)
{ {
this.output.WriteLine( this.Test(
this.Elapsed x => new UpgradableLock(x),
+ ": " settleTocks,
+ sequence waitTocks,
+ " => entering write lock: " sequence);
+ 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);
}
} }
/// <summary> private void TestWrite(
/// Verifies the sequence of events. int settleTocks,
/// </summary> int waitTocks,
private void Verify() int sequence)
{ {
this.output.WriteLine( this.Test(
"Final Sequence: " + string.Join(", ", this.events)); 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);
}
} }
} }
} }

View file

@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>net5</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CompareNETObjects" Version="4.70.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
<PackageReference Include="Roslynator.Analyzers" Version="3.0.0"> <PackageReference Include="Roslynator.Analyzers" Version="3.0.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>