using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Amphora; using UnitTestSharp; namespace Ariadne.UnitTests { public class ArcSchedulerTests : TestFixture { public static TickContext At(ExceptionSandbox sandbox, double gameSeconds) { return TestTicks.At(sandbox, TimeSpan.FromMilliseconds(16), TimeSpan.FromSeconds(gameSeconds)); } public static NapScript Nap(string name, double seconds) { return new NapScript { Name = name, Nap = TimeSpan.FromSeconds(seconds) }; } public static PollScript Poll(string name) { return new PollScript { Name = name }; } public static ChainScript Chain(string name, double seconds) { return new ChainScript { Name = name, Nap = TimeSpan.FromSeconds(seconds) }; } public static PassageNapScript PassageNap(string name, double seconds) { return new PassageNapScript { Name = name, Nap = TimeSpan.FromSeconds(seconds) }; } public static SleeperArc Sleeper(double deadlineSeconds) { return new SleeperArc { Deadline = TimeSpan.FromSeconds(deadlineSeconds) }; } public class MatchesTickingEveryArcTests : TestFixture { /// /// Plays , where "@2.5" sets the game time, "" ticks, and "+go" opens the gate "go" /// and ticks. Every arc is ticked each step in order, or the scheduler does it. /// [IgnoreTest] public string[] Play(Func[]> makeArcs, string[] steps, bool scheduled) { var world = new SchedulerWorld(); ExceptionSandbox sandbox = TestTicks.NewSandbox(); Arc[] arcs = makeArcs(); var scheduler = new ArcScheduler(); foreach (Arc arc in arcs) { scheduler.Add(arc); } double seconds = 0; foreach (string step in steps) { if (step.StartsWith("@", StringComparison.Ordinal)) { seconds = double.Parse(step.Substring(1), System.Globalization.CultureInfo.InvariantCulture); continue; } if (step.StartsWith("+", StringComparison.Ordinal)) { world.Open.Add(step.Substring(1)); } if (scheduled) { scheduler.Tick(world, At(sandbox, seconds)); } else { foreach (Arc arc in arcs) { arc.Tick(world, At(sandbox, seconds)); } } } return world.Events.ToArray(); } [IgnoreTest] public string[] PlayBothWays(Func[]> makeArcs, string[] steps) { string[] ticked = Play(makeArcs, steps, scheduled: false); string[] scheduled = Play(makeArcs, steps, scheduled: true); CheckEqual(ticked, scheduled); return scheduled; } public void NapsOfDifferentLengths_RunInTheSameOrder() { string[] events = PlayBothWays( () => new Arc[] { Nap("a", 3), Nap("b", 1), Nap("c", 2) }, new[] { "@0", "", "@1", "", "@2", "", "@3", "", "@4", "", "@5", "", "@6", "" }); CheckEqual(new[] { "a begins", "b begins", "c begins", "b wakes", "b wakes again", "c wakes", "a wakes", "c wakes again", "a wakes again", }, events); } public void SeveralWakingInOneFrame_RunInRegistrationOrder() { string[] events = PlayBothWays( () => new Arc[] { Nap("a", 3), Nap("b", 1), Nap("c", 2) }, new[] { "@0", "", "@10", "" }); CheckEqual(new[] { "a begins", "b begins", "c begins", "a wakes", "b wakes", "c wakes" }, events); } [IgnoreTest] public Arc[] NappersOfSeveralLengths(int count) { var returnMe = new Arc[count]; for (int i = 0; i < count; ++i) { returnMe[i] = Nap("a" + i, 1 + i % 7); } return returnMe; } [IgnoreTest] public string[] EverySecondUpTo(int seconds) { var steps = new List { "@0", "" }; for (int second = 1; second <= seconds; ++second) { steps.Add("@" + second); steps.Add(""); } return steps.ToArray(); } public void ManyArcsOfSeveralNapLengthsWakingTogether_RunInTheSameOrder() { string[] events = PlayBothWays(() => NappersOfSeveralLengths(300), EverySecondUpTo(20)); CheckEqual(900, events.Length); } public void ManyArcsOfOneNapLengthWakingTogether_RunInTheSameOrder() { string[] events = PlayBothWays( () => Enumerable.Range(0, 300).Select(i => (Arc)Nap("a" + i, 2)).ToArray(), EverySecondUpTo(6)); CheckEqual(900, events.Length); } public void ArcsThatPollAndArcsThatNap_RunInTheSameOrder() { string[] events = PlayBothWays( () => new Arc[] { Poll("p"), Nap("n", 2), Poll("q"), Chain("c", 1) }, new[] { "@0", "", "@1", "", "@2", "", "@2.5", "+go", "@3", "", "@4", "" }); CheckTrue(events.Contains("n wakes again")); CheckTrue(events.Contains("c done")); CheckTrue(events.Contains("p goes")); CheckTrue(events.Contains("q goes")); } public void WaitThenYieldThenWait_RunsTheSameAsTickingEveryArc() { string[] events = PlayBothWays( () => new Arc[] { Poll("p"), Chain("c", 1) }, new[] { "@0", "", "@1", "", "@1", "", "@2", "" }); CheckEqual(new[] { "p begins", "p polls", "p polls", "c wakes", "p polls", "c yielded", "p polls", "c done" }, events); } public void WaitInAPassageOfTheArc_RunsTheSameAsTickingEveryArc() { string[] events = PlayBothWays( () => new Arc[] { PassageNap("a", 1), PassageNap("b", 2) }, new[] { "@0", "", "@1", "", "@2", "", "@3", "", "@4", "" }); CheckEqual(new[] { "a rested", "a rested", "b rested", "b rested" }, events); } public void WaitInAPassageOfAnotherArc_RunsTheSameAsTickingEveryArc() { string[] events = PlayBothWays( () => new Arc[] { new NapClientScript(), Nap("n", 1) }, new[] { "@0", "", "@0.5", "", "@1", "", "@2", "" }); CheckEqual(new[] { "n begins", "worker rested", "client done", "n wakes", "n wakes again" }, events); } public void WaitsEitherSideOfTheMinimum_RunTheSameAsTickingEveryArc() { var steps = new System.Collections.Generic.List(); for (int frame = 0; frame < 60; ++frame) { steps.Add("@" + (frame * 0.016).ToString(System.Globalization.CultureInfo.InvariantCulture)); steps.Add(""); } string[] events = PlayBothWays( () => new Arc[] { Nap("under", 0.1), Nap("over", 0.2), Nap("edge", 0.128), Nap("short", 0.016), }, steps.ToArray()); CheckTrue(events.Contains("under wakes again")); CheckTrue(events.Contains("over wakes again")); CheckTrue(events.Contains("edge wakes again")); CheckTrue(events.Contains("short wakes again")); } public void GameTimeStandingStill_RunsTheSameAsTickingEveryArc() { string[] events = PlayBothWays( () => new Arc[] { Nap("a", 1), Poll("p") }, new[] { "@5", "", "", "", "" }); CheckEqual(new[] { "a begins", "p begins", "p polls", "p polls", "p polls", "p polls" }, events); } public void GameTimeJumpingFarPastEveryDeadline_RunsTheSameAsTickingEveryArc() { string[] events = PlayBothWays( () => new Arc[] { Nap("a", 1), Nap("b", 2) }, new[] { "@0", "", "@1000", "", "" }); CheckEqual(new[] { "a begins", "b begins", "a wakes", "b wakes" }, events); } } public class SleepTests : TestFixture { private SchedulerWorld world; private ExceptionSandbox sandbox; private ArcScheduler scheduler; public override void TestSetup() { world = new SchedulerWorld(); sandbox = TestTicks.NewSandbox(); scheduler = new ArcScheduler(); } public void ArcPausedInAWait_Sleeps() { scheduler.Add(Nap("a", 3)); scheduler.Tick(world, At(sandbox, 0)); CheckEqual(1, scheduler.SleepingCount); CheckEqual(1, scheduler.Count); } public void ArcWaitingForever_SleepsAndIsNeverWoken() { scheduler.Add(new NapScript { Name = "a", Nap = Timeout.InfiniteTimeSpan }); scheduler.Tick(world, At(sandbox, 0)); scheduler.Tick(world, At(sandbox, 1e9)); scheduler.Tick(world, At(sandbox, 1e11)); CheckEqual(new[] { "a begins" }, world.Events.ToArray()); CheckEqual(1, scheduler.SleepingCount); CheckEqual(0, sandbox.Entries.Count); } public void ArcWaitingForever_CanBeRemoved() { SleeperArc sleeper = new SleeperArc { Deadline = Timeout.InfiniteTimeSpan }; scheduler.Add(sleeper); scheduler.Tick(world, At(sandbox, 0)); scheduler.Remove(sleeper); CheckEqual(0, scheduler.SleepingCount); CheckEqual(0, scheduler.Count); } public void ArcWakedWhileWaitingForever_RunsOnTheNextTick() { SleeperArc sleeper = new SleeperArc { Deadline = Timeout.InfiniteTimeSpan }; scheduler.Add(sleeper); scheduler.Tick(world, At(sandbox, 0)); scheduler.Wake(sleeper); scheduler.Tick(world, At(sandbox, 1)); CheckEqual(2, sleeper.Resumes); } public void ArcPausedInAYield_StaysAwake() { scheduler.Add(Poll("p")); scheduler.Tick(world, At(sandbox, 0)); CheckEqual(0, scheduler.SleepingCount); } public void SleepingArc_IsNotRunBeforeItsDeadline() { SleeperArc sleeper = Sleeper(10); scheduler.Add(sleeper); for (int second = 0; second < 10; ++second) { scheduler.Tick(world, At(sandbox, second)); } scheduler.Tick(world, At(sandbox, 9.99)); CheckEqual(1, sleeper.Resumes); } public void SleepingArc_RunsOnTheFrameItsDeadlineIsReached() { SleeperArc sleeper = Sleeper(10); scheduler.Add(sleeper); scheduler.Tick(world, At(sandbox, 0)); scheduler.Tick(world, At(sandbox, 10)); CheckEqual(2, sleeper.Resumes); } public void ThousandSleepers_AreNotRunWhileTheyWait() { var sleepers = new SleeperArc[1000]; for (int i = 0; i < sleepers.Length; ++i) { sleepers[i] = Sleeper(10); scheduler.Add(sleepers[i]); } scheduler.Tick(world, At(sandbox, 0)); CheckEqual(1000, scheduler.SleepingCount); for (int second = 1; second < 10; ++second) { scheduler.Tick(world, At(sandbox, second)); } CheckEqual(0, sleepers.Count(each => each.Resumes != 1)); scheduler.Tick(world, At(sandbox, 10)); CheckEqual(0, sleepers.Count(each => each.Resumes != 2)); CheckEqual(0, scheduler.Count); } public void WaitInAPassageOfAnotherArc_PutsTheRegisteredArcToSleep() { scheduler.Add(new NapClientScript()); scheduler.Tick(world, At(sandbox, 0)); CheckEqual(1, scheduler.SleepingCount); scheduler.Tick(world, At(sandbox, 1)); CheckEqual(new[] { "worker rested", "client done" }, world.Events.ToArray()); CheckEqual(0, scheduler.Count); } public void DefaultMinimumSleep_IsEightFrames() { CheckEqual(8, scheduler.MinimumSleepFrames); CheckEqual(ArcScheduler.DefaultMinimumSleepFrames, scheduler.MinimumSleepFrames); } public void WaitShorterThanTheMinimum_StaysAwakeAndRunsEveryFrame() { SleeperArc sleeper = Sleeper(0.1); scheduler.Add(sleeper); scheduler.Tick(world, At(sandbox, 0)); CheckEqual(0, scheduler.SleepingCount); scheduler.Tick(world, At(sandbox, 0.016)); scheduler.Tick(world, At(sandbox, 0.032)); CheckEqual(3, sleeper.Resumes); } public void WaitLongerThanTheMinimum_Sleeps() { scheduler.Add(Sleeper(0.2)); scheduler.Tick(world, At(sandbox, 0)); CheckEqual(1, scheduler.SleepingCount); } public void MinimumOfZero_SleepsEveryWait() { scheduler.MinimumSleepFrames = 0; scheduler.Add(Sleeper(0.01)); scheduler.Tick(world, At(sandbox, 0)); CheckEqual(1, scheduler.SleepingCount); } public void MinimumMeasuredInTheTicksDeltaTime_FollowsTheFrameLength() { scheduler.MinimumSleepFrames = 10; scheduler.Add(Sleeper(5)); scheduler.Tick(world, TestTicks.At(sandbox, TimeSpan.FromSeconds(1), TimeSpan.Zero)); CheckEqual(0, scheduler.SleepingCount); scheduler.Tick(world, TestTicks.At(sandbox, TimeSpan.FromSeconds(0.1), TimeSpan.FromSeconds(1))); CheckEqual(1, scheduler.SleepingCount); } public void NegativeMinimum_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); scheduler.MinimumSleepFrames = -1; } public void ArcThatOverridesTickSelf_NeverSleeps() { var parent = new ParentArc { Deadline = TimeSpan.FromSeconds(10) }; scheduler.Add(parent); for (int second = 0; second < 4; ++second) { scheduler.Tick(world, At(sandbox, second)); } CheckEqual(4, parent.SelfTicks); CheckEqual(0, scheduler.SleepingCount); } } public class SuspendTests : TestFixture { private SchedulerWorld world; private ExceptionSandbox sandbox; private ArcScheduler scheduler; public override void TestSetup() { world = new SchedulerWorld(); sandbox = TestTicks.NewSandbox(); scheduler = new ArcScheduler(); } public void ArcThatSuspends_IsCountedAsSuspendedAndNotAsSleeping() { scheduler.Add(new SuspenderArc()); scheduler.Tick(world, At(sandbox, 0)); CheckEqual(1, scheduler.SuspendedCount); CheckEqual(0, scheduler.SleepingCount); CheckEqual(1, scheduler.Count); } public void SuspendedArc_IsNotRunHoweverMuchTimePasses() { var suspender = new SuspenderArc(); scheduler.Add(suspender); foreach (double seconds in new double[] { 0, 1, 1000, 1000000 }) { scheduler.Tick(world, At(sandbox, seconds)); } CheckEqual(1, suspender.Resumes); } public void ThousandSuspendedArcs_AreNotRunUntilWoken() { var suspenders = new SuspenderArc[1000]; for (int i = 0; i < suspenders.Length; ++i) { suspenders[i] = new SuspenderArc(); scheduler.Add(suspenders[i]); } for (int second = 0; second < 10; ++second) { scheduler.Tick(world, At(sandbox, second)); } CheckEqual(1000, scheduler.SuspendedCount); CheckEqual(0, suspenders.Count(each => each.Resumes != 1)); foreach (SuspenderArc suspender in suspenders) { scheduler.Wake(suspender); } scheduler.Tick(world, At(sandbox, 10)); CheckEqual(0, suspenders.Count(each => each.Resumes != 2)); CheckEqual(1000, scheduler.SuspendedCount); } public void Wake_RunsTheArcOnTheNextTick() { var suspender = new SuspenderArc(); scheduler.Add(suspender); scheduler.Tick(world, At(sandbox, 0)); CheckTrue(scheduler.Wake(suspender)); CheckEqual(0, scheduler.SuspendedCount); CheckEqual(1, suspender.Resumes); scheduler.Tick(world, At(sandbox, 0.016)); CheckEqual(2, suspender.Resumes); CheckEqual(1, scheduler.SuspendedCount); } public void WakeTwice_RunsTheArcOnce() { var suspender = new SuspenderArc(); scheduler.Add(suspender); scheduler.Tick(world, At(sandbox, 0)); scheduler.Wake(suspender); scheduler.Wake(suspender); scheduler.Tick(world, At(sandbox, 0.016)); CheckEqual(2, suspender.Resumes); CheckEqual(1, scheduler.SuspendedCount); } public void Wake_FromAnotherArcsTick_RunsTheArcOnTheNextTick() { var suspender = new SuspenderArc(); bool woke = false; scheduler.Add(suspender); scheduler.Add(new SchedulerUsingArc { OnResume = () => { if (!woke) { woke = true; scheduler.Wake(suspender); } }, }); scheduler.Tick(world, At(sandbox, 0)); CheckEqual(1, suspender.Resumes); scheduler.Tick(world, At(sandbox, 0.016)); CheckEqual(2, suspender.Resumes); } public void WokenArcs_RunInRegistrationOrderAmongTheRunningOnes() { scheduler.Add(new SuspenderArc { Name = "a" }); scheduler.Add(new RecordingArc { Name = "b" }); var last = new SuspenderArc { Name = "c" }; scheduler.Add(last); scheduler.Tick(world, At(sandbox, 0)); scheduler.Wake(last); scheduler.Tick(world, At(sandbox, 0.016)); CheckEqual(new[] { "a", "b", "c", "b", "c" }, world.Events.ToArray()); } public void ArcWokenAfterAnEarlierOneInTheOrder_RunsAfterIt() { var first = new SuspenderArc { Name = "a" }; var second = new SuspenderArc { Name = "b" }; scheduler.Add(first); scheduler.Add(second); scheduler.Tick(world, At(sandbox, 0)); scheduler.Wake(second); scheduler.Wake(first); scheduler.Tick(world, At(sandbox, 0.016)); CheckEqual(new[] { "a", "b", "a", "b" }, world.Events.ToArray()); } public void Remove_OfASuspendedArc_ForgetsIt() { var suspender = new SuspenderArc(); scheduler.Add(suspender); scheduler.Tick(world, At(sandbox, 0)); CheckTrue(scheduler.Remove(suspender)); CheckFalse(scheduler.Wake(suspender)); scheduler.Tick(world, At(sandbox, 1)); CheckEqual(1, suspender.Resumes); CheckEqual(0, scheduler.Count); CheckEqual(0, scheduler.SuspendedCount); } public void RemovedSuspendedArc_CanBeAddedAgain() { var suspender = new SuspenderArc(); scheduler.Add(suspender); scheduler.Tick(world, At(sandbox, 0)); scheduler.Remove(suspender); scheduler.Add(suspender); scheduler.Tick(world, At(sandbox, 1)); CheckEqual(2, suspender.Resumes); CheckEqual(1, scheduler.SuspendedCount); } public void Restart_OfASuspendedArc_WakesIt() { var suspender = new SuspenderArc(); scheduler.Add(suspender); scheduler.Tick(world, At(sandbox, 0)); scheduler.Restart(suspender); scheduler.Tick(world, At(sandbox, 0.016)); CheckEqual(2, suspender.Resumes); CheckEqual(1, scheduler.SuspendedCount); } public void ArcThatFinishesWhenWoken_IsDropped() { var suspender = new SuspenderArc { FinishAtResume = 2 }; scheduler.Add(suspender); scheduler.Tick(world, At(sandbox, 0)); scheduler.Wake(suspender); scheduler.Tick(world, At(sandbox, 0.016)); CheckEqual(0, scheduler.Count); CheckEqual(0, scheduler.SuspendedCount); } public void ArcThatThrowsWhenWoken_IsQuarantinedAndNotSuspended() { var suspender = new SuspenderArc { ThrowAtResume = 2 }; scheduler.Add(suspender); scheduler.Tick(world, At(sandbox, 0)); scheduler.Wake(suspender); scheduler.Tick(world, At(sandbox, 0.016)); scheduler.Tick(world, At(sandbox, 0.032)); CheckTrue(sandbox.IsQuarantined(AriadneSites.Tick(suspender))); CheckEqual(2, suspender.Resumes); CheckEqual(0, scheduler.SuspendedCount); CheckEqual(1, scheduler.Count); } public void ArcThatOverridesTickSelf_NeverSuspends() { var parent = new SuspenderParentArc(); scheduler.Add(parent); for (int second = 0; second < 4; ++second) { scheduler.Tick(world, At(sandbox, second)); } CheckEqual(4, parent.Resumes); CheckEqual(0, scheduler.SuspendedCount); } public void TickRefusedByAnArc_LeavesASuspendedArcSuspended() { var suspender = new SuspenderArc(); var yielder = new YieldingArc(); scheduler.Add(suspender); scheduler.Add(yielder); scheduler.Tick(world, At(sandbox, 0)); try { scheduler.Tick(world, default(TickContext)); } catch (ArgumentException) { } scheduler.Tick(world, At(sandbox, 1)); CheckEqual(1, suspender.Resumes); CheckEqual(1, scheduler.SuspendedCount); CheckEqual(2, yielder.Resumes); } public void SuspendedWithADistantDeadline_SleepsInsteadAndRunsAtTheDeadline() { var suspender = new SuspenderArc { Deadline = TimeSpan.FromSeconds(10) }; scheduler.Add(suspender); scheduler.Tick(world, At(sandbox, 0)); CheckEqual(1, scheduler.SleepingCount); CheckEqual(0, scheduler.SuspendedCount); scheduler.Tick(world, At(sandbox, 9.99)); CheckEqual(1, suspender.Resumes); scheduler.Tick(world, At(sandbox, 10)); CheckEqual(2, suspender.Resumes); } public void SuspendedWithADistantDeadline_CanBeWokenEarly() { var suspender = new SuspenderArc { Deadline = TimeSpan.FromSeconds(10) }; scheduler.Add(suspender); scheduler.Tick(world, At(sandbox, 0)); scheduler.Wake(suspender); scheduler.Tick(world, At(sandbox, 1)); CheckEqual(2, suspender.Resumes); } public void SuspendedWithANearDeadline_StaysAwakeAndRunsEveryFrame() { var suspender = new SuspenderArc { Deadline = TimeSpan.FromSeconds(0.1) }; scheduler.Add(suspender); scheduler.Tick(world, At(sandbox, 0)); scheduler.Tick(world, At(sandbox, 0.016)); scheduler.Tick(world, At(sandbox, 0.032)); CheckEqual(3, suspender.Resumes); CheckEqual(0, scheduler.SuspendedCount); CheckEqual(0, scheduler.SleepingCount); } } public class ManageTests : TestFixture { private SchedulerWorld world; private ExceptionSandbox sandbox; private ArcScheduler scheduler; public override void TestSetup() { world = new SchedulerWorld(); sandbox = TestTicks.NewSandbox(); scheduler = new ArcScheduler(); } public void Add_OfAnArcTheSchedulerHolds_Throws() { SleeperArc sleeper = Sleeper(1); scheduler.Add(sleeper); CheckThrow(typeof(ArgumentException)); scheduler.Add(sleeper); } public void Add_OfNull_Throws() { CheckThrow(typeof(ArgumentNullException)); scheduler.Add(null); } public void Remove_OfAnArcItDoesNotHold_ReturnsFalse() { CheckFalse(scheduler.Remove(Sleeper(1))); } public void Remove_OfASleepingArc_ForgetsIt() { SleeperArc sleeper = Sleeper(10); scheduler.Add(sleeper); scheduler.Tick(world, At(sandbox, 0)); CheckTrue(scheduler.Remove(sleeper)); scheduler.Tick(world, At(sandbox, 100)); CheckEqual(1, sleeper.Resumes); CheckEqual(0, scheduler.Count); CheckEqual(0, scheduler.SleepingCount); } public void Remove_OfAnArcThatHasNotRunYet_NeverRunsIt() { SleeperArc sleeper = Sleeper(10); scheduler.Add(sleeper); scheduler.Remove(sleeper); scheduler.Tick(world, At(sandbox, 0)); CheckEqual(0, sleeper.Resumes); } public void RemovedArc_CanBeAddedAgain() { SleeperArc sleeper = Sleeper(10); scheduler.Add(sleeper); scheduler.Remove(sleeper); scheduler.Add(sleeper); scheduler.Tick(world, At(sandbox, 0)); CheckEqual(1, sleeper.Resumes); } public void Wake_OfASleepingArc_RunsItOnTheNextTick() { SleeperArc sleeper = Sleeper(100); scheduler.Add(sleeper); scheduler.Tick(world, At(sandbox, 0)); CheckTrue(scheduler.Wake(sleeper)); CheckEqual(0, scheduler.SleepingCount); scheduler.Tick(world, At(sandbox, 1)); CheckEqual(2, sleeper.Resumes); CheckEqual(1, scheduler.SleepingCount); } public void Wake_OfAnArcItDoesNotHold_ReturnsFalse() { CheckFalse(scheduler.Wake(Sleeper(1))); } public void Restart_OfASleepingScript_BeginsItAgain() { NapScript nap = Nap("a", 5); scheduler.Add(nap); scheduler.Tick(world, At(sandbox, 0)); scheduler.Restart(nap); scheduler.Tick(world, At(sandbox, 1)); CheckEqual(new[] { "a begins", "a begins" }, world.Events.ToArray()); CheckEqual(1, scheduler.SleepingCount); } public void Restart_OfAnArcItDoesNotHold_Throws() { CheckThrow(typeof(ArgumentException)); scheduler.Restart(Nap("a", 5)); } public void FinishedArc_IsDropped() { scheduler.Add(Nap("a", 1)); scheduler.Tick(world, At(sandbox, 0)); scheduler.Tick(world, At(sandbox, 1)); CheckEqual(1, scheduler.Count); scheduler.Tick(world, At(sandbox, 2)); CheckEqual(0, scheduler.Count); CheckEqual(3, world.Events.Count); } } public class DuringATickTests : TestFixture { private SchedulerWorld world; private ExceptionSandbox sandbox; private ArcScheduler scheduler; public override void TestSetup() { world = new SchedulerWorld(); sandbox = TestTicks.NewSandbox(); scheduler = new ArcScheduler(); } public void ArcAdded_FirstRunsOnTheNextTick() { SleeperArc late = Sleeper(0); bool added = false; scheduler.Add(new SchedulerUsingArc { OnResume = () => { if (!added) { added = true; scheduler.Add(late); } }, }); scheduler.Tick(world, At(sandbox, 0)); CheckEqual(0, late.Resumes); scheduler.Tick(world, At(sandbox, 1)); CheckEqual(1, late.Resumes); } public void ArcRemovedByAnEarlierArc_IsNotRun() { SleeperArc victim = Sleeper(0); scheduler.Add(new SchedulerUsingArc { OnResume = () => scheduler.Remove(victim) }); scheduler.Add(victim); scheduler.Tick(world, At(sandbox, 0)); CheckEqual(0, victim.Resumes); CheckEqual(1, scheduler.Count); } public void TickingTheSchedulerFromAnArc_IsRefused() { scheduler.Add(new SchedulerUsingArc { OnResume = () => scheduler.Tick(world, At(sandbox, 0)) }); scheduler.Tick(world, At(sandbox, 0)); CheckEqual(1, sandbox.Entries.Count); CheckEqual(typeof(InvalidOperationException), sandbox.Entries[0].Exception.GetType()); CheckEqual(1, scheduler.Count); } } public class FaultTests : TestFixture { private SchedulerWorld world; private ExceptionSandbox sandbox; private ArcScheduler scheduler; public override void TestSetup() { world = new SchedulerWorld(); sandbox = TestTicks.NewSandbox(); scheduler = new ArcScheduler(); } public void ArcThatThrows_IsQuarantinedAndTheOthersCarryOn() { var thrower = new ThrowingSchedulerArc(); var other = new YieldingArc(); scheduler.Add(thrower); scheduler.Add(other); scheduler.Tick(world, At(sandbox, 0)); scheduler.Tick(world, At(sandbox, 1)); CheckEqual(1, sandbox.Entries.Count); CheckEqual(1, thrower.Resumes); CheckEqual(2, other.Resumes); } public void QuarantinedArc_RunsAgainOnceReleased() { var thrower = new ThrowingSchedulerArc(); scheduler.Add(thrower); scheduler.Tick(world, At(sandbox, 0)); sandbox.Release(AriadneSites.Tick(thrower)); scheduler.Tick(world, At(sandbox, 1)); CheckEqual(2, thrower.Resumes); CheckEqual(0, scheduler.Count); } public void QuarantinedArc_IsHeldAndNotRun() { var thrower = new ThrowingSchedulerArc(); scheduler.Add(thrower); scheduler.Tick(world, At(sandbox, 0)); scheduler.Tick(world, At(sandbox, 1)); scheduler.Tick(world, At(sandbox, 2)); CheckEqual(1, thrower.Resumes); CheckEqual(1, scheduler.Count); CheckEqual(0, scheduler.SleepingCount); } public void ArcThatThrowsOnWakingFromAWait_IsHeldAwakeAndRunsAgainOnceReleased() { var napper = new NapThenThrowScript { Nap = TimeSpan.FromSeconds(20) }; scheduler.Add(napper); scheduler.Tick(world, At(sandbox, 0)); CheckEqual(1, scheduler.SleepingCount); scheduler.Tick(world, At(sandbox, 20)); scheduler.Tick(world, At(sandbox, 21)); CheckTrue(sandbox.IsQuarantined(AriadneSites.Tick(napper))); CheckEqual(0, scheduler.SleepingCount); CheckEqual(1, scheduler.Count); CheckEqual(1, napper.Wakes); sandbox.Release(AriadneSites.Tick(napper)); scheduler.Tick(world, At(sandbox, 22)); CheckEqual(2, napper.Wakes); CheckTrue(napper.Finished); CheckEqual(0, scheduler.Count); } public void RestartOfAQuarantinedArc_DoesNotReleaseIt() { var thrower = new ThrowingSchedulerArc(); scheduler.Add(thrower); scheduler.Tick(world, At(sandbox, 0)); scheduler.Restart(thrower); scheduler.Tick(world, At(sandbox, 1)); CheckEqual(1, thrower.Resumes); CheckTrue(sandbox.IsQuarantined(AriadneSites.Tick(thrower))); } public void ArcWithAnOverloadOfTickSelf_CanBeAddedAndMaySleep() { scheduler.Add(new OverloadedTickSelfArc()); scheduler.Tick(world, At(sandbox, 0)); CheckEqual(1, scheduler.Count); } public void TickRefusedByAnArc_KeepsRegistrationOrder() { var world = new SchedulerWorld(); foreach (string name in new[] { "a", "b", "c" }) { scheduler.Add(new RecordingArc { Name = name }); } try { scheduler.Tick(world, default(TickContext)); } catch (ArgumentException) { } scheduler.Tick(world, At(sandbox, 0)); scheduler.Tick(world, At(sandbox, 1)); CheckEqual(new[] { "a", "b", "c", "a", "b", "c" }, world.Events.ToArray()); } public void TickRefusedByAnArc_LeavesEveryArcInTheScheduler() { var first = new YieldingArc(); var second = new YieldingArc(); scheduler.Add(first); scheduler.Add(second); try { scheduler.Tick(world, default(TickContext)); } catch (ArgumentException) { } scheduler.Tick(world, At(sandbox, 0)); CheckEqual(2, scheduler.Count); CheckEqual(1, first.Resumes); CheckEqual(1, second.Resumes); } } public class AllocationTests : TestFixture { private SchedulerWorld world; private ExceptionSandbox sandbox; private ArcScheduler scheduler; public override void TestSetup() { world = new SchedulerWorld(); sandbox = TestTicks.NewSandbox(); scheduler = new ArcScheduler(); for (int i = 0; i < 100; ++i) { scheduler.Add(Sleeper(1000)); } for (int i = 0; i < 3; ++i) { scheduler.Add(new YieldingArc()); } scheduler.Tick(world, At(sandbox, 0)); } [AllocationTest] public void FrameWithSleepersAndActiveArcs_AllocatesNothing() { scheduler.Tick(world, At(sandbox, 1)); CheckNoAllocations(); } } public class SuspendAllocationTests : TestFixture { private SchedulerWorld world; private ExceptionSandbox sandbox; private ArcScheduler scheduler; private SuspenderArc[] suspenders; [IgnoreTest] public void WakeAll() { foreach (SuspenderArc suspender in suspenders) { scheduler.Wake(suspender); } } // One round of waking first, so everything a herd's wake grows is ready for the next. public override void TestSetup() { world = new SchedulerWorld(); sandbox = TestTicks.NewSandbox(); scheduler = new ArcScheduler(); suspenders = new SuspenderArc[100]; for (int i = 0; i < suspenders.Length; ++i) { suspenders[i] = new SuspenderArc(); scheduler.Add(suspenders[i]); } for (int i = 0; i < 3; ++i) { scheduler.Add(new YieldingArc()); } scheduler.Tick(world, At(sandbox, 0)); WakeAll(); scheduler.Tick(world, At(sandbox, 1)); } [AllocationTest] public void FrameWhereSuspendedArcsAreWokenAndSuspendAgain_AllocatesNothing() { WakeAll(); scheduler.Tick(world, At(sandbox, 2)); CheckNoAllocations(); CheckEqual(100, scheduler.SuspendedCount); } } public class HerdAllocationTests : TestFixture { private SchedulerWorld world; private ExceptionSandbox sandbox; private ArcScheduler scheduler; [IgnoreTest] public void AddHerd(double firstDeadlineSeconds) { for (int i = 0; i < 300; ++i) { scheduler.Add(Sleeper(firstDeadlineSeconds + (i % 7) * 0.001)); } } // One herd wakes and finishes first, so everything a herd's wake grows is ready for the next. public override void TestSetup() { world = new SchedulerWorld(); sandbox = TestTicks.NewSandbox(); scheduler = new ArcScheduler(); AddHerd(1); scheduler.Tick(world, At(sandbox, 0)); scheduler.Tick(world, At(sandbox, 2)); AddHerd(3); scheduler.Tick(world, At(sandbox, 2.5)); } [AllocationTest] public void FrameWhereAHerdOfMixedDeadlinesWakes_AllocatesNothing() { scheduler.Tick(world, At(sandbox, 4)); CheckNoAllocations(); CheckEqual(0, scheduler.Count); } } } }