using System; using System.Threading; using UnitTestSharp; namespace Nona.UnitTests { public class MessageQueueTests : TestFixture { public struct Message { public int Sequence; public int Check; } public static Message Make(int sequence) { return new Message { Sequence = sequence, Check = sequence * 3 }; } public class CtorTests : TestFixture { public void AcceptsASegmentSizeOfOne() { var queue = new MessageQueue(segmentSize: 1); queue.Enqueue(Make(1)); CheckTrue(queue.TryDequeue(out Message value)); CheckEqual(1, value.Sequence); } public void ZeroSegmentSize_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new MessageQueue(segmentSize: 0); } public void NegativeSegmentSize_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new MessageQueue(segmentSize: -1); } } public class EnqueueTests : TestFixture { public void NeverRefusesAValue() { var queue = new MessageQueue(segmentSize: 2); for (int sequence = 1; sequence <= 100; ++sequence) { queue.Enqueue(Make(sequence)); } for (int sequence = 1; sequence <= 100; ++sequence) { CheckTrue(queue.TryDequeue(out Message value)); CheckEqual(sequence, value.Sequence); CheckEqual(sequence * 3, value.Check); } CheckFalse(queue.TryDequeue(out Message _)); } public void ReusesDrainedSegmentsWhenTheQueueGrowsAgain() { var queue = new MessageQueue(segmentSize: 2); int next = 1; int expected = 1; for (int round = 0; round < 5; ++round) { for (int i = 0; i < 7; ++i) { queue.Enqueue(Make(next++)); } for (int i = 0; i < 7; ++i) { queue.TryDequeue(out Message value); CheckEqual(expected++, value.Sequence); } } CheckFalse(queue.TryDequeue(out Message _)); } } public class TryDequeueTests : TestFixture { public void WhenNewlyMade_ReturnsFalseWithADefaultValue() { var queue = new MessageQueue(segmentSize: 2); CheckFalse(queue.TryDequeue(out Message value)); CheckEqual(0, value.Sequence); CheckEqual(0, value.Check); } public void WhenDrainedExactlyToASegmentEnd_ReturnsFalseThenFindsLaterValues() { var queue = new MessageQueue(segmentSize: 2); queue.Enqueue(Make(1)); queue.Enqueue(Make(2)); queue.TryDequeue(out Message _); queue.TryDequeue(out Message _); CheckFalse(queue.TryDequeue(out Message _)); queue.Enqueue(Make(3)); CheckTrue(queue.TryDequeue(out Message value)); CheckEqual(3, value.Sequence); } public void KeepsOrderWhenEnqueuingAndDequeuingAlternate() { var queue = new MessageQueue(segmentSize: 3); for (int sequence = 1; sequence <= 20; ++sequence) { queue.Enqueue(Make(sequence)); CheckTrue(queue.TryDequeue(out Message value)); CheckEqual(sequence, value.Sequence); CheckFalse(queue.TryDequeue(out Message _)); } } public void KeepsOrderWhenSeveralSegmentsDrainBeforeTheProducerReturns() { var queue = new MessageQueue(segmentSize: 2); for (int sequence = 1; sequence <= 6; ++sequence) { queue.Enqueue(Make(sequence)); } for (int sequence = 1; sequence <= 6; ++sequence) { queue.TryDequeue(out Message value); CheckEqual(sequence, value.Sequence); } for (int sequence = 7; sequence <= 12; ++sequence) { queue.Enqueue(Make(sequence)); } for (int sequence = 7; sequence <= 12; ++sequence) { queue.TryDequeue(out Message value); CheckEqual(sequence, value.Sequence); } } } public class ThreadedTests : TestFixture { public void EveryValueArrivesOnceWholeAndInOrder() { const int MessageCount = 20000; var queue = new MessageQueue(segmentSize: 8); var producer = new Thread(() => { for (int sequence = 1; sequence <= MessageCount; ++sequence) { queue.Enqueue(Make(sequence)); } }); int wrong = 0; int expected = 1; producer.Start(); while (expected <= MessageCount) { if (!queue.TryDequeue(out Message message)) { Thread.Yield(); continue; } if (message.Sequence != expected || message.Check != expected * 3) { ++wrong; } ++expected; } producer.Join(); CheckEqual(0, wrong); } } public class AllocationTests : TestFixture { private MessageQueue queue; public override void TestSetup() { queue = new MessageQueue(segmentSize: 2); for (int i = 0; i < 8; ++i) { queue.Enqueue(Make(i)); queue.TryDequeue(out Message _); } } [AllocationTest] public void EnqueuingAndDequeuingAcrossSegments_AllocateNothingOnceWarm() { queue.Enqueue(Make(1)); queue.TryDequeue(out Message _); CheckNoAllocations(); } } } }