using System; using System.Collections.Generic; using System.Threading; using UnitTestSharp; namespace Nona.UnitTests { public class TripleBufferTests : TestFixture { public class Frame { public long Sequence; public long Check; } public static TripleBuffer Create() { return new TripleBuffer(() => new Frame()); } public static bool Same(Frame left, Frame right) { return ReferenceEquals(left, right); } public class CtorTests : TestFixture { public void MakesThreeElements() { int made = 0; new TripleBuffer(() => { ++made; return new Frame(); }); CheckEqual(3, made); } public void NullFactory_Throws() { CheckThrow(typeof(ArgumentNullException)); new TripleBuffer(null); } public void AFactoryReturningNull_Throws() { CheckThrow(typeof(InvalidOperationException)); new TripleBuffer(() => null); } public void AFactoryReturningOneObjectTwice_Throws() { var shared = new Frame(); CheckThrow(typeof(InvalidOperationException)); new TripleBuffer(() => shared); } public void AFactoryRepeatingAnEarlierObject_Throws() { var made = new List(); var buffer = new Func(() => { Frame frame = made.Count == 2 ? made[0] : new Frame(); made.Add(frame); return frame; }); CheckThrow(typeof(InvalidOperationException)); new TripleBuffer(buffer); } public void StartsWithTheBackAndFrontApart() { TripleBuffer buffer = Create(); CheckFalse(Same(buffer.Back, buffer.Front)); } } public class TryAcquireLatestTests : TestFixture { public void BeforeAnyPublish_ReturnsFalseAndLeavesTheFront() { TripleBuffer buffer = Create(); Frame front = buffer.Front; CheckFalse(buffer.TryAcquireLatest()); Check(Same(front, buffer.Front)); } public void AfterAPublish_ReturnsTrueAndFrontIsWhatWasPublished() { TripleBuffer buffer = Create(); Frame published = buffer.Back; published.Sequence = 7; buffer.Publish(); CheckTrue(buffer.TryAcquireLatest()); Check(Same(published, buffer.Front)); CheckEqual(7L, buffer.Front.Sequence); } public void ReturnsFalseAgainUntilThereIsAnotherPublish() { TripleBuffer buffer = Create(); buffer.Publish(); buffer.TryAcquireLatest(); Frame front = buffer.Front; CheckFalse(buffer.TryAcquireLatest()); Check(Same(front, buffer.Front)); buffer.Publish(); CheckTrue(buffer.TryAcquireLatest()); } public void WithSeveralPublishes_SkipsToTheNewest() { TripleBuffer buffer = Create(); buffer.Back.Sequence = 1; buffer.Publish(); buffer.Back.Sequence = 2; buffer.Publish(); buffer.Back.Sequence = 3; buffer.Publish(); CheckTrue(buffer.TryAcquireLatest()); CheckEqual(3L, buffer.Front.Sequence); CheckFalse(buffer.TryAcquireLatest()); } public void TakesEachPublishWhenKeepingUp() { TripleBuffer buffer = Create(); for (long sequence = 1; sequence <= 5; ++sequence) { buffer.Back.Sequence = sequence; buffer.Publish(); CheckTrue(buffer.TryAcquireLatest()); CheckEqual(sequence, buffer.Front.Sequence); } } } public class PublishTests : TestFixture { public void MovesTheProducerOntoAnotherElement() { TripleBuffer buffer = Create(); Frame filled = buffer.Back; buffer.Publish(); CheckFalse(Same(filled, buffer.Back)); } public void NeverTouchesTheElementTheConsumerHolds() { TripleBuffer buffer = Create(); buffer.Back.Sequence = 42; buffer.Publish(); buffer.TryAcquireLatest(); Frame front = buffer.Front; for (long sequence = 100; sequence < 110; ++sequence) { CheckFalse(Same(front, buffer.Back)); buffer.Back.Sequence = sequence; buffer.Publish(); } CheckEqual(42L, front.Sequence); Check(Same(front, buffer.Front)); } public void ReusesAnElementNobodyTook() { TripleBuffer buffer = Create(); Frame first = buffer.Back; buffer.Publish(); buffer.Publish(); Check(Same(first, buffer.Back)); } public void NeverHandsTheConsumerAnElementTheProducerHolds() { TripleBuffer buffer = Create(); for (int step = 0; step < 12; ++step) { buffer.Publish(); if (step % 3 != 0) { buffer.TryAcquireLatest(); } CheckFalse(Same(buffer.Back, buffer.Front)); } } } public class ThreadedTests : TestFixture { public void AConsumerSeesOnlyWholeFramesAndNeverGoesBackwards() { const int FrameCount = 20000; TripleBuffer buffer = Create(); var producer = new Thread(() => { for (long sequence = 1; sequence <= FrameCount; ++sequence) { Frame frame = buffer.Back; frame.Sequence = sequence; frame.Check = sequence * 3; buffer.Publish(); } }); int torn = 0; int outOfOrder = 0; long last = 0; producer.Start(); while (last < FrameCount) { if (!buffer.TryAcquireLatest()) { Thread.Yield(); continue; } Frame frame = buffer.Front; if (frame.Check != frame.Sequence * 3) { ++torn; } if (frame.Sequence <= last) { ++outOfOrder; } last = frame.Sequence; } producer.Join(); CheckEqual(0, torn); CheckEqual(0, outOfOrder); } } public class AllocationTests : TestFixture { private TripleBuffer buffer; public override void TestSetup() { buffer = Create(); } [AllocationTest] public void PublishingAndAcquiring_AllocateNothing() { buffer.Back.Sequence = 1; buffer.Publish(); buffer.TryAcquireLatest(); CheckNoAllocations(); } } } }