using System;
using System.Collections.Generic;
using Amphora;
using UnitTestSharp;
namespace Ariadne.UnitTests
{
///
/// A slot that logs a tag each time it is woken.
///
public class RecordingSlot : WakeSlot
{
private readonly List woken;
private readonly int tag;
public RecordingSlot(List woken, int order, int tag)
{
this.woken = woken;
this.tag = tag;
Order = order;
}
public RecordingSlot(List woken, int order)
: this(woken, order, tag: order)
{
}
public override void Wake()
{
woken.Add(tag);
}
}
public class TriggerTests : TestFixture
{
public static int[] Counting(int count)
{
var returnMe = new int[count];
for (int i = 0; i < count; ++i)
{
returnMe[i] = i;
}
return returnMe;
}
///
/// Returns a wait on for an arc that logs when woken.
///
public static TriggerWait WaitOf(List woken, int order, int tag, bool requiresAll,
params Trigger[] triggers)
{
var returnMe = new TriggerWait(capacity: Math.Max(1, triggers.Length));
returnMe.Begin(new Frame { Wake = new RecordingSlot(woken, order, tag) }, requiresAll);
foreach (Trigger trigger in triggers)
{
returnMe.Add(trigger);
}
return returnMe;
}
public static TriggerWait WaitOf(List woken, int order, params Trigger[] triggers)
{
return WaitOf(woken, order, tag: order, requiresAll: false, triggers: triggers);
}
public class SubscribeTests : TestFixture
{
private List woken;
private Trigger trigger;
public override void TestSetup()
{
woken = new List();
trigger = new Trigger();
}
public void NullSubscription_Throws()
{
CheckThrow(typeof(ArgumentNullException));
trigger.Subscribe(null, wait: 1);
}
public void Subscription_IsCounted()
{
WaitOf(woken, order: 0, trigger);
CheckEqual(1, trigger.SubscriptionCount);
}
public void ManyLiveSubscriptions_AreAllKept()
{
for (int order = 0; order < 100; ++order)
{
WaitOf(woken, order, trigger);
}
CheckEqual(100, trigger.SubscriptionCount);
}
public void SubscriptionsOfWaitsThatBeganAgain_AreSweptWhenTheListFills()
{
TriggerWait wait = WaitOf(woken, order: 0, trigger);
for (int i = 0; i < 1000; ++i)
{
wait.Begin(new Frame { Wake = wait.Slot }, requiresAll: false);
wait.Add(trigger);
}
CheckTrue(trigger.SubscriptionCount <= Trigger.InitialCapacity);
}
public void SubscriptionsOfWaitsThatAreOver_AreSweptWhenTheListFills()
{
for (int i = 0; i < 1000; ++i)
{
WaitOf(woken, order: 0, trigger).Mark(0);
}
CheckTrue(trigger.SubscriptionCount <= Trigger.InitialCapacity);
}
public void SubscriptionsOfDroppedSlots_AreSweptWhenTheListFills()
{
for (int i = 0; i < 1000; ++i)
{
WaitOf(woken, order: 0, trigger).Slot.Dropped = true;
}
CheckTrue(trigger.SubscriptionCount <= Trigger.InitialCapacity);
}
public void SubscriptionsOfUnattachedWaits_AreKeptWhenTheListFills()
{
for (int i = 0; i < 20; ++i)
{
WaitOf(woken, order: 0, trigger).Slot = null;
}
CheckEqual(20, trigger.SubscriptionCount);
}
public void SweepWhenTheListFills_KeepsTheLiveSubscriptions()
{
WaitOf(woken, order: 0, trigger);
TriggerWait restless = WaitOf(woken, order: 1, trigger);
for (int i = 0; i < 1000; ++i)
{
restless.Begin(new Frame { Wake = restless.Slot }, requiresAll: false);
restless.Add(trigger);
}
trigger.Raise();
CheckEqual(new[] { 0, 1 }, woken.ToArray());
}
}
public class SubscriptionAtTests : TestFixture
{
private List woken;
private Trigger trigger;
public override void TestSetup()
{
woken = new List();
trigger = new Trigger();
}
public void ReturnsTheSubscriptionsInTheOrderTheyWereMade()
{
TriggerWait first = WaitOf(woken, order: 0, trigger);
TriggerWait second = WaitOf(woken, order: 1, trigger);
CheckTrue(ReferenceEquals(first.Subscriptions[0], trigger.SubscriptionAt(0)));
CheckTrue(ReferenceEquals(second.Subscriptions[0], trigger.SubscriptionAt(1)));
}
public void NegativeIndex_Throws()
{
WaitOf(woken, order: 0, trigger);
CheckThrow(typeof(ArgumentOutOfRangeException));
trigger.SubscriptionAt(-1);
}
public void IndexPastTheCount_Throws()
{
WaitOf(woken, order: 0, trigger);
CheckThrow(typeof(ArgumentOutOfRangeException));
trigger.SubscriptionAt(1);
}
}
public class WaitAtTests : TestFixture
{
private List woken;
private Trigger trigger;
public override void TestSetup()
{
woken = new List();
trigger = new Trigger();
}
public void ReturnsTheNumberOfTheWaitTheSubscriptionWasMadeFor()
{
TriggerWait wait = WaitOf(woken, order: 0, trigger);
wait.Begin(new Frame { Wake = wait.Slot }, requiresAll: false);
wait.Add(trigger);
CheckEqual(1, trigger.WaitAt(0));
CheckEqual(2, trigger.WaitAt(1));
}
public void IndexPastTheCount_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
trigger.WaitAt(0);
}
}
public class IsLiveAtTests : TestFixture
{
private List woken;
private Trigger trigger;
public override void TestSetup()
{
woken = new List();
trigger = new Trigger();
}
public void SubscriptionOfAWaitInProgress_IsLive()
{
WaitOf(woken, order: 0, trigger);
CheckTrue(trigger.IsLiveAt(0));
}
public void SubscriptionOfAWaitThatBeganAgain_IsNotLive()
{
TriggerWait wait = WaitOf(woken, order: 0, trigger);
wait.Begin(new Frame { Wake = wait.Slot }, requiresAll: false);
CheckFalse(trigger.IsLiveAt(0));
}
public void SubscriptionOfAWaitThatIsOver_IsNotLive()
{
WaitOf(woken, order: 0, trigger).Mark(0);
CheckFalse(trigger.IsLiveAt(0));
}
public void SubscriptionOfADroppedSlot_IsNotLive()
{
WaitOf(woken, order: 0, trigger).Slot.Dropped = true;
CheckFalse(trigger.IsLiveAt(0));
}
public void SubscriptionOfAnUnattachedWait_IsLive()
{
WaitOf(woken, order: 0, trigger).Slot = null;
CheckTrue(trigger.IsLiveAt(0));
}
public void IndexPastTheCount_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
trigger.IsLiveAt(0);
}
}
public class RestoreTests : TestFixture
{
private Trigger trigger;
public override void TestSetup()
{
trigger = new Trigger();
}
public void NullSubscription_Throws()
{
CheckThrow(typeof(ArgumentNullException));
trigger.Restore(null, wait: 1);
}
public void AppendsTheSubscriptionWithItsWaitNumber()
{
var wait = new TriggerWait(capacity: 1);
trigger.Restore(wait.Subscriptions[0], wait: 7);
CheckEqual(1, trigger.SubscriptionCount);
CheckTrue(ReferenceEquals(wait.Subscriptions[0], trigger.SubscriptionAt(0)));
CheckEqual(7, trigger.WaitAt(0));
}
public void SubscriptionsWhoseWaitsAreNotFilledYet_AreAllKept()
{
for (int i = 0; i < 100; ++i)
{
trigger.Restore(new TriggerWait(capacity: 1).Subscriptions[0], wait: 5);
}
CheckEqual(100, trigger.SubscriptionCount);
}
}
public class EnsureSubscribedTests : TestFixture
{
private List woken;
private Trigger trigger;
public override void TestSetup()
{
woken = new List();
trigger = new Trigger();
}
public void NullSubscription_Throws()
{
CheckThrow(typeof(ArgumentNullException));
trigger.EnsureSubscribed(null, wait: 1);
}
public void SubscriptionTheTriggerLacks_IsAdded()
{
var wait = new TriggerWait(capacity: 1);
trigger.EnsureSubscribed(wait.Subscriptions[0], wait: 1);
CheckEqual(1, trigger.SubscriptionCount);
CheckTrue(ReferenceEquals(wait.Subscriptions[0], trigger.SubscriptionAt(0)));
CheckEqual(1, trigger.WaitAt(0));
}
public void SubscriptionTheTriggerHolds_IsNotAddedAgain()
{
TriggerWait wait = WaitOf(woken, order: 0, trigger);
trigger.EnsureSubscribed(wait.Subscriptions[0], wait.Wait);
CheckEqual(1, trigger.SubscriptionCount);
}
public void SubscriptionHeldForAnEarlierWait_IsAddedForThisOne()
{
TriggerWait wait = WaitOf(woken, order: 0, trigger);
wait.Begin(new Frame { Wake = wait.Slot }, requiresAll: false);
trigger.EnsureSubscribed(wait.Subscriptions[0], wait.Wait);
CheckEqual(2, trigger.SubscriptionCount);
CheckEqual(wait.Wait, trigger.WaitAt(1));
}
public void SubscriptionOfADroppedSlot_IsNotAddedAgain()
{
TriggerWait wait = WaitOf(woken, order: 0, trigger);
wait.Slot.Dropped = true;
trigger.EnsureSubscribed(wait.Subscriptions[0], wait.Wait);
CheckEqual(1, trigger.SubscriptionCount);
}
public void SubscriptionRestoredFromASave_IsNotAddedAgain()
{
var wait = new TriggerWait(capacity: 1);
trigger.Restore(wait.Subscriptions[0], wait: 3);
trigger.EnsureSubscribed(wait.Subscriptions[0], wait: 3);
CheckEqual(1, trigger.SubscriptionCount);
}
public void SubscriptionUsedUpByARaise_IsAddedAgain()
{
TriggerWait wait = WaitOf(woken, order: 0, trigger);
trigger.Raise();
trigger.EnsureSubscribed(wait.Subscriptions[0], wait.Wait);
CheckEqual(1, trigger.SubscriptionCount);
}
public void SubscriptionSweptOutOfTheList_IsAddedAgain()
{
TriggerWait wait = WaitOf(woken, order: 0, trigger);
wait.Slot.Dropped = true;
for (int i = 0; i < 20; ++i)
{
WaitOf(woken, order: 1, trigger);
}
int before = trigger.SubscriptionCount;
trigger.EnsureSubscribed(wait.Subscriptions[0], wait.Wait);
CheckEqual(before + 1, trigger.SubscriptionCount);
}
public void SubscriptionHeldByAnotherTrigger_IsAddedHere()
{
TriggerWait wait = WaitOf(woken, order: 0, new Trigger());
trigger.EnsureSubscribed(wait.Subscriptions[0], wait.Wait);
CheckEqual(1, trigger.SubscriptionCount);
}
public void EarlierEntryOfTheSubscriptionSweptAway_LeavesTheLaterOneHeld()
{
TriggerWait wait = WaitOf(woken, order: 0, trigger);
wait.Begin(new Frame { Wake = wait.Slot }, requiresAll: false);
wait.Add(trigger);
for (int i = 0; i < 20; ++i)
{
WaitOf(woken, order: 1, trigger);
}
int before = trigger.SubscriptionCount;
trigger.EnsureSubscribed(wait.Subscriptions[0], wait.Wait);
CheckEqual(before, trigger.SubscriptionCount);
}
}
public class RaiseTests : TestFixture
{
private List woken;
private Trigger trigger;
[IgnoreTest]
public void SubscribeAll(IEnumerable orders)
{
foreach (int order in orders)
{
WaitOf(woken, order, trigger);
}
}
public override void TestSetup()
{
woken = new List();
trigger = new Trigger();
}
public void NobodySubscribed_DoesNothing()
{
trigger.Raise();
CheckEqual(0, woken.Count);
CheckEqual(0, trigger.SubscriptionCount);
}
public void WakesEverySubscriber()
{
SubscribeAll(new[] { 0, 1, 2 });
trigger.Raise();
CheckEqual(new[] { 0, 1, 2 }, woken.ToArray());
}
public void SubscriptionsMadeInOrder_WakeInThatOrder()
{
SubscribeAll(Counting(100));
trigger.Raise();
CheckEqual(Counting(100), woken.ToArray());
}
public void FewSubscribersAddedBackwards_WakeInRegistrationOrder()
{
SubscribeAll(new[] { 2, 1, 0 });
trigger.Raise();
CheckEqual(new[] { 0, 1, 2 }, woken.ToArray());
}
public void SubscribersAddedBackwards_AreReleasedAfterTheRaise()
{
TriggerWait later = WaitOf(woken, order: 1, trigger);
TriggerWait earlier = WaitOf(woken, order: 0, trigger);
earlier.Begin(new Frame { Wake = earlier.Slot }, requiresAll: false);
earlier.Add(trigger);
trigger.Raise();
CheckNull(later.Subscriptions[0].HeldBy);
CheckNull(earlier.Subscriptions[0].HeldBy);
}
public void ManySubscribersAddedBackwards_WakeInRegistrationOrder()
{
int[] backwards = Counting(100);
Array.Reverse(backwards);
SubscribeAll(backwards);
trigger.Raise();
CheckEqual(Counting(100), woken.ToArray());
}
public void ManySubscribersAddedEvensThenOdds_WakeInRegistrationOrder()
{
var shuffled = new List();
for (int order = 0; order < 100; order += 2)
{
shuffled.Add(order);
}
for (int order = 1; order < 100; order += 2)
{
shuffled.Add(order);
}
SubscribeAll(shuffled);
trigger.Raise();
CheckEqual(Counting(100), woken.ToArray());
}
public void SubscribersWithTheSameOrder_WakeInTheOrderTheySubscribed()
{
foreach (int tag in new[] { 5, 3, 9 })
{
WaitOf(woken, order: 7, tag: tag, requiresAll: false, triggers: new[] { trigger });
}
WaitOf(woken, order: 2, tag: 1, requiresAll: false, triggers: new[] { trigger });
trigger.Raise();
CheckEqual(new[] { 1, 5, 3, 9 }, woken.ToArray());
}
public void Raise_ConsumesTheSubscriptions()
{
SubscribeAll(new[] { 0, 1 });
trigger.Raise();
CheckEqual(0, trigger.SubscriptionCount);
trigger.Raise();
CheckEqual(new[] { 0, 1 }, woken.ToArray());
}
public void WaitThatBeganAgain_IsNotMarkedByTheOldSubscription()
{
var other = new Trigger();
TriggerWait wait = WaitOf(woken, order: 0, trigger);
wait.Begin(new Frame { Wake = wait.Slot }, requiresAll: false);
wait.Add(other);
trigger.Raise();
CheckEqual(0, wait.Seen);
CheckEqual(0, woken.Count);
}
public void WaitForAny_IsWokenByTheFirstRaiseOnly()
{
var other = new Trigger();
TriggerWait wait = WaitOf(woken, order: 0, tag: 0, requiresAll: false,
triggers: new[] { trigger, other });
other.Raise();
trigger.Raise();
CheckEqual(new[] { 0 }, woken.ToArray());
CheckEqual(1, wait.Fired);
CheckEqual(0, trigger.SubscriptionCount);
}
public void WaitForAll_IsWokenByTheLastRaise()
{
var other = new Trigger();
TriggerWait wait = WaitOf(woken, order: 0, tag: 0, requiresAll: true,
triggers: new[] { trigger, other });
trigger.Raise();
CheckEqual(0, woken.Count);
other.Raise();
CheckEqual(new[] { 0 }, woken.ToArray());
CheckEqual(1, wait.Fired);
}
public void WaitForAllOfOneTriggerTwice_IsWokenByOneRaise()
{
TriggerWait wait = WaitOf(woken, order: 0, tag: 0, requiresAll: true,
triggers: new[] { trigger, trigger });
trigger.Raise();
CheckEqual(new[] { 0 }, woken.ToArray());
CheckEqual(1, wait.Fired);
}
public void DroppedSlot_IsNotWoken()
{
TriggerWait dropped = WaitOf(woken, order: 0, trigger);
dropped.Slot.Dropped = true;
SubscribeAll(new[] { 1 });
trigger.Raise();
CheckEqual(new[] { 1 }, woken.ToArray());
CheckEqual(0, dropped.Seen);
}
public void UnattachedWait_ThrowsAndLeavesEverySubscriptionUnmarked()
{
TriggerWait unattached = WaitOf(woken, order: 0, trigger);
unattached.Slot = null;
TriggerWait attached = WaitOf(woken, order: 1, trigger);
bool threw = false;
try
{
trigger.Raise();
}
catch (InvalidOperationException)
{
threw = true;
}
CheckTrue(threw);
CheckEqual(0, attached.Seen);
CheckEqual(0, woken.Count);
CheckEqual(2, trigger.SubscriptionCount);
}
public void WaitAttachedAfterwards_IsWokenByTheNextRaise()
{
TriggerWait wait = WaitOf(woken, order: 4, trigger);
WakeSlot slot = wait.Slot;
wait.Slot = null;
try
{
trigger.Raise();
}
catch (InvalidOperationException)
{
}
wait.Slot = slot;
trigger.Raise();
CheckEqual(new[] { 4 }, woken.ToArray());
}
}
public class SchedulerTests : TestFixture
{
private SchedulerWorld world;
private ExceptionSandbox sandbox;
private ArcScheduler scheduler;
private Trigger trigger;
public override void TestSetup()
{
world = new SchedulerWorld();
sandbox = TestTicks.NewSandbox();
scheduler = new ArcScheduler();
trigger = new Trigger();
}
public void Raise_WakesASuspendedArcOnTheNextTick()
{
var waiter = new SuspenderArc();
scheduler.Add(waiter);
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 0));
waiter.WaitOn(trigger);
trigger.Raise();
CheckEqual(0, scheduler.SuspendedCount);
CheckEqual(1, waiter.Resumes);
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 0.016));
CheckEqual(2, waiter.Resumes);
}
public void Raise_DoesNotWakeAnArcThatIsNotSubscribed()
{
var waiter = new SuspenderArc();
scheduler.Add(waiter);
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 0));
trigger.Raise();
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 0.016));
CheckEqual(1, waiter.Resumes);
CheckEqual(1, scheduler.SuspendedCount);
}
public void ArcThatWaitsOnTheTriggerEachTime_IsWokenByEveryRaise()
{
var waiter = new SuspenderArc { Triggers = new[] { trigger } };
scheduler.Add(waiter);
for (int frame = 0; frame < 3; ++frame)
{
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, frame));
trigger.Raise();
}
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 3));
CheckEqual(4, waiter.Resumes);
}
public void WaitForAny_IsWokenByTheFirstTriggerAndNamesIt()
{
var other = new Trigger();
var waiter = new SuspenderArc();
scheduler.Add(waiter);
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 0));
waiter.WaitOn(trigger, other);
other.Raise();
trigger.Raise();
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 1));
CheckEqual(2, waiter.Resumes);
CheckTrue(ReferenceEquals(other, waiter.Waiting.Result));
}
public void WaitForAny_IsNotWokenAgainByTheOtherTrigger()
{
var other = new Trigger();
var waiter = new SuspenderArc();
scheduler.Add(waiter);
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 0));
waiter.WaitOn(trigger, other);
trigger.Raise();
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 1));
other.Raise();
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 2));
CheckEqual(2, waiter.Resumes);
CheckEqual(1, scheduler.SuspendedCount);
}
public void WaitForAll_IsWokenOnlyByTheLastTrigger()
{
var other = new Trigger();
var waiter = new SuspenderArc { RequireAll = true };
scheduler.Add(waiter);
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 0));
waiter.WaitOn(trigger, other);
trigger.Raise();
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 1));
CheckEqual(1, waiter.Resumes);
other.Raise();
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 2));
CheckEqual(2, waiter.Resumes);
CheckTrue(ReferenceEquals(other, waiter.Waiting.Result));
}
public void Raise_WakesAnArcSleepingOnATimeoutBeforeTheDeadline()
{
var waiter = new SuspenderArc { Deadline = TimeSpan.FromSeconds(100) };
scheduler.Add(waiter);
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 0));
CheckEqual(1, scheduler.SleepingCount);
waiter.WaitOn(trigger);
trigger.Raise();
CheckEqual(0, scheduler.SleepingCount);
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 1));
CheckEqual(2, waiter.Resumes);
}
public void RemovedArc_IsNotWoken()
{
var waiter = new SuspenderArc();
scheduler.Add(waiter);
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 0));
waiter.WaitOn(trigger);
scheduler.Remove(waiter);
trigger.Raise();
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 1));
CheckEqual(1, waiter.Resumes);
CheckEqual(0, scheduler.Count);
CheckEqual(0, trigger.SubscriptionCount);
}
public void ArcThatWaitedAgainOnAnotherTrigger_IsNotWokenByTheFirst()
{
var other = new Trigger();
var waiter = new SuspenderArc();
scheduler.Add(waiter);
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 0));
waiter.WaitOn(trigger);
waiter.WaitOn(other);
trigger.Raise();
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 1));
CheckEqual(1, waiter.Resumes);
other.Raise();
scheduler.Tick(world, ArcSchedulerTests.At(sandbox, 2));
CheckEqual(2, waiter.Resumes);
}
public void ArcOutsideAScheduler_CannotWaitOnATrigger()
{
var waiter = new SuspenderArc { Triggers = new[] { trigger } };
waiter.Tick(world, ArcSchedulerTests.At(sandbox, 0));
CheckEqual(1, sandbox.Entries.Count);
CheckEqual(typeof(InvalidOperationException), sandbox.Entries[0].Exception.GetType());
}
}
public class RaiseAllocationTests : TestFixture
{
private Trigger trigger;
private TriggerWait[] waits;
private Frame[] frames;
[IgnoreTest]
public void WaitAll()
{
for (int i = 0; i < waits.Length; ++i)
{
waits[i].Begin(frames[i], requiresAll: false);
waits[i].Add(trigger);
}
}
// One round first, so everything a raise grows is ready for the next.
public override void TestSetup()
{
trigger = new Trigger();
waits = new TriggerWait[100];
frames = new Frame[waits.Length];
for (int i = 0; i < waits.Length; ++i)
{
waits[i] = new TriggerWait(capacity: 1);
frames[i] = new Frame { Wake = new WakeSlot { Order = waits.Length - 1 - i } };
}
WaitAll();
trigger.Raise();
}
[AllocationTest]
public void RaiseOfSubscribersAddedOutOfOrder_AllocatesNothing()
{
WaitAll();
trigger.Raise();
CheckNoAllocations();
}
}
}
}