using Amphora; using System; using System.Collections.Generic; using System.Linq; using UnitTestSharp; namespace Ariadne.UnitTests { public class ArcTests : TestFixture { public class Gates { public readonly HashSet Open = new HashSet(); } /// /// Hand-written in the form Ariadne.Generator emits: an intro pause, then passages act2 and act3, which both /// have a "connect" key. Log records run-once code. /// public class TutorialArc : Arc { public readonly List Log = new List(); public int Counter = 7; private string note = "initial"; public string Note { get { return note; } set { note = value; } } private bool Act2(Gates gates) { using (__Enter("act2")) { switch (__ResumeKey) { case "build": goto build; case "connect": goto connect; } Log.Add("act2 start"); build: if (!gates.Open.Contains("build")) { __Mark("build"); return false; } Log.Add("built"); connect: if (!gates.Open.Contains("connect")) { __Mark("connect"); return false; } __Exit(); return true; } } private bool Act3(Gates gates) { using (__Enter("act3")) { switch (__ResumeKey) { case "connect": goto connect; } Log.Add("act3 start"); connect: if (!gates.Open.Contains("act3 connect")) { __Mark("connect"); return false; } __Exit(); return true; } } protected override bool __Resume(Gates gates, TickTime time) { switch (__ResumeKey) { case "intro": goto intro; case "act2": goto act2; case "act3": goto act3; } Log.Add("start"); intro: if (!gates.Open.Contains("intro")) { __Mark("intro"); return false; } Log.Add("after intro"); act2: if (!Act2(gates)) { return false; } act3: if (!Act3(gates)) { return false; } Log.Add("end"); return true; } protected override void __SaveFields(IArcWriter writer) { writer.Field("Counter", Counter); writer.Field("note", note); } public TutorialArc() { } private TutorialArc(IArcReader reader) : base(reader) { int counter; if (reader.TryField("Counter", out counter)) { Counter = counter; } string loadedNote; if (reader.TryField("note", out loadedNote)) { note = loadedNote; } } protected override void Run(Gates gates) { } } public static readonly string[] EveryGate = { "intro", "build", "connect", "act3 connect" }; public class PassageFirstArc : Arc { public PassageFirstArc() { } private PassageFirstArc(IArcReader reader) : base(reader) { } private bool Only(Gates gates) { using (__Enter("only")) { switch (__ResumeKey) { case "wait": goto wait; } wait: if (!gates.Open.Contains("wait")) { __Mark("wait"); return false; } __Exit(); return true; } } protected override bool __Resume(Gates gates, TickTime time) { switch (__ResumeKey) { case "only": goto only; } only: return Only(gates); } protected override void Run(Gates gates) { } } /// /// Saves a local it never loads, so a save without it is missing something the arc needs. /// public class UnloadedLocalArc : Arc { public UnloadedLocalArc() { } private UnloadedLocalArc(IArcReader reader) : base(reader) { } protected override void __SaveLocals(IArcWriter writer) { writer.Local("Run.count", 0); } protected override bool __Resume(Gates gates, TickTime time) { return true; } protected override void Run(Gates gates) { } } public class GuardedArc : Arc { public void CallYield() { Yield(); } public void CallWait(TimeSpan span) { Wait(span); } public void CallWait(Trigger trigger) { Wait(trigger); } public void CallWait(Condition condition) { Wait(condition); } public Condition CallTimedWait(Condition condition, TimeSpan timeout) { return Wait(condition, timeout); } public Condition CallWaitAny(params Condition[] conditions) { return WaitAny(conditions); } public Condition CallWaitAll(params Condition[] conditions) { return WaitAll(conditions); } public Condition CallTimedWaitAny(TimeSpan timeout, params Condition[] conditions) { return WaitAny(timeout, conditions); } public Condition CallTimedWaitAll(TimeSpan timeout, params Condition[] conditions) { return WaitAll(timeout, conditions); } public Waitable CallWaitAny(params Waitable[] waitables) { return WaitAny(waitables); } public Waitable CallWaitAll(params Waitable[] waitables) { return WaitAll(waitables); } public Waitable CallTimedWaitAny(TimeSpan timeout, params Waitable[] waitables) { return WaitAny(timeout, waitables); } public Waitable CallTimedWaitAll(TimeSpan timeout, params Waitable[] waitables) { return WaitAll(timeout, waitables); } public Trigger CallWaitAny(params Trigger[] triggers) { return WaitAny(triggers); } public Trigger CallWaitAll(params Trigger[] triggers) { return WaitAll(triggers); } public Trigger CallTimedWait(Trigger trigger, TimeSpan timeout) { return Wait(trigger, timeout); } public Trigger CallTimedWaitAny(TimeSpan timeout, params Trigger[] triggers) { return WaitAny(timeout, triggers); } public Trigger CallTimedWaitAll(TimeSpan timeout, params Trigger[] triggers) { return WaitAll(timeout, triggers); } public Duration ReadGameTime() { return GameTime; } public Duration ReadDeltaTime() { return DeltaTime; } protected override bool __Resume(Gates gates, TickTime time) { return true; } protected override void Run(Gates gates) { } } public class TimeTests : TestFixture { public void GameTimeOutsideGeneratedCode_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().ReadGameTime(); } public void DeltaTimeOutsideGeneratedCode_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().ReadDeltaTime(); } } public class TimeRecordingArc : Arc { public TickTime Seen; protected override bool __Resume(Gates gates, TickTime time) { Seen = time; return false; } protected override void Run(Gates gates) { } } public class YieldTests : TestFixture { public void Always_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallYield(); } } public class WaitTests : TestFixture { public void ForATimeSpan_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallWait(TimeSpan.FromSeconds(1)); } public void ForATrigger_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallWait(new Trigger()); } public void ForACondition_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallWait(new Latch()); } public void ForAConditionWithATimeout_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallTimedWait(new Latch(), TimeSpan.FromSeconds(1)); } public void ForAnyCondition_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallWaitAny(new Latch()); } public void ForEveryCondition_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallWaitAll(new Latch()); } public void ForAnyConditionWithATimeout_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallTimedWaitAny(TimeSpan.FromSeconds(1), new Latch()); } public void ForEveryConditionWithATimeout_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallTimedWaitAll(TimeSpan.FromSeconds(1), new Latch()); } public void ForAnyOfMixedWaitables_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallWaitAny(new Trigger(), new Latch()); } public void ForEveryOfMixedWaitables_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallWaitAll(new Trigger(), new Latch()); } public void ForAnyOfMixedWaitablesWithATimeout_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallTimedWaitAny(TimeSpan.FromSeconds(1), new Trigger(), new Latch()); } public void ForEveryOfMixedWaitablesWithATimeout_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallTimedWaitAll(TimeSpan.FromSeconds(1), new Trigger(), new Latch()); } public void ForAnyTrigger_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallWaitAny(new Trigger()); } public void ForEveryTrigger_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallWaitAll(new Trigger()); } public void ForATriggerWithATimeout_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallTimedWait(new Trigger(), TimeSpan.FromSeconds(1)); } public void ForAnyTriggerWithATimeout_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallTimedWaitAny(TimeSpan.FromSeconds(1), new Trigger()); } public void ForEveryTriggerWithATimeout_Throws() { CheckThrow(typeof(InvalidOperationException)); new GuardedArc().CallTimedWaitAll(TimeSpan.FromSeconds(1), new Trigger()); } } public class TickTests : TestFixture { private TickContext tick; private TutorialArc arc; private Gates gates; public override void TestSetup() { tick = TestTicks.Fresh(); arc = new TutorialArc(); gates = new Gates(); } public void FreshArc_RunsItsOpeningCodeAndWaitsAtTheFirstLabel() { arc.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start" }, arc.Log); CheckEqual(new[] { "intro" }, arc.Cursor); } public void Resume_IsHandedTheTimeOfTheTick() { var timed = new TimeRecordingArc(); timed.Tick(gates, TestTicks.At(TestTicks.NewSandbox(), deltaTime: TimeSpan.FromSeconds(0.5), gameTime: TimeSpan.FromSeconds(12))); CheckEqual(Duration.FromSeconds(0.5), timed.Seen.DeltaTime); CheckEqual(Duration.FromSeconds(12), timed.Seen.GameTime); } public void StillWaiting_RerunsNothing() { arc.Tick(gates, TestTicks.Fresh()); arc.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start" }, arc.Log); } public void MetWait_RunsOnIntoTheNextPassage() { gates.Open.Add("intro"); arc.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start", "after intro", "act2 start" }, arc.Log); CheckEqual(new[] { "act2", "build" }, arc.Cursor); } public void CodeAfterAWait_RunsOnce() { gates.Open.Add("intro"); gates.Open.Add("build"); arc.Tick(gates, TestTicks.Fresh()); arc.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start", "after intro", "act2 start", "built" }, arc.Log); CheckEqual(new[] { "act2", "connect" }, arc.Cursor); } public void LastWaitMet_FinishesTheArc() { gates.Open.UnionWith(EveryGate); arc.Tick(gates, TestTicks.Fresh()); CheckTrue(arc.Finished); CheckEqual(new[] { "start", "after intro", "act2 start", "built", "act3 start", "end" }, arc.Log); } public void FinishedArc_RunsNothing() { gates.Open.UnionWith(EveryGate); arc.Tick(gates, TestTicks.Fresh()); arc.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start", "after intro", "act2 start", "built", "act3 start", "end" }, arc.Log); } public void UnknownKey_RestartsItsPassage() { arc = LoadedArc.From(new ArcRecord { Cursor = { "act2", "removed" } }); arc.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "act2 start" }, arc.Log); CheckEqual(new[] { "act2", "build" }, arc.Cursor); } public void UnknownTopLevelLabel_RestartsTheArc() { arc = LoadedArc.From(new ArcRecord { Cursor = { "removed" } }); arc.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start" }, arc.Log); CheckEqual(new[] { "intro" }, arc.Cursor); } public void PassageAfterAPassage_StartsFreshDespiteASharedKey() { arc = LoadedArc.From(new ArcRecord { Cursor = { "act2", "connect" } }); gates.Open.Add("connect"); arc.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "act3 start" }, arc.Log); CheckEqual(new[] { "act3", "connect" }, arc.Cursor); } public void PassageAsTheFirstStep_RecordsBothLevels() { var passageFirst = new PassageFirstArc(); passageFirst.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "only", "wait" }, passageFirst.Cursor); } public void Waiting_DropsStaleDeeperLabels() { arc = LoadedArc.From(new ArcRecord { Cursor = { "intro", "stale" } }); arc.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "intro" }, arc.Cursor); } [AllocationTest] public void WaitingInsideAPassage_AllocatesNothing() { gates.Open.Add("intro"); arc.Tick(gates, tick); CheckNoAllocations(); } } public class RestartTests : TestFixture { private TutorialArc arc; private Gates gates; public override void TestSetup() { arc = new TutorialArc(); gates = new Gates(); } public void FinishedArc_RunsFromTheTopAgain() { gates.Open.UnionWith(ScriptTests.EveryGate); arc.Tick(gates, TestTicks.Fresh()); arc.Restart(); arc.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start", "after intro", "act2 start", "built", "act3 start", "end", "start", "after intro", "act2 start", "built", "act3 start", "end" }, arc.Log); CheckTrue(arc.Finished); } public void FinishedArc_IsNotFinishedUntilTicked() { gates.Open.UnionWith(ScriptTests.EveryGate); arc.Tick(gates, TestTicks.Fresh()); arc.Restart(); CheckFalse(arc.Finished); } public void PausedArc_LeavesNoPathBehind() { gates.Open.Add("intro"); arc.Tick(gates, TestTicks.Fresh()); arc.Restart(); CheckEqual(0, arc.Cursor.Count); } public void PausedArc_StartsOverOnTheNextTick() { gates.Open.Add("intro"); arc.Tick(gates, TestTicks.Fresh()); arc.Restart(); arc.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start", "after intro", "act2 start", "start", "after intro", "act2 start" }, arc.Log); CheckEqual(new[] { "act2", "build" }, arc.Cursor); } public void ArcNeverTicked_ChangesNothing() { arc.Restart(); arc.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start" }, arc.Log); CheckEqual(new[] { "intro" }, arc.Cursor); } public void Fields_KeepTheirValues() { arc.Counter = 12; arc.Restart(); CheckEqual(12, arc.Counter); } } public class SaveTests : TestFixture { public void FreshArc_WritesAnEmptyCursor() { CheckEqual(0, ArcRecord.Of(new TutorialArc()).Cursor.Count); } public void WritesTheCursorsLabels() { var arc = new TutorialArc(); var gates = new Gates(); gates.Open.Add("intro"); arc.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "act2", "build" }, ArcRecord.Of(arc).Cursor); } public void WritesFinished() { var arc = new TutorialArc(); var gates = new Gates(); gates.Open.UnionWith(ScriptTests.EveryGate); arc.Tick(gates, TestTicks.Fresh()); CheckTrue(ArcRecord.Of(arc).Finished); } public void WritesWhatSaveFieldsWrites() { var arc = new TutorialArc(); arc.Counter = 12; arc.Note = "edited"; ArcRecord record = ArcRecord.Of(arc); CheckEqual(2, record.Fields.Count); CheckEqual(12, record.Fields["Counter"]); CheckEqual("edited", record.Fields["note"]); } public void ArcWithNoSaveCode_WritesOnlyItsPosition() { var arc = new PassageFirstArc(); arc.Tick(new Gates(), TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(arc); CheckEqual(new[] { "only", "wait" }, record.Cursor); CheckEqual(0, record.Fields.Count); CheckEqual(0, record.Locals.Count); } } public class LoadTests : TestFixture { public void RestoresCursorAndFinished() { var record = new ArcRecord { Cursor = { "act2", "connect" }, Finished = true }; var arc = LoadedArc.From(record); CheckEqual(new[] { "act2", "connect" }, arc.Cursor); CheckTrue(arc.Finished); } public void EmptyCursor_StartsTheArcFromTheTop() { var arc = LoadedArc.From(new ArcRecord()); arc.Tick(new Gates(), TestTicks.Fresh()); CheckEqual(new[] { "start" }, arc.Log); } public void RestoresSavedFields() { var record = new ArcRecord(); record.Fields["Counter"] = 12; record.Fields["note"] = "edited"; var arc = LoadedArc.From(record); CheckEqual(12, arc.Counter); CheckEqual("edited", arc.Note); } public void FieldMissingFromTheSave_KeepsItsInitialValue() { var arc = LoadedArc.From(new ArcRecord()); CheckEqual(7, arc.Counter); } public void ArcWithNoSaveCode_RestoresItsPosition() { var arc = LoadedArc.From(new ArcRecord { Cursor = { "only", "wait" } }); CheckEqual(new[] { "only", "wait" }, arc.Cursor); } public void PublishesTheArcBeforeReadingAnything() { var record = new ArcRecord(); var arc = LoadedArc.From(record); CheckEqual(1, record.Published.Count); CheckTrue(ReferenceEquals(arc, record.Published[0])); } public void ResumedArc_DoesNotRerunEarlierCode() { var gates = new Gates(); gates.Open.UnionWith(new[] { "intro", "build" }); var original = new TutorialArc(); original.Tick(gates, TestTicks.Fresh()); var resumed = LoadedArc.From(ArcRecord.Of(original)); gates.Open.UnionWith(EveryGate); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "act3 start", "end" }, resumed.Log); CheckTrue(resumed.Finished); } } public class VerifyLoadedTests : TestFixture { public void SaveWithoutLocals_PassesForAnArcThatNeedsNone() { var record = new ArcRecord(); LoadedArc.From(record).VerifyLoaded(record); } public void LocalTheArcLacks_Throws() { var record = new ArcRecord(); record.Locals["Run.removed"] = 1; var arc = LoadedArc.From(record); CheckThrow(typeof(NotImplementedException)); arc.VerifyLoaded(record); } public void NeededLocalMissingFromTheSave_Throws() { var record = new ArcRecord(); var arc = LoadedArc.From(record); CheckThrow(typeof(NotImplementedException)); arc.VerifyLoaded(record); } public static ArcRecord PausedInACall() { var client = new ClientScript(); client.Tick(new Gates(), TestTicks.Fresh()); return ArcRecord.Of(client); } public static string MessageOfVerifying(ArcRecord record) { try { LoadedArc.From(record).VerifyLoaded(record); } catch (NotImplementedException exception) { return exception.Message; } return null; } public void SaveInsideACall_PassesWhenItHoldsTheCallsLocals() { ArcRecord record = PausedInACall(); CheckNull(MessageOfVerifying(record)); } public void LocalInACallTheScriptLacks_ThrowsNamingTheCallAndTheLocal() { ArcRecord record = PausedInACall(); record.Scopes["Run.one/"].Locals["Add.removed"] = 1; CheckTrue(MessageOfVerifying(record).Contains("The save has local Run.one/Add.removed")); } public void LocalInACallMissingFromTheSave_ThrowsNamingTheCallAndTheLocal() { ArcRecord record = PausedInACall(); record.Scopes["Run.one/"].Locals.Remove("Add.seen"); CheckTrue(MessageOfVerifying(record).Contains("The save lacks local Run.one/Add.seen")); } public void CallTheScriptLacks_ThrowsNamingItsLocal() { ArcRecord record = PausedInACall(); ((IArcWriter)record).Scope("Run.gone/").Local("Gone.count", 1); CheckTrue(MessageOfVerifying(record).Contains("The save has local Run.gone/Gone.count")); } public void CallWithNothingInIt_IsTheSameAsNoCall() { ArcRecord record = PausedInACall(); ((IArcWriter)record).Scope("Run.gone/"); CheckNull(MessageOfVerifying(record)); } } public class TickFaultTests : TestFixture { private Gates exploding; private ExceptionSandbox sandbox; private FaultingScript script; public override void TestSetup() { exploding = new Gates(); exploding.Open.Add("explode"); sandbox = TestTicks.NewSandbox(); script = new FaultingScript(); } [IgnoreTest] private void Tick(Arc arc, Gates gates) { arc.Tick(gates, TestTicks.With(sandbox)); } public void ScriptThatThrows_DoesNotThrowOutOfTick() { Tick(script, exploding); CheckEqual(1, sandbox.Entries.Count); } public void ScriptThatThrows_IsRecordedWithItsException() { Tick(script, exploding); CheckEqual("boom", sandbox.Entries[0].Exception.Message); } public void ScriptThatThrows_IsQuarantinedAtItsTickSite() { Tick(script, exploding); CheckEqual(new[] { AriadneSites.Tick(script) }, sandbox.QuarantinedSites.ToArray()); } public void ScriptThatThrows_IsLeftUnfinishedWhereItPaused() { var gates = new Gates(); Tick(script, gates); gates.Open.Add("explode"); Tick(script, gates); CheckFalse(script.Finished); CheckEqual(new[] { "hold" }, script.Cursor.ToArray()); } public void QuarantinedArc_IsNotRunAgain() { Tick(script, exploding); Tick(script, exploding); Tick(script, exploding); CheckEqual(1, script.Attempts); CheckEqual(1, sandbox.Entries[0].Count); } public void ReleasedArc_RunsAgain() { Tick(script, exploding); sandbox.Release(AriadneSites.Tick(script)); exploding.Open.Remove("explode"); exploding.Open.Add("finish"); Tick(script, exploding); CheckTrue(script.Finished); } public void ReleasedArcThatThrowsAgain_IsQuarantinedAgain() { Tick(script, exploding); sandbox.Release(AriadneSites.Tick(script)); Tick(script, exploding); Tick(script, exploding); CheckEqual(2, script.Attempts); CheckTrue(sandbox.IsSkipped(AriadneSites.Tick(script))); } public void IgnoringTheEntry_DoesNotReleaseTheArc() { Tick(script, exploding); sandbox.Entries[0].Ignored = true; sandbox.Entries[0].Ignored = false; Tick(script, exploding); CheckEqual(1, script.Attempts); } public void OtherArcsOnTheSandbox_KeepRunning() { var healthy = new FaultingScript(); var calm = new Gates(); calm.Open.Add("finish"); Tick(script, exploding); Tick(healthy, calm); CheckTrue(healthy.Finished); } public void SameBugInSeveralArcs_IsOneEntryWithASiteEach() { var other = new FaultingScript(); Tick(script, exploding); Tick(other, exploding); CheckEqual(1, sandbox.Entries.Count); CheckEqual(2, sandbox.Entries[0].Sites.Count); CheckEqual(2, sandbox.Entries[0].Count); } public void Quarantine_IsAnnouncedOnTheNextAdvance() { ExceptionSite announced = default; sandbox.SiteQuarantined += (site, entry) => announced = site; Tick(script, exploding); CheckNull(announced.Target); sandbox.Advance(TimeSpan.Zero); CheckEqual(AriadneSites.Tick(script), announced); } public void ArcSavedWhileQuarantined_LoadsAndFaultsAgainOnAFreshSandbox() { Tick(script, exploding); FaultingScript loaded = LoadedArc.From(ArcRecord.Of(script)); ExceptionSandbox fresh = TestTicks.NewSandbox(); loaded.Tick(exploding, TestTicks.With(fresh)); CheckEqual(2, loaded.Attempts); CheckTrue(fresh.IsQuarantined(AriadneSites.Tick(loaded))); } public void DefaultTickContext_Throws() { CheckThrow(typeof(ArgumentException)); script.Tick(exploding, default(TickContext)); } public void DefaultTickContext_ThrowsEvenForAFinishedArc() { var calm = new Gates(); calm.Open.Add("finish"); Tick(script, calm); CheckThrow(typeof(ArgumentException)); script.Tick(calm, default(TickContext)); } } public class AddFaultNotesTests : TestFixture { private Gates gates; private ExceptionSandbox sandbox; public override void TestSetup() { gates = new Gates(); sandbox = TestTicks.NewSandbox(); } [IgnoreTest] private IReadOnlyDictionary NotesOfAFault(Arc arc) { arc.Tick(gates, TestTicks.With(sandbox)); gates.Open.Add("explode"); arc.Tick(gates, TestTicks.With(sandbox)); return sandbox.Entries[0].NotesOf(AriadneSites.Tick(arc)).Single(); } public void Always_NotesTheArcsClassAndCursor() { IReadOnlyDictionary notes = NotesOfAFault(new FaultingScript()); CheckEqual(typeof(FaultingScript).FullName, notes["Arc"]); CheckEqual("hold", notes["Cursor"]); } public void ArcWithNoCursorYet_NotesAnEmptyCursor() { gates.Open.Add("explode"); new FaultingScript().Tick(gates, TestTicks.With(sandbox)); CheckEqual("", sandbox.Entries[0].NotesOf(sandbox.Entries[0].Sites.Single()).Single()["Cursor"]); } public void ScriptThatOverrides_AddsItsOwnNotes() { IReadOnlyDictionary notes = NotesOfAFault(new NotingScript { Area = "attic" }); CheckEqual("attic", notes["Area"]); CheckEqual("hold", notes["Cursor"]); } public void ScriptWhoseNotesThrow_StillQuarantinesWithTheNotesSoFar() { var broken = new BrokenNotingScript(); IReadOnlyDictionary notes = NotesOfAFault(broken); CheckTrue(sandbox.IsQuarantined(AriadneSites.Tick(broken))); CheckEqual("kept", notes["Before"]); CheckEqual(typeof(InvalidOperationException).FullName, notes["AddFaultNotes threw"]); } } public class DeltaTimeAndGameTimeTests : TestFixture { private Gates gates; private ClockScript script; private ExceptionSandbox sandbox; public override void TestSetup() { gates = new Gates(); script = new ClockScript(); sandbox = TestTicks.NewSandbox(); } public void Tick_HandsTheScriptItsTimes() { script.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(16), TimeSpan.FromSeconds(5))); CheckEqual(TimeSpan.FromMilliseconds(16).Ticks, script.SeenDeltaTimeTicks); CheckEqual(TimeSpan.FromSeconds(5).Ticks, script.SeenGameTicks); } public void EachTick_HandsTheScriptItsOwnTimes() { script.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(16), TimeSpan.FromSeconds(5))); script.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(4), TimeSpan.FromSeconds(6))); CheckEqual(TimeSpan.FromMilliseconds(4).Ticks, script.SeenDeltaTimeTicks); CheckEqual(TimeSpan.FromSeconds(6).Ticks, script.SeenGameTicks); } public void Times_AreNotSaved() { script.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(16), TimeSpan.FromSeconds(5))); ArcRecord record = ArcRecord.Of(script); CheckFalse(record.Fields.ContainsKey("DeltaTime")); CheckFalse(record.Fields.ContainsKey("GameTime")); } } public class DeadlineTests : TestFixture { private Gates gates; private ExceptionSandbox sandbox; public override void TestSetup() { gates = new Gates(); sandbox = TestTicks.NewSandbox(); } public void BeforeTheDeadline_KeepsWaiting() { var script = new DeadlineScript(); script.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(16), TimeSpan.FromSeconds(10))); script.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(16), TimeSpan.FromSeconds(10.9))); CheckFalse(script.Done); } public void AtTheDeadline_Finishes() { var script = new DeadlineScript(); script.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(16), TimeSpan.FromSeconds(10))); script.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(16), TimeSpan.FromSeconds(11))); CheckTrue(script.Done); CheckTrue(script.Finished); } public void GameTimeThatStandsStill_KeepsWaiting() { var script = new DeadlineScript(); for (int i = 0; i < 5; ++i) { script.Tick(gates, TestTicks.At(sandbox, TimeSpan.Zero, TimeSpan.FromSeconds(10))); } CheckFalse(script.Done); } public void Deadline_SurvivesAReloadMidWait() { var script = new DeadlineScript(); script.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(16), TimeSpan.FromSeconds(10))); var reloaded = LoadedArc.From(ArcRecord.Of(script)); reloaded.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(16), TimeSpan.FromSeconds(10.5))); CheckFalse(reloaded.Done); reloaded.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(16), TimeSpan.FromSeconds(11))); CheckTrue(reloaded.Done); } public void RestartedWait_TakesItsDeadlineFromTheNewStart() { var script = new DeadlineScript(); script.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(16), TimeSpan.FromSeconds(10))); script.Restart(); script.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(16), TimeSpan.FromSeconds(20))); script.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(16), TimeSpan.FromSeconds(20.9))); CheckFalse(script.Done); } } public class LendTimeTests : TestFixture { private Gates gates; private TimeClientScript client; private ExceptionSandbox sandbox; public override void TestSetup() { gates = new Gates(); client = new TimeClientScript(); sandbox = TestTicks.NewSandbox(); } public void PassageRunOnAnotherArc_SeesTheCallersTimes() { gates.Open.Add("go"); client.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(16), TimeSpan.FromSeconds(5))); CheckEqual(TimeSpan.FromMilliseconds(16).Ticks, client.Worker.SeenDeltaTimeTicks); CheckEqual(TimeSpan.FromSeconds(5).Ticks, client.Worker.SeenGameTicks); } public void PassageResumedAfterAPause_SeesTheResumingTicksTimes() { client.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(16), TimeSpan.FromSeconds(5))); gates.Open.Add("go"); client.Tick(gates, TestTicks.At(sandbox, TimeSpan.FromMilliseconds(3), TimeSpan.FromSeconds(8))); CheckEqual(TimeSpan.FromMilliseconds(3).Ticks, client.Worker.SeenDeltaTimeTicks); CheckEqual(TimeSpan.FromSeconds(8).Ticks, client.Worker.SeenGameTicks); } } public class TickSelfTests : TestFixture { private Gates gates; private ExceptionSandbox sandbox; public override void TestSetup() { gates = new Gates(); gates.Open.Add("finish"); sandbox = TestTicks.NewSandbox(); } public void ChildTickedFirst_IsFinishedByTheTimeTheScriptLooks() { var parent = new ParentScript(); parent.Tick(gates, TestTicks.With(sandbox)); CheckEqual(new[] { "child", "start", "end" }, parent.Log.ToArray()); CheckTrue(parent.Finished); } public void ChildTickedLast_IsSeenByTheScriptAtTheNextTick() { var parent = new ParentScript { ChildFirst = false }; parent.Tick(gates, TestTicks.With(sandbox)); CheckEqual(new[] { "start", "self", "child" }, parent.Log.ToArray()); CheckFalse(parent.Finished); parent.Tick(gates, TestTicks.With(sandbox)); CheckEqual(new[] { "start", "self", "child", "end", "child" }, parent.Log.ToArray()); CheckTrue(parent.Finished); } public void OverrideThatSkipsTheScript_CanFinishTheArc() { var skipping = new SkippingScript(); skipping.Tick(gates, TestTicks.With(sandbox)); CheckTrue(skipping.Finished); CheckFalse(skipping.Ran); } public void OverrideThatReturnsFalse_KeepsTheArcRunning() { var skipping = new SkippingScript(); skipping.Tick(new Gates(), TestTicks.With(sandbox)); CheckFalse(skipping.Finished); CheckEqual(1, skipping.Turns); } public void FinishedArc_IsNotGivenATurn() { var skipping = new SkippingScript(); skipping.Tick(gates, TestTicks.With(sandbox)); skipping.Tick(gates, TestTicks.With(sandbox)); CheckEqual(1, skipping.Turns); } public void QuarantinedArc_IsNotGivenATurn() { var throwing = new ThrowingTurnScript(); throwing.Tick(gates, TestTicks.With(sandbox)); throwing.Tick(gates, TestTicks.With(sandbox)); throwing.Tick(gates, TestTicks.With(sandbox)); CheckEqual(1, throwing.Turns); } public void OverrideThatThrows_QuarantinesTheArcAndSwallowsIt() { var throwing = new ThrowingTurnScript(); throwing.Tick(gates, TestTicks.With(sandbox)); CheckTrue(sandbox.IsQuarantined(AriadneSites.Tick(throwing))); CheckEqual(1, sandbox.Entries.Count); CheckEqual("turn", sandbox.Entries[0].Exception.Message); } public void OverrideThatThrows_KeepsWhatItsChildrenDidBeforeTheThrow() { var throwing = new ThrowingTurnScript(); throwing.Tick(gates, TestTicks.With(sandbox)); CheckTrue(throwing.Child.Finished); } public void ReleasedArcWhoseTurnThrew_IsGivenATurnAgain() { var throwing = new ThrowingTurnScript(); throwing.Tick(gates, TestTicks.With(sandbox)); sandbox.Release(AriadneSites.Tick(throwing)); throwing.Tick(gates, TestTicks.With(sandbox)); CheckEqual(2, throwing.Turns); CheckEqual(1, sandbox.Entries.Count); } public void ChildThatThrows_QuarantinesOnlyTheChild() { var exploding = new Gates(); exploding.Open.Add("explode"); var parent = new ParentScript(); parent.Tick(exploding, TestTicks.With(sandbox)); CheckTrue(sandbox.IsQuarantined(AriadneSites.Tick(parent.Child))); CheckFalse(sandbox.IsQuarantined(AriadneSites.Tick(parent))); CheckEqual(new[] { "child", "start", "self" }, parent.Log.ToArray()); } public void ArcTickingItself_IsQuarantinedAndTheEntryNamesTheCause() { var ticking = new SelfTickingScript(); ticking.Tick(gates, TestTicks.With(sandbox)); CheckTrue(sandbox.IsQuarantined(AriadneSites.Tick(ticking))); CheckTrue(sandbox.Entries[0].Exception is InvalidOperationException); CheckTrue(sandbox.Entries[0].Exception.Message.Contains("already being ticked")); } public void CycleOfArcs_QuarantinesTheArcThatClosedIt() { var first = new LoopingScript(); var second = new LoopingScript { Other = first }; first.Other = second; first.Tick(gates, TestTicks.With(sandbox)); CheckTrue(sandbox.IsQuarantined(AriadneSites.Tick(second))); CheckFalse(sandbox.IsQuarantined(AriadneSites.Tick(first))); CheckEqual(1, first.Turns); CheckEqual(1, second.Turns); } public void ArcThatFinishedATick_CanBeTickedAgain() { var looping = new LoopingScript(); looping.Tick(gates, TestTicks.With(sandbox)); looping.Tick(gates, TestTicks.With(sandbox)); CheckEqual(2, looping.Turns); } } } }