test: cleaning up tests for locking
This commit is contained in:
parent
54d05c8485
commit
28898dbaff
4 changed files with 153 additions and 233 deletions
|
@ -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>
|
||||||
|
|
14
src/MfGames.Locking.Tests/LockAction.cs
Normal file
14
src/MfGames.Locking.Tests/LockAction.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
namespace MfGames.Locking.Tests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The various states for testing lock logic.
|
||||||
|
/// </summary>
|
||||||
|
internal enum LockAction
|
||||||
|
{
|
||||||
|
BeforeLock,
|
||||||
|
|
||||||
|
InLock,
|
||||||
|
|
||||||
|
AfterLock,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.RecordSequenceAction(sequence, LockAction.BeforeLock);
|
||||||
this.output.WriteLine(
|
|
||||||
this.Elapsed + ": " + sequence + " => finished process");
|
using (getLock(this.locker))
|
||||||
this.AddEvent(sequence);
|
{
|
||||||
|
Thread.Sleep(10);
|
||||||
|
|
||||||
|
this.RecordSequenceAction(sequence, LockAction.InLock);
|
||||||
|
|
||||||
|
Thread.Sleep(50 * waitTocks);
|
||||||
|
|
||||||
|
this.RecordSequenceAction(sequence, LockAction.AfterLock);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReportInReadLock(
|
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 read lock: "
|
sequence);
|
||||||
+ sequence);
|
|
||||||
|
|
||||||
Thread.Sleep(settleTime);
|
|
||||||
|
|
||||||
this.output.WriteLine(
|
|
||||||
this.Elapsed
|
|
||||||
+ ": "
|
|
||||||
+ sequence
|
|
||||||
+ " => locking read lock: "
|
|
||||||
+ sequence);
|
|
||||||
|
|
||||||
using (new ReadLock(this.locker))
|
|
||||||
{
|
|
||||||
this.output.WriteLine(
|
|
||||||
this.Elapsed
|
|
||||||
+ ": "
|
|
||||||
+ sequence
|
|
||||||
+ " => starting read lock: "
|
|
||||||
+ sequence);
|
|
||||||
|
|
||||||
this.Report(processTime, sequence);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReportInUpgradableLock(
|
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 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 TestWrite(
|
||||||
TimeSpan settleTime,
|
int settleTocks,
|
||||||
TimeSpan processTime,
|
int waitTocks,
|
||||||
int sequence)
|
int sequence)
|
||||||
{
|
{
|
||||||
this.output.WriteLine(
|
this.Test(
|
||||||
this.Elapsed
|
x => new WriteLock(x),
|
||||||
+ ": "
|
settleTocks,
|
||||||
+ sequence
|
waitTocks,
|
||||||
+ " => entering write lock: "
|
sequence);
|
||||||
+ sequence);
|
}
|
||||||
|
|
||||||
Thread.Sleep(settleTime);
|
private void VerifySequenceRecord(
|
||||||
|
params (int, LockAction BeforeLock)[] values)
|
||||||
this.output.WriteLine(
|
|
||||||
this.Elapsed
|
|
||||||
+ ": "
|
|
||||||
+ sequence
|
|
||||||
+ " => locking write lock: "
|
|
||||||
+ sequence);
|
|
||||||
|
|
||||||
using (new WriteLock(this.locker))
|
|
||||||
{
|
{
|
||||||
this.output.WriteLine(
|
lock (this.syncLock)
|
||||||
this.Elapsed
|
|
||||||
+ ": "
|
|
||||||
+ sequence
|
|
||||||
+ " => starting write lock: "
|
|
||||||
+ sequence);
|
|
||||||
|
|
||||||
this.Report(processTime, sequence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Verifies the sequence of events.
|
|
||||||
/// </summary>
|
|
||||||
private void Verify()
|
|
||||||
{
|
{
|
||||||
this.output.WriteLine(
|
var compareLogic = new CompareLogic
|
||||||
"Final Sequence: " + string.Join(", ", this.events));
|
|
||||||
|
|
||||||
for (int i = 1; i <= this.events.Count; i++)
|
|
||||||
{
|
{
|
||||||
Assert.Equal(i, this.events[i - 1]);
|
Config =
|
||||||
|
{
|
||||||
|
IgnoreObjectTypes = true,
|
||||||
|
MaxDifferences = 2,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ComparisonResult result = compareLogic.Compare(
|
||||||
|
values,
|
||||||
|
this.sequenceRecord);
|
||||||
|
|
||||||
|
if (!result.AreEqual)
|
||||||
|
{
|
||||||
|
throw new XunitException(result.DifferencesString);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Reference in a new issue