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 void ReadBlocksWrite() { Task.WaitAll( 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 void ReadsDoNotBlockReads() { Task.WaitAll( 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 void UpgradableBlocksUpgradable() { Task.WaitAll( 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 void UpgradableDoesNotBlockReads() { Task.WaitAll( 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 void WriteBlockRead() { Task.WaitAll( 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 void WriteBlockReads() { Task.WaitAll( 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 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); } } } } }