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();
}
}
}
}