using System; using System.Collections.Generic; using System.Linq; using UnitTestSharp; namespace Ariadne.UnitTests { /// /// Scripts as users write them, run through the code Ariadne.Generator writes from them. /// public class ScriptTests : TestFixture { public static readonly string[] EveryGate = { "intro", "build", "connect", "act3 connect" }; public class TickTests : TestFixture { private TickContext tick; private TutorialScript script; private LocalsScript locals; private ArcTests.Gates gates; public override void TestSetup() { tick = TestTicks.Fresh(); script = new TutorialScript(); locals = new LocalsScript(); gates = new ArcTests.Gates(); } public void FreshScript_RunsItsOpeningCodeAndPausesAtTheFirstLoop() { script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start" }, script.Log); CheckEqual(new[] { "intro" }, script.Cursor); } public void StillPaused_RerunsNothing() { script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start" }, script.Log); } public void FinishedLoop_RunsOnIntoTheNextPassage() { gates.Open.Add("intro"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start", "after intro", "act2 start" }, script.Log); CheckEqual(new[] { "Act2", "build" }, script.Cursor); } public void CodeAfterALoop_RunsOnce() { gates.Open.UnionWith(new[] { "intro", "build" }); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start", "after intro", "act2 start", "built" }, script.Log); CheckEqual(new[] { "Act2", "connect" }, script.Cursor); } public void LastLoopDone_FinishesTheArc() { gates.Open.UnionWith(EveryGate); script.Tick(gates, TestTicks.Fresh()); CheckTrue(script.Finished); CheckEqual(new[] { "start", "after intro", "act2 start", "built", "act3 start", "end" }, script.Log); } public void PassageAfterAPassage_StartsFreshDespiteASharedStepName() { script = LoadedArc.From(new ArcRecord { Cursor = { "Act2", "connect" } }); gates.Open.Add("connect"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "act3 start" }, script.Log); CheckEqual(new[] { "Act3", "connect" }, script.Cursor); } public void UnknownKey_RestartsItsPassage() { script = LoadedArc.From(new ArcRecord { Cursor = { "Act2", "removed" } }); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "act2 start" }, script.Log); CheckEqual(new[] { "Act2", "build" }, script.Cursor); } public void RepeatedPassage_IsKeyedByEachCallsLabel() { var repeated = new RepeatedPassageScript(); gates.Open.Add("read"); repeated.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "second", "read" }, repeated.Cursor); } public void SecondLoopInAStep_IsKeyedWithItsCount() { var twoPauses = new TwoPausesInOneStepScript(); gates.Open.Add("breaker"); twoPauses.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "connect#1" }, twoPauses.Cursor); } public void FinishedArc_LeavesNoPathBehind() { gates.Open.UnionWith(EveryGate); script.Tick(gates, TestTicks.Fresh()); CheckEqual(0, script.Cursor.Count); } public void PassageCalledAgainInTheTickItEnds_StartsFromTheTop() { var looped = new LoopedPassageScript(); looped.Tick(gates, TestTicks.Fresh()); gates.Open.Add("A"); looped.Tick(gates, TestTicks.Fresh()); gates.Open.Add("B"); looped.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "step begins", "A seen", "step ends", "step begins", "A seen", "step ends" }, looped.Log); CheckTrue(looped.Finished); } public void PassageEndedByAReturn_StartsFromTheTopWhenCalledAgain() { var looped = new LoopedEarlyReturnScript(); looped.Tick(gates, TestTicks.Fresh()); gates.Open.UnionWith(new[] { "A", "skip" }); looped.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "step begins", "step ends", "step begins" }, looped.Log); CheckTrue(looped.Finished); } public void BareReturn_EndsThePassage() { var earlyReturn = new EarlyReturnScript(); gates.Open.Add("skip"); earlyReturn.Tick(gates, TestTicks.Fresh()); CheckTrue(earlyReturn.Finished); CheckEqual(0, earlyReturn.Log.Count); } public void Exception_ReportsTheScriptFileNotTheCodeBehind() { gates.Open.Add("explode"); var sandbox = TestTicks.NewSandbox(); new ThrowingScript().Tick(gates, TestTicks.With(sandbox)); Exception thrown = sandbox.Entries[0].Exception; string file = new System.Diagnostics.StackTrace(thrown, fNeedFileInfo: true).GetFrame(0).GetFileName(); CheckTrue(file.EndsWith("TestScripts.cs", StringComparison.OrdinalIgnoreCase)); } public void LocalChangedWhilePaused_KeepsItsValue() { locals.Tick(gates, TestTicks.Fresh()); locals.Tick(gates, TestTicks.Fresh()); locals.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); locals.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "hello", "waited 3" }, locals.Log); } public void PassageParameter_KeepsItsValueWhilePaused() { gates.Open.Add("go"); locals.Tick(gates, TestTicks.Fresh()); gates.Open.Add("crate"); locals.Tick(gates, TestTicks.Fresh()); gates.Open.Add("crate"); locals.Tick(gates, TestTicks.Fresh()); locals.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "hello", "waited 0", "delivered" }, locals.Log); CheckTrue(locals.Finished); } public void PassageArgument_IsEvaluatedOnlyOnTheWayIn() { gates.Open.Add("go"); locals.Tick(gates, TestTicks.Fresh()); locals.Tick(gates, TestTicks.Fresh()); locals.Tick(gates, TestTicks.Fresh()); CheckEqual(1, locals.ArgumentEvaluations); } public void LambdaReadingASavedLocal_SeesItsValue() { var captured = new CapturedLocalScript(); gates.Open.Add("go"); captured.Tick(gates, TestTicks.Fresh()); CheckTrue(captured.Finished); } [AllocationTest] public void PausedInsideAPassage_AllocatesNothing() { gates.Open.Add("intro"); script.Tick(gates, tick); CheckNoAllocations(); } [AllocationTest] public void PausedWithSavedLocals_AllocatesNothing() { gates.Open.Add("go"); locals.Tick(gates, tick); CheckNoAllocations(); } } public class SaveTests : TestFixture { public void FreshScript_SavesNoLocals() { CheckEqual(0, ArcRecord.Of(new LocalsScript()).Locals.Count); } public void PausedScript_SavesOnlyTheLocalsStillNeeded() { var script = new LocalsScript(); var gates = new ArcTests.Gates(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(script); CheckEqual(1, record.Locals.Count); CheckEqual(2, record.Locals["Run.waits"]); } public void PausedInAPassage_SavesItsParameterButNotTheCallersFinishedLocals() { var script = new LocalsScript(); var gates = new ArcTests.Gates(); gates.Open.Add("go"); script.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(script); CheckEqual(1, record.Locals.Count); CheckEqual(2, record.Locals["Deliver.crates"]); } public void ContextParameter_IsNotSaved() { var script = new TutorialScript(); script.Tick(new ArcTests.Gates(), TestTicks.Fresh()); CheckEqual(0, ArcRecord.Of(script).Locals.Count); } public void ScratchAndReadonlyFields_AreNotSaved() { CheckEqual(0, ArcRecord.Of(new LocalsScript()).Fields.Count); } public void FieldsAndLocals_AreWrittenAsTheirDeclaredTypes() { var script = new UserTypesScript(); script.Tick(new ArcTests.Gates(), TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(script); CheckEqual(typeof(Waypoint), record.Types["Destination"]); CheckEqual(typeof(IList), record.Types["Visits"]); CheckEqual(typeof(Point), record.Types["Run.start"]); } public void BaseClassFields_AreSavedAlongsideTheScripts() { var script = new MissionScript(); script.Reward = 9; script.Title = "relief"; script.Tick(new ArcTests.Gates(), TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(script); CheckEqualCommutative(new[] { "Reward", "Title", "Stage" }, record.Fields.Keys); CheckEqual(9, record.Fields["Reward"]); CheckEqual("relief", record.Fields["Title"]); CheckEqual(1, record.Fields["Stage"]); } } public class LoadTests : TestFixture { public static ArcRecord PausedAtWait(params object[] keysAndValues) { var returnMe = new ArcRecord { Cursor = { "wait" } }; for (int i = 0; i < keysAndValues.Length; i += 2) { returnMe.Locals[(string)keysAndValues[i]] = keysAndValues[i + 1]; } return returnMe; } public void RestoresALocal() { var resumed = LoadedArc.From(PausedAtWait("Run.waits", 4)); var gates = new ArcTests.Gates(); gates.Open.Add("go"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "waited 4" }, resumed.Log); } public void SavedInsideAPassage_ResumesWithItsParameter() { var gates = new ArcTests.Gates(); gates.Open.UnionWith(new[] { "go", "crate" }); var original = new LocalsScript(); original.Tick(gates, TestTicks.Fresh()); var resumed = LoadedArc.From(ArcRecord.Of(original)); gates.Open.Add("crate"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(0, ArcRecord.Of(resumed).Locals["Deliver.crates"]); CheckEqual(0, resumed.ArgumentEvaluations); } public void LambdaReadingASavedLocal_SeesTheRestoredValue() { var resumed = LoadedArc.From(PausedAtWait("Run.wanted", "other")); var gates = new ArcTests.Gates(); gates.Open.Add("other"); resumed.Tick(gates, TestTicks.Fresh()); CheckTrue(resumed.Finished); } public void LocalTheScriptLacks_Throws() { ArcRecord record = PausedAtWait("Run.waits", 1, "Run.removed", 1); var script = LoadedArc.From(record); CheckThrow(typeof(NotImplementedException)); script.VerifyLoaded(record); } public void NeededLocalMissingFromTheSave_Throws() { ArcRecord record = PausedAtWait(); var script = LoadedArc.From(record); CheckThrow(typeof(NotImplementedException)); script.VerifyLoaded(record); } public void LocalsTheScriptKeeps_PassVerification() { ArcRecord record = PausedAtWait("Run.waits", 4); var script = LoadedArc.From(record); script.VerifyLoaded(record); } public void LocalTheReaderCannotFind_KeepsItsValue() { var resumed = LoadedArc.From( new UnreadableLocals { Cursor = { "wait" }, Keys = { "Run.waits" } }); var gates = new ArcTests.Gates(); gates.Open.Add("go"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "waited 0" }, resumed.Log); } public void UserTypes_SurviveASaveAndLoad() { var gates = new ArcTests.Gates(); var original = new UserTypesScript(); original.Tick(gates, TestTicks.Fresh()); original.Destination = new Waypoint { Name = "midway" }; var resumed = LoadedArc.From(ArcRecord.Of(original)); string loadedName = resumed.Destination.Name; gates.Open.Add("go"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual("midway", loadedName); CheckEqual(1.5, resumed.Destination.At.X); CheckEqual(-2.0, resumed.Destination.At.Y); } public void BaseClassFields_AreRestoredAlongsideTheScripts() { var record = new ArcRecord { Cursor = { "wait" } }; record.Fields["Reward"] = 9; record.Fields["Title"] = "relief"; record.Fields["Stage"] = 3; var resumed = LoadedArc.From(record); CheckEqual(9, resumed.Reward); CheckEqual("relief", resumed.Title); CheckEqual(3, resumed.Stage); CheckEqual(1, resumed.Cache); } public void ResumedScript_DoesNotRerunEarlierCode() { var gates = new ArcTests.Gates(); gates.Open.UnionWith(new[] { "intro", "build" }); var original = new TutorialScript(); 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 NestedPauseTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public static ScriptType Reloaded(ScriptType paused) where ScriptType : Arc { return LoadedArc.From(ArcRecord.Of(paused)); } public void TopLevelYield_PausesBeforeAnyBranch() { var script = new BranchingScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start" }, script.Log); CheckEqual(new[] { "first" }, script.Cursor); } public void ThenBranch_PausesInsideIt() { gates.Open.Add("left"); var script = new BranchingScript(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start", "after first", "left" }, script.Log); CheckEqual(new[] { "second" }, script.Cursor); } public void ElseIfBranch_PausesInsideIt() { gates.Open.Add("middle"); var script = new BranchingScript(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start", "after first", "middle" }, script.Log); CheckEqual(new[] { "third" }, script.Cursor); } public void ElseBranch_PausesInsideIt() { var script = new BranchingScript(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start", "after first", "right" }, script.Log); CheckEqual(new[] { "fourth" }, script.Cursor); } public void ResumedInsideABranch_FinishesThatBranchAndRunsOn() { gates.Open.Add("left"); var script = new BranchingScript(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start", "after first", "left", "left done", "end" }, script.Log); CheckTrue(script.Finished); } public void LoadedInsideAThenBranch_FinishesIt() { gates.Open.Add("left"); var original = new BranchingScript(); original.Tick(gates, TestTicks.Fresh()); original.Tick(gates, TestTicks.Fresh()); BranchingScript resumed = Reloaded(original); gates.Open.Clear(); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "left done", "end" }, resumed.Log); CheckTrue(resumed.Finished); } public void LoadedInsideAnElseIfBranch_FinishesIt() { gates.Open.Add("middle"); var original = new BranchingScript(); original.Tick(gates, TestTicks.Fresh()); original.Tick(gates, TestTicks.Fresh()); BranchingScript resumed = Reloaded(original); gates.Open.Clear(); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "middle done", "end" }, resumed.Log); CheckTrue(resumed.Finished); } public void LoadedInsideAnElseBranch_FinishesIt() { var original = new BranchingScript(); original.Tick(gates, TestTicks.Fresh()); original.Tick(gates, TestTicks.Fresh()); BranchingScript resumed = Reloaded(original); gates.Open.Add("left"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "right done", "end" }, resumed.Log); CheckTrue(resumed.Finished); } public void LoadedAtAPauseBeforeAnIf_ChoosesTheBranchWhenItGetsThere() { var original = new BranchingScript(); original.Tick(gates, TestTicks.Fresh()); BranchingScript resumed = Reloaded(original); gates.Open.Add("middle"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "after first", "middle" }, resumed.Log); CheckEqual(new[] { "third" }, resumed.Cursor); } public void BlockInsideAnIf_PausesAndResumesInsideIt() { gates.Open.Add("outer"); var original = new NestedBlocksScript(); original.Tick(gates, TestTicks.Fresh()); NestedBlocksScript resumed = Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "outer", "inner" }, original.Log); CheckEqual(new[] { "deep" }, original.Cursor); CheckEqual(new[] { "inner done", "outer done" }, resumed.Log); CheckEqual(new[] { "after" }, resumed.Cursor); } public void IfSkipped_RunsOnToThePauseAfterIt() { var script = new NestedBlocksScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(0, script.Log.Count); CheckEqual(new[] { "after" }, script.Cursor); } public void PauseAfterAnIfThatHeldPauses_ResumesPastTheIf() { gates.Open.Add("outer"); var original = new NestedBlocksScript(); original.Tick(gates, TestTicks.Fresh()); original.Tick(gates, TestTicks.Fresh()); NestedBlocksScript resumed = Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "end" }, resumed.Log); CheckTrue(resumed.Finished); } public void ConditionOfTheIfResumedInside_IsNotRunAgain() { gates.Open.Add("go"); var script = new ConditionProbeScript(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); CheckEqual(1, script.Probes); CheckEqual(new[] { "inside", "end" }, script.Log); } public void ConditionOfTheIfLoadedInside_IsNotRunAgain() { gates.Open.Add("go"); var original = new ConditionProbeScript(); original.Tick(gates, TestTicks.Fresh()); ConditionProbeScript resumed = Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(0, resumed.Probes); CheckEqual(new[] { "inside", "end" }, resumed.Log); } public void LocalInAThenBranch_KeepsItsValueAcrossASaveAndLoad() { gates.Open.Add("big"); var original = new BranchLocalsScript(); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); BranchLocalsScript resumed = Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(10, record.Locals["Run.total"]); CheckEqual(1, record.Locals.Count); CheckEqual(new[] { "big 11" }, resumed.Log); } public void SameNamedLocalInAnElseBranch_IsSavedUnderItsOwnKey() { var original = new BranchLocalsScript(); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); BranchLocalsScript resumed = Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(1, record.Locals["Run.total#1"]); CheckEqual(1, record.Locals.Count); CheckEqual(new[] { "small 2" }, resumed.Log); } public void LocalOnlyAssignedAfterThePause_IsNotSaved() { gates.Open.Add("go"); var script = new AcrossScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(0, ArcRecord.Of(script).Locals.Count); } public void LocalOnlyAssignedAfterThePause_HasItsValueOnceResumed() { gates.Open.Add("go"); var script = new AcrossScript(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "doubled 2" }, script.Log); } public void TopLevelLocalChangedOnBothSidesOfAPause_KeepsItsValueAcrossASaveAndLoad() { gates.Open.Add("go"); var original = new SharedCounterScript(); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); SharedCounterScript resumed = Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(1, record.Locals["Run.visits"]); CheckEqual(new[] { "visits 2" }, resumed.Log); } public void PassageCalledInAnIf_PausesAndResumesInsideIt() { gates.Open.Add("brief"); var original = new NestedKindsScript(); original.Tick(gates, TestTicks.Fresh()); NestedKindsScript resumed = Reloaded(original); gates.Open.Add("read"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "Briefing", "read" }, original.Cursor); CheckEqual(new[] { "briefed", "after brief" }, resumed.Log); CheckTrue(resumed.Finished); } public void LoopInAnElse_PausesAndResumesInsideIt() { var original = new NestedKindsScript(); original.Tick(gates, TestTicks.Fresh()); NestedKindsScript resumed = Reloaded(original); gates.Open.Add("go"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "wait" }, original.Cursor); CheckEqual(new[] { "went" }, resumed.Log); CheckTrue(resumed.Finished); } } public class LoopPauseTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void WhileLoop_PausesInTheMiddleOfEachTrip() { var script = new WhileMiddleScript(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "before 1", "after 1", "before 2" }, script.Log); CheckEqual(new[] { "wait" }, script.Cursor); } public void WhileLoop_RunsToItsEnd() { var script = new WhileMiddleScript(); for (int tick = 0; tick < 5; ++tick) { script.Tick(gates, TestTicks.Fresh()); } CheckEqual(new[] { "before 1", "after 1", "before 2", "after 2", "before 3", "after 3", "before 4", "after 4", "done 4" }, script.Log); CheckTrue(script.Finished); } public void ContinueBeforeThePause_StartsTheNextTrip() { var script = new WhileMiddleScript(); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("skip2"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "before 1", "after 1", "before 3" }, script.Log); } public void BreakAfterAResume_LeavesTheLoop() { var script = new WhileMiddleScript(); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("stop2"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "before 1", "after 1", "done 2" }, script.Log); CheckTrue(script.Finished); } public void WhileLoopLoadedMidTrip_KeepsItsCounter() { var original = new WhileMiddleScript(); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); WhileMiddleScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("stop2"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(1, record.Locals["Run.round"]); CheckEqual(new[] { "after 1", "done 2" }, resumed.Log); CheckTrue(resumed.Finished); } public void ForLoop_PausesOnEachTripAndThenEnds() { var script = new ForPauseScript(); for (int tick = 0; tick < 4; ++tick) { script.Tick(gates, TestTicks.Fresh()); } CheckEqual(new[] { "i 0", "i 1", "i 2", "end" }, script.Log); CheckTrue(script.Finished); } public void ForLoopLoadedMidLoop_RestoresItsCounter() { var original = new ForPauseScript(); original.Tick(gates, TestTicks.Fresh()); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); ForPauseScript resumed = NestedPauseTests.Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(1, record.Locals["Run.i"]); CheckEqual(new[] { "i 2" }, resumed.Log); } public void DoLoop_TestsItsConditionAfterTheBody() { var script = new DoPauseScript(); for (int tick = 0; tick < 4; ++tick) { script.Tick(gates, TestTicks.Fresh()); } CheckEqual(new[] { "do 0", "do 1", "do 2", "done" }, script.Log); CheckTrue(script.Finished); } public void DoLoopLoadedMidLoop_RestoresItsCounter() { var original = new DoPauseScript(); original.Tick(gates, TestTicks.Fresh()); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); DoPauseScript resumed = NestedPauseTests.Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(2, record.Locals["Run.count"]); CheckEqual(new[] { "do 2" }, resumed.Log); } public void LoopInsideAForLoop_PausesInTheInnerOne() { var script = new NestedLoopsScript(); for (int tick = 0; tick < 5; ++tick) { script.Tick(gates, TestTicks.Fresh()); } CheckEqual(new[] { "j0k0", "j0k1", "row 0", "j1k0", "j1k1", "row 1" }, script.Log); CheckTrue(script.Finished); } public void NestedLoopsLoadedInTheInnerOne_RestoresBothCounters() { var original = new NestedLoopsScript(); original.Tick(gates, TestTicks.Fresh()); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); NestedLoopsScript resumed = NestedPauseTests.Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(0, record.Locals["Run.j"]); CheckEqual(2, record.Locals["Run.k"]); CheckEqual(new[] { "row 0", "j1k0" }, resumed.Log); } public void ContinueInAForLoop_RunsTheStepBeforeTheNextTest() { var script = new ForContinueScript(); for (int tick = 0; tick < 4; ++tick) { script.Tick(gates, TestTicks.Fresh()); } CheckEqual(new[] { "i 0", "i 2", "i 3", "end" }, script.Log); CheckTrue(script.Finished); } public void ContinueInADoLoop_TestsTheConditionBeforeTheNextTrip() { var script = new DoContinueScript(); for (int tick = 0; tick < 3; ++tick) { script.Tick(gates, TestTicks.Fresh()); } CheckEqual(new[] { "n 1", "n 3", "end" }, script.Log); CheckTrue(script.Finished); } public void LocalDeclaredInALoopBody_KeepsItsValueAcrossASaveAndLoad() { var original = new LoopBodyLocalsScript(); original.Tick(gates, TestTicks.Fresh()); LoopBodyLocalsScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("changed"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "count 1 seen 0" }, resumed.Log); } public void ContinueInsideASwitch_StartsTheNextTrip() { var script = new LoopBodyLocalsScript(); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("changed"); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "count 1 seen 0", "count 3 seen 1", "end" }, script.Log); CheckTrue(script.Finished); } } public class SwitchPauseTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void SectionWithAPause_PausesInsideIt() { gates.Open.Add("a"); var script = new SwitchPauseScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "one" }, script.Log); CheckEqual(new[] { "first" }, script.Cursor); } public void ResumedSection_FinishesAndRunsOnPastTheSwitch() { gates.Open.Add("a"); var script = new SwitchPauseScript(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "one", "one done", "end" }, script.Log); CheckTrue(script.Finished); } public void SectionWithSeveralLabels_RunsForEachOfThem() { gates.Open.Add("b"); var script = new SwitchPauseScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "two or three" }, script.Log); CheckEqual(new[] { "second" }, script.Cursor); } public void SectionWithoutAPause_RunsStraightThrough() { var script = new SwitchPauseScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "other", "end" }, script.Log); CheckTrue(script.Finished); } public void LoadedInsideASection_FinishesItWithoutChoosingAgain() { gates.Open.Add("a"); var original = new SwitchPauseScript(); original.Tick(gates, TestTicks.Fresh()); SwitchPauseScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Clear(); gates.Open.Add("b"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "one done", "end" }, resumed.Log); CheckTrue(resumed.Finished); } public void ContinueInASwitchInALoop_StartsTheNextTrip() { var script = new SwitchLoopScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "other 1", "after 1", "three" }, script.Log); CheckEqual(new[] { "wait" }, script.Cursor); } public void LoadedInsideASwitchInALoop_RestoresTheCounter() { var original = new SwitchLoopScript(); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); SwitchLoopScript resumed = NestedPauseTests.Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(3, record.Locals["Run.n"]); CheckEqual(new[] { "after 3", "end" }, resumed.Log); CheckTrue(resumed.Finished); } public void GotoCase_RunsTheOtherSectionBeforeItPauses() { var script = new GotoCaseScript(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "one", "two", "after" }, script.Log); CheckTrue(script.Finished); } public void LoadedAfterAGotoCase_ResumesInTheSectionItReached() { var original = new GotoCaseScript(); original.Tick(gates, TestTicks.Fresh()); GotoCaseScript resumed = NestedPauseTests.Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "after" }, resumed.Log); CheckEqual(new[] { "step" }, original.Cursor); } } public class ExpressionVariableTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void OutVariableInAGuardClause_KeepsItsValueAcrossASaveAndLoad() { var original = new GuardClauseScript(); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); GuardClauseScript resumed = NestedPauseTests.Reloaded(original); resumed.Text = "unused"; resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(5, record.Locals["Run.count"]); CheckEqual(new[] { "count 5" }, resumed.Log); } public void GuardClauseThatFails_ReturnsBeforeAnyPause() { var script = new GuardClauseScript { Text = "x" }; script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "bad" }, script.Log); CheckTrue(script.Finished); } public void PatternVariableInAnIf_KeepsItsValueAcrossASaveAndLoad() { var original = new PatternIfScript { Thing = "hi" }; original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); PatternIfScript resumed = NestedPauseTests.Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual("hi", record.Locals["Run.text"]); CheckEqual(new[] { "text hi" }, resumed.Log); } public void PatternThatDoesNotMatch_TakesTheElseBranch() { var script = new PatternIfScript { Thing = 7 }; script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "none" }, script.Log); CheckTrue(script.Finished); } public void OutVariableInALoopCondition_KeepsTheItemAcrossASaveAndLoad() { var original = new OutLoopScript(); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); OutLoopScript resumed = NestedPauseTests.Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(2, record.Locals["Run.item"]); CheckEqual(new[] { "item 2" }, resumed.Log); } public void OutLoop_RunsToItsEnd() { var script = new OutLoopScript(); for (int tick = 0; tick < 3; ++tick) { script.Tick(gates, TestTicks.Fresh()); } CheckEqual(new[] { "item 2", "item 1", "end" }, script.Log); CheckTrue(script.Finished); } public void PatternVariableInACaseLabel_KeepsItsValueAcrossASaveAndLoad() { var original = new PatternCaseScript { Thing = "hi" }; original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); PatternCaseScript resumed = NestedPauseTests.Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual("hi", record.Locals["Run.text"]); CheckEqual(new[] { "string hi" }, original.Log); CheckEqual(new[] { "done hi" }, resumed.Log); } public void OtherCaseLabels_StillPickTheirSections() { var number = new PatternCaseScript { Thing = 7 }; number.Tick(gates, TestTicks.Fresh()); var other = new PatternCaseScript { Thing = 1.5 }; other.Tick(gates, TestTicks.Fresh()); var empty = new PatternCaseScript { Thing = "" }; empty.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "int 7" }, number.Log); CheckEqual(new[] { "other" }, other.Log); CheckEqual(new[] { "other" }, empty.Log); } public void TupleDeconstruction_KeepsEachVariableAcrossASaveAndLoad() { var original = new DeconstructionScript(); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); DeconstructionScript resumed = NestedPauseTests.Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(2, record.Locals["Run.low"]); CheckEqual(9, record.Locals["Run.high"]); CheckEqual(10, record.Locals["Run.inner"]); CheckEqual(new[] { "2-9 10" }, resumed.Log); } } public class ValuePassageTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void PassageThatPauses_KeepsTheCallerWaitingForItsValue() { var script = new AskScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(0, script.Log.Count); CheckEqual(new[] { "first", "wait" }, script.Cursor); } public void PassageThatFinishes_HandsItsValueToTheCaller() { var script = new AskScript(); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("answer"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "choice 11" }, script.Log); CheckTrue(script.Finished); } public void PassageThatNeverPauses_HandsItsValueOnTheFirstTick() { gates.Open.Add("answer"); var script = new AskScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "choice 10" }, script.Log); } public void LoadedInsideTheCalledPassage_StillHandsTheValueBack() { var original = new AskScript(); original.Tick(gates, TestTicks.Fresh()); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); AskScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("answer"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(2, record.Locals["Ask.waited"]); CheckEqual(new[] { "choice 12" }, resumed.Log); } public void ValueHandedThroughTwoPassages_ArrivesAtTheCaller() { var original = new ValueChainScript(); original.Tick(gates, TestTicks.Fresh()); ValueChainScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("inner"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "Outer", "Inner", "wait" }, original.Cursor); CheckEqual(new[] { "result 7" }, resumed.Log); CheckTrue(resumed.Finished); } public void ValueKeptAcrossALaterPause_SurvivesASaveAndLoad() { var original = new KeptResultScript(); original.Tick(gates, TestTicks.Fresh()); gates.Open.Add("answer"); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); KeptResultScript resumed = NestedPauseTests.Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "second" }, original.Cursor); CheckEqual(11, record.Locals["Run.first"]); CheckEqual(new[] { "first 11" }, resumed.Log); } public void ValuesAssignedToAFieldAndDiscarded_EachKeyByTheirLabel() { var script = new AssignedValuesScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "one", "wait" }, script.Cursor); gates.Open.Add("answer"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "total 11" }, script.Log); CheckTrue(script.Finished); } public void PassagesThatNeverPause_RunStraightThrough() { gates.Open.Add("answer"); var script = new AssignedValuesScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "total 10" }, script.Log); } public void ReturnFromABranchThatPauses_HandsItsValueBack() { gates.Open.Add("left"); var original = new BranchingValueScript(); original.Tick(gates, TestTicks.Fresh()); BranchingValueScript resumed = NestedPauseTests.Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "Pick", "wait" }, original.Cursor); CheckEqual(new[] { "left done" }, resumed.Log); } public void ReturnFromABranchThatDoesNotPause_HandsItsValueAtOnce() { var script = new BranchingValueScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "right" }, script.Log); CheckTrue(script.Finished); } } public class SpilledExpressionTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void FieldReadBeforeACall_KeepsItsOldValue() { var script = new SpillOrderScript(); script.Tick(gates, TestTicks.Fresh()); script.Offset = 500; gates.Open.Add("answer"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "result 111" }, script.Log); CheckTrue(script.Finished); } public void FieldReadBeforeACall_SurvivesASaveAndLoad() { var original = new SpillOrderScript(); original.Tick(gates, TestTicks.Fresh()); original.Offset = 500; ArcRecord record = ArcRecord.Of(original); SpillOrderScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("answer"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(100, record.Locals["Run.__spill1"]); CheckEqual(new[] { "result 111" }, resumed.Log); } public void TwoCallsInOneExpression_KeepTheFirstValueAcrossTheSecondPause() { var original = new TwoCallsScript(); original.Tick(gates, TestTicks.Fresh()); gates.Open.Add("answer"); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); TwoCallsScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("later"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "AskLater", "later" }, original.Cursor); CheckEqual(1100, record.Locals["Run.__spill2"]); CheckEqual(new[] { "sum 1107" }, resumed.Log); } public void ReturnOfACallPlusSomethingElse_HandsTheSumBack() { var script = new SpillReturnScript(); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("answer"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "doubled 22" }, script.Log); CheckTrue(script.Finished); } public void CallAsTheReceiverOfAnotherCall_UsesItsValue() { var original = new SpillReceiverScript(); original.Tick(gates, TestTicks.Fresh()); SpillReceiverScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("answer"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "got 11" }, resumed.Log); } public void ValueCallFeedingAPausingPassage_HandsItsResultOver() { var original = new RememberScript(); original.Tick(gates, TestTicks.Fresh()); gates.Open.Add("answer"); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); RememberScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("remember"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "Remember", "wait" }, original.Cursor); CheckEqual(11, record.Locals["Remember.value"]); CheckEqual(new[] { "remembered 11" }, resumed.Log); CheckTrue(resumed.Finished); } } public class ConditionalOperatorTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void AndWhoseLeftIsFalse_NeverCallsTheRight() { var script = new AndAskScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "ok False" }, script.Log); CheckTrue(script.Finished); } public void AndWhoseLeftIsTrue_PausesInTheRightAndUsesItsValue() { gates.Open.Add("flag"); var script = new AndAskScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "Ask", "wait" }, script.Cursor); gates.Open.Add("answer"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "ok True" }, script.Log); } public void AndPausedInTheRight_SurvivesASaveAndLoad() { gates.Open.Add("flag"); var original = new AndAskScript(); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); AndAskScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Remove("flag"); gates.Open.Add("answer"); gates.Open.Add("more"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(true, record.Locals["Run.__spill1"]); CheckEqual(new[] { "ok True" }, resumed.Log); } public void OrWhoseLeftIsTrue_NeverCallsTheRight() { gates.Open.Add("flag"); var script = new OrAskScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "ok True" }, script.Log); CheckTrue(script.Finished); } public void OrWhoseLeftIsFalse_PausesInTheRight() { var script = new OrAskScript(); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("answer"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "ok False" }, script.Log); } public void CoalesceWithAValue_NeverCallsTheRight() { var script = new CoalesceAskScript { Cached = 7 }; script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "value 7" }, script.Log); CheckTrue(script.Finished); } public void CoalesceWithoutAValue_PausesInTheRight() { var script = new CoalesceAskScript(); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("answer"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "value 11" }, script.Log); } public void ConditionalTakingTheLiteral_NeverCallsThePassage() { gates.Open.Add("fast"); var script = new TernaryAskScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "how quick" }, script.Log); CheckTrue(script.Finished); } public void ConditionalTakingThePassage_PausesInIt() { var original = new TernaryAskScript(); original.Tick(gates, TestTicks.Fresh()); TernaryAskScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("describe"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "Describe", "describe" }, original.Cursor); CheckEqual(new[] { "how slow" }, resumed.Log); } public void ConditionalAccessOnNothing_NeverEvaluatesItsArguments() { var script = new MaybeRecordScript(); script.Tick(gates, TestTicks.Fresh()); CheckTrue(script.Finished); } public void ConditionalAccessOnAnObject_PausesInItsArgument() { var script = new MaybeRecordScript { Helper = new Recorder() }; script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "Ask", "wait" }, script.Cursor); gates.Open.Add("answer"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { 11 }, script.Helper.Seen); } public void FieldReadBeforeAConditionalThatPauses_KeepsItsOldValue() { gates.Open.Add("flag"); var script = new OrderThroughBranchScript(); script.Tick(gates, TestTicks.Fresh()); script.Offset = 500; gates.Open.Add("answer"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "total 121" }, script.Log); } public void FieldReadBeforeAConditionalThatDoesNotPause_IsStillRead() { var script = new OrderThroughBranchScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "total 100" }, script.Log); CheckTrue(script.Finished); } } public class SameFileNameTests : TestFixture { public void ScriptsInDifferentFolders_EachRunTheirOwnCode() { var gates = new ArcTests.Gates(); var first = new FirstBriefing(); var second = new SecondBriefing(); gates.Open.Add("second"); first.Tick(gates, TestTicks.Fresh()); second.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "first" }, first.Log); CheckEqual(new[] { "second", "second done" }, second.Log); } } public class NestedClassTests : TestFixture { public void ScriptInAContainer_PausesAndResumesAcrossAReload() { var gates = new ArcTests.Gates(); var original = new Campaign.Outpost(); original.Tick(gates, TestTicks.Fresh()); original.Tick(gates, TestTicks.Fresh()); Campaign.Outpost resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("clear"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "scouted after 2", "done" }, resumed.Log); CheckTrue(resumed.Finished); } } public class GenericScriptTests : TestFixture { public void LocalOfTheTypeParameter_SurvivesAReloadForEachInstantiation() { var gates = new ArcTests.Gates(); var numbers = new Collector { Source = 7 }; var words = new Collector { Source = "seven" }; numbers.Tick(gates, TestTicks.Fresh()); words.Tick(gates, TestTicks.Fresh()); Collector resumedNumbers = NestedPauseTests.Reloaded(numbers); Collector resumedWords = NestedPauseTests.Reloaded(words); gates.Open.Add("go"); resumedNumbers.Tick(gates, TestTicks.Fresh()); resumedWords.Tick(gates, TestTicks.Fresh()); CheckEqual(7, resumedNumbers.Result); CheckEqual("seven", resumedWords.Result); CheckEqual(2, resumedNumbers.Count); } public void PassageOfAConstructedGenericBase_KeepsItsLocalAcrossAReload() { var gates = new ArcTests.Gates(); var paused = new WordStepper(); paused.Tick(gates, TestTicks.Fresh()); WordStepper resumed = NestedPauseTests.Reloaded(paused); gates.Open.Add("go"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual("word!", resumed.Last); CheckTrue(resumed.Finished); } public void CallOnAConstructedGenericArc_RunsItsPassageOnThatArc() { var gates = new ArcTests.Gates(); var gardener = new Gardener { Plot = new Planter() }; gardener.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); gardener.Tick(gates, TestTicks.Fresh()); CheckEqual("tulip", gardener.Plot.Planted); CheckEqual("planted tulip", gardener.Report); } } public class GenericPassageTests : TestFixture { private TickContext tick; private ArcTests.Gates gates; private DualHolder holder; private SteadyHolder steady; public override void TestSetup() { tick = TestTicks.Fresh(); gates = new ArcTests.Gates(); holder = new DualHolder(); holder.Tick(gates, tick); steady = new SteadyHolder(); steady.Tick(gates, tick); } public void EachInstantiation_KeepsItsOwnLocalsAcrossTheCall() { holder.Tick(gates, tick); gates.Open.Add("go1"); holder.Tick(gates, tick); holder.Tick(gates, tick); gates.Open.Add("go2"); holder.Tick(gates, tick); CheckEqual("7x2;sevenx2;", holder.Report); CheckTrue(holder.Finished); } public void PausedInsideTheFirstInstantiation_ResumesAfterAReload() { holder.Tick(gates, tick); DualHolder resumed = LoadedArc.From(ArcRecord.Of(holder)); gates.Open.Add("go1"); resumed.Tick(gates, tick); CheckEqual("7x2;", resumed.Report); CheckEqual(new[] { "words", "hold" }, resumed.Cursor); } public void PausedInsideTheSecondInstantiation_ResumesAfterAReload() { gates.Open.Add("go1"); holder.Tick(gates, tick); holder.Tick(gates, tick); DualHolder resumed = LoadedArc.From(ArcRecord.Of(holder)); gates.Open.Add("go2"); resumed.Tick(gates, tick); CheckEqual("7x1;sevenx2;", resumed.Report); CheckTrue(resumed.Finished); } public void LocalsOfEachInstantiation_AreSavedBehindTheirCallSite() { CheckEqual(new[] { "Run.numbers/Hold.gate", "Run.numbers/Hold.held", "Run.numbers/Hold.seen" }, ArcRecord.Of(holder).AllLocals().Keys.OrderBy(key => key, StringComparer.Ordinal)); } public void ValueAndConstraints_ComeBackFromAPassageWithTwoTypeParameters() { var chooser = new Chooser(); chooser.Tick(gates, tick); Chooser resumed = LoadedArc.From(ArcRecord.Of(chooser)); gates.Open.Add("go"); resumed.Tick(gates, tick); CheckEqual(42, resumed.Chosen); CheckEqual("left/41", resumed.Said); } public void GenericPassage_CallsOrdinaryAndGenericPassagesAndCarriesOn() { var relayer = new Relayer(); relayer.Tick(gates, tick); CheckEqual(new[] { "start", "first", "step" }, relayer.Cursor); gates.Open.Add("a"); relayer.Tick(gates, tick); CheckEqual(new[] { "start", "second", "inner" }, relayer.Cursor); gates.Open.Add("b"); relayer.Tick(gates, tick); CheckEqual(new[] { "start", "third", "inner" }, relayer.Cursor); gates.Open.Add("c"); relayer.Tick(gates, tick); CheckEqual(new[] { "start", "fourth", "step" }, relayer.Cursor); gates.Open.Add("d"); relayer.Tick(gates, tick); CheckEqual(new[] { "end", "step" }, relayer.Cursor); gates.Open.Add("e"); relayer.Tick(gates, tick); CheckEqual("step1;inner5;innerword;step1;step1;", relayer.Log); CheckTrue(relayer.Finished); } public void PausedAtEachCallOfAGenericPassage_ResumesAfterAReload() { var relayer = new Relayer(); relayer.Tick(gates, tick); relayer.Tick(gates, tick); foreach (string gate in new[] { "a", "b", "c", "d", "e" }) { relayer = LoadedArc.From(ArcRecord.Of(relayer)); gates.Open.Add(gate); relayer.Tick(gates, tick); } CheckEqual("step2;inner5;innerword;step1;step1;", relayer.Log); CheckTrue(relayer.Finished); } public void LocalsOfCallsInsideAGenericPassage_AreSavedBehindBothCallSites() { var relayer = new Relayer(); relayer.Tick(gates, tick); gates.Open.Add("a"); relayer.Tick(gates, tick); CheckEqual(new[] { "Run.start/Outer.held", "Run.start/Outer.second/Inner.deep", "Run.start/Outer.second/Inner.gate" }, ArcRecord.Of(relayer).AllLocals().Keys .OrderBy(key => key, StringComparer.Ordinal)); } public void GenericPassageOfAnotherArc_RunsOnThatArcWithAFrameForEachTypeArgument() { var farmer = new Farmer { House = new Greenhouse() }; farmer.Tick(gates, tick); CheckEqual(new[] { "first", "grow", "step" }, farmer.Cursor); gates.Open.Add("s"); farmer.Tick(gates, tick); CheckEqual(new[] { "first", "wait" }, farmer.Cursor); gates.Open.Add("g1"); farmer.Tick(gates, tick); CheckEqual(new[] { "second", "wait" }, farmer.Cursor); gates.Open.Add("g2"); farmer.Tick(gates, tick); CheckEqual("step1;grew4;step0;grewx;", farmer.House.Note); CheckEqual(2, farmer.House.Grown); CheckTrue(farmer.Finished); } public void RefAndOutParametersOfTheTypeParameter_ComeBackAfterAReload() { var swapper = new Swapper(); swapper.Tick(gates, tick); Swapper resumed = LoadedArc.From(ArcRecord.Of(swapper)); gates.Open.Add("go"); resumed.Tick(gates, tick); CheckEqual(5, resumed.Left); CheckEqual(5, resumed.Right); CheckEqual(10, resumed.Total); } public void GenericPassageOfAGenericBaseClass_RunsWithBothTypeParametersAcrossAReload() { var steps = new IntSteps(); steps.Tick(gates, tick); gates.Open.Add("go"); steps.Tick(gates, tick); IntSteps resumed = LoadedArc.From(ArcRecord.Of(steps)); resumed.Tick(gates, tick); CheckEqual("a;7;", resumed.Log); CheckEqual(4, resumed.Last); CheckTrue(resumed.Finished); } [AllocationTest] public void PausedInsideAGenericPassage_AllocatesNothing() { steady.Tick(gates, tick); CheckNoAllocations(); } } public class HeaderCallTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void IfCondition_PausesThenTakesTheBranch() { var script = new IfAskScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "Ask", "wait" }, script.Cursor); gates.Open.Add("answer"); gates.Open.Add("more"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "big" }, script.Log); CheckTrue(script.Finished); } public void IfConditionPaused_SurvivesASaveAndLoad() { var original = new IfAskScript(); original.Tick(gates, TestTicks.Fresh()); IfAskScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("answer"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "small" }, resumed.Log); } public void WhileCondition_AsksOnEveryTrip() { var script = new WhileAskScript(); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("answer"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "trips 2" }, script.Log); CheckTrue(script.Finished); } public void WhileConditionPaused_KeepsItsTripCountAcrossASaveAndLoad() { var original = new WhileAskScript(); original.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(original); WhileAskScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("answer"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(0, record.Locals["Run.trips"]); CheckEqual(new[] { "trips 2" }, resumed.Log); } public void DoCondition_AsksAfterEachTrip() { var script = new DoAskScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "Ask", "wait" }, script.Cursor); gates.Open.Add("answer"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "trips 2" }, script.Log); CheckTrue(script.Finished); } public void ForInitializer_StartsTheLoopAtTheAnswer() { var original = new ForInitAskScript(); original.Tick(gates, TestTicks.Fresh()); ForInitAskScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("answer"); gates.Open.Add("more"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "i 21", "i 22", "i 23" }, resumed.Log); } public void ForCondition_AsksOnEveryTest() { var original = new ForConditionAskScript(); original.Tick(gates, TestTicks.Fresh()); ForConditionAskScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("answer"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "i 0" }, resumed.Log); CheckTrue(resumed.Finished); } public void ForStep_AsksAfterEachTrip() { var original = new ForStepAskScript(); original.Tick(gates, TestTicks.Fresh()); ForStepAskScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("answer"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "note 11", "note 10" }, resumed.Log); CheckTrue(resumed.Finished); } public void SwitchExpression_PausesThenTakesTheCase() { var original = new SwitchAskScript(); original.Tick(gates, TestTicks.Fresh()); SwitchAskScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("answer"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "eleven" }, resumed.Log); } } public class SeveralDeclaratorTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void PausesInEachInitializerInTurn_AndKeepsTheFirstAnswer() { var script = new TwoDeclaratorsScript(); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("one"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "both#1", "wait" }, script.Cursor); TwoDeclaratorsScript resumed = NestedPauseTests.Reloaded(script); gates.Open.Add("two"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "1,3" }, resumed.Log); } public void InitializerBeforeTheCall_RunsOnceBeforeThePause() { var script = new DeclaratorOrderScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(1, script.Calls); DeclaratorOrderScript resumed = NestedPauseTests.Reloaded(script); gates.Open.Add("one"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "1,1,1" }, resumed.Log); } } public class PatternCallTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void PatternVariableOfACall_SurvivesALaterPauseAndASaveAndLoad() { var original = new PatternAskScript(); original.Tick(gates, TestTicks.Fresh()); gates.Open.Add("answer"); gates.Open.Add("more"); original.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "show" }, original.Cursor); PatternAskScript resumed = NestedPauseTests.Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "n 20", "done" }, resumed.Log); } public void PatternThatDoesNotMatch_SkipsTheBody() { var script = new PatternAskScript(); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("answer"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "done" }, script.Log); CheckTrue(script.Finished); } public void SwitchExpressionOverACall_PicksTheArm() { var original = new SwitchExpressionAskScript(); original.Tick(gates, TestTicks.Fresh()); SwitchExpressionAskScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("answer"); gates.Open.Add("more"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "big" }, resumed.Log); } } public class SwitchExpressionArmTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void ArmWithoutACall_NeverRunsTheOthersPasses() { gates.Open.Add("fast"); var script = new ArmAskScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "fast 1 1" }, script.Log); CheckTrue(script.Finished); } public void ArmWithACall_PausesOnlyInTheTakenArm() { gates.Open.Add("slow"); var original = new ArmAskScript(); original.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "Ask", "wait" }, original.Cursor); ArmAskScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("answer"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "slow 20 0" }, resumed.Log); } public void PatternVariableUsedByAnArmsCall_ReachesThePassage() { var original = new ArmAskScript(); original.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "Scale", "scale" }, original.Cursor); ArmAskScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("scale"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "other 500 0" }, resumed.Log); } } public class AssignmentCallTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void CompoundAssignment_AddsToTheValueItHadBeforeThePause() { gates.Open.Add("describe"); var original = new AssignAskScript(); original.Tick(gates, TestTicks.Fresh()); original.Total = 100; AssignAskScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("answer"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "25 21 20 1 described" }, resumed.Log); } public void CompoundAssignmentOnALocal_SurvivesASaveAndLoad() { gates.Open.Add("describe"); gates.Open.Add("answer"); var original = new AssignAskScript(); original.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "25 21 20 1 described" }, original.Log); CheckTrue(original.Finished); } public void ArraySlot_IsPickedBeforeThePauseAndFilledAfter() { gates.Open.Add("describe"); gates.Open.Add("answer"); var original = new AssignAskScript(); original.Tick(gates, TestTicks.Fresh()); CheckEqual(1, original.Calls); CheckEqual(20, original.Numbers[1]); } public void CoalesceAssignmentOnNothing_PausesForTheValue() { gates.Open.Add("answer"); var original = new AssignAskScript(); original.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "name", "describe" }, original.Cursor); AssignAskScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("describe"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual("described", resumed.Name); } public void CoalesceAssignmentOnSomething_NeverAsks() { gates.Open.Add("answer"); var script = new AssignAskScript { Name = "kept" }; script.Tick(gates, TestTicks.Fresh()); CheckEqual("kept", script.Name); CheckTrue(script.Finished); } } public class DeconstructionCallTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void DeconstructingIntoNewLocals_KeepsThemAcrossALaterPause() { var original = new PairAskScript(); original.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "fresh", "range" }, original.Cursor); gates.Open.Add("answer"); original.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "pause" }, original.Cursor); PairAskScript resumed = NestedPauseTests.Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "1-10", "1-10" }, resumed.Log); CheckEqual(1, resumed.Total); } } public class OutParameterTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void EveryCallForm_ReceivesTheValueWhenThePassageEnds() { gates.Open.Add("accept"); gates.Open.Add("fill"); gates.Open.Add("answer"); var script = new OutParameterScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual( new[] { "picked 30", "stored 30", "again 30 True", "6 filled", "relayed 6" }, script.Log); CheckEqual(30, script.Stored); CheckTrue(script.Finished); } public void ReturnBeforeAnyPause_StillHandsBackWhatWasAssigned() { gates.Open.Add("accept"); gates.Open.Add("skip"); gates.Open.Add("answer"); var script = new OutParameterScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "5 early", "relayed 5" }, script.Log.GetRange(3, 2).ToArray()); } public void PausedInTheCallee_SurvivesASaveAndLoadWithTheValueAssignedBeforeThePause() { var original = new OutParameterScript(); original.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "header", "pick" }, original.Cursor); OutParameterScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("answer"); gates.Open.Add("fill"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "declined 20", "stored 20" }, resumed.Log.GetRange(0, 2).ToArray()); } public void OutParameterChangedAfterAPause_IsSavedAcrossIt() { gates.Open.Add("answer"); var original = new OutParameterScript(); original.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "both", "fill" }, original.Cursor); ArcRecord record = ArcRecord.Of(original); OutParameterScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("fill"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(5, record.Locals["Fill.number"]); CheckTrue(resumed.Log.Contains("6 filled")); } } public class RefParameterTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void RefToALocalAndAField_ChangesThemInPlaceAcrossPauses() { gates.Open.Add("go"); var script = new RefParameterScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "local 30", "total 80", "relayed 315" }, script.Log); CheckEqual(80, script.Total); CheckTrue(script.Finished); } public void WriteBeforeThePause_IsKeptInTheCallersVariableAcrossASaveAndLoad() { var original = new RefParameterScript(); original.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "call", "bump" }, original.Cursor); ArcRecord record = ArcRecord.Of(original); RefParameterScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("go"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(3, record.Locals["Run.local"]); CheckEqual(new[] { "local 30", "total 80", "relayed 315" }, resumed.Log); } } public class ArgumentEvaluationTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void ArgumentToAParameterNeverUsedAfterThePause_IsWorkedOutOnce() { var script = new ArgumentEffectScript(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); CheckEqual(1, script.Calls); } public void InArguments_AreCopiedOnceAndKeptAcrossASaveAndLoad() { var original = new ArgumentEffectScript(); original.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); original.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "shown", "show" }, original.Cursor); ArcRecord record = ArcRecord.Of(original); ArgumentEffectScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("show"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(2, record.Locals["Show.amount"]); CheckEqual(new[] { "amount 2", "named 3", "amount 4" }, resumed.Log); CheckEqual(4, resumed.Calls); CheckTrue(resumed.Finished); } } public class ReadonlyRefParameterTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void CallersVariableChangedDuringThePause_IsSeenAfterIt() { var script = new ReadonlyRefScript(); script.Tick(gates, TestTicks.Fresh()); script.Level = 9; gates.Open.Add("go"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "level 9" }, script.Log); CheckTrue(script.Finished); } public void PausedInTheCallee_ResumesReadingTheCallersVariableAfterALoad() { var original = new ReadonlyRefScript(); original.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "watching", "watch" }, original.Cursor); ReadonlyRefScript resumed = NestedPauseTests.Reloaded(original); resumed.Level = 6; gates.Open.Add("go"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "level 6" }, resumed.Log); } } public class StepJumpTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void GotoToAYieldStep_PausesThereAgain() { var script = new StepJumpScript(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start", "woke" }, script.Log); CheckEqual(new[] { "retry" }, script.Cursor); CheckFalse(script.Finished); } public void GotoToAYieldStep_RunsOnOnceTheConditionHolds() { var script = new StepJumpScript(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "start", "woke", "woke", "end" }, script.Log); CheckTrue(script.Finished); } public void LoadedAfterAGotoPause_ResumesAfterTheStep() { var original = new StepJumpScript(); original.Tick(gates, TestTicks.Fresh()); original.Tick(gates, TestTicks.Fresh()); StepJumpScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("go"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "woke", "end" }, resumed.Log); CheckTrue(resumed.Finished); } public void SiblingBlocksSharingALabel_EachJumpsToItsOwn() { var script = new SiblingLabelScript(); for (int tick = 0; tick < 4; ++tick) { script.Tick(gates, TestTicks.Fresh()); } CheckEqual(new[] { "right", "right", "right" }, script.Log); CheckEqual(new[] { "again#1" }, script.Cursor); } public void SiblingBlocksSharingALabel_FirstBlockStillJumpsToItself() { gates.Open.Add("left"); var script = new SiblingLabelScript(); for (int tick = 0; tick < 4; ++tick) { script.Tick(gates, TestTicks.Fresh()); } CheckEqual(new[] { "left", "left", "left" }, script.Log); CheckEqual(new[] { "again" }, script.Cursor); } public void SiblingBlocksSharingALabel_RunOnOnceTheConditionHolds() { var script = new SiblingLabelScript(); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("stop"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "right", "end" }, script.Log); CheckTrue(script.Finished); } public void SecondSiblingLoadedAtItsStep_ResumesInItsOwnBlock() { var original = new SiblingLabelScript(); original.Tick(gates, TestTicks.Fresh()); SiblingLabelScript resumed = NestedPauseTests.Reloaded(original); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "right" }, resumed.Log); CheckEqual(new[] { "again#1" }, resumed.Cursor); } public void CopiedBlockWithAStepsLabelName_JumpsWithinItself() { gates.Open.Add("left"); var script = new CopiedLabelScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "copied", "copied", "copied", "end" }, script.Log); CheckTrue(script.Finished); } public void StepBesideACopiedBlockWithItsLabelName_StillPauses() { var script = new CopiedLabelScript(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "step", "end" }, script.Log); CheckTrue(script.Finished); } public void GotoToALabelOnAnOrdinaryStatement_RunsFromThatStatement() { var script = new PlainLabelJumpScript(); for (int tick = 0; tick < 4; ++tick) { script.Tick(gates, TestTicks.Fresh()); } CheckEqual(new[] { "body", "body", "body", "end" }, script.Log); CheckTrue(script.Finished); } } public class ParamsArgumentTests : TestFixture { private TickContext tick; private ArcTests.Gates gates; private ParamsScript script; public override void TestSetup() { tick = TestTicks.Fresh(); gates = new ArcTests.Gates(); script = new ParamsScript(); script.Tick(gates, TestTicks.Fresh()); } public void ExpandedArguments_AreWorkedOutOnceHoweverLongThePassagePauses() { script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(1, script.Evaluations); CheckEqual(new[] { "a,n1" }, script.Log); } public void LoadedWhilePaused_StillHasTheArray() { ParamsScript resumed = LoadedArc.From(ArcRecord.Of(script)); gates.Open.Add("go"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "a,n1" }, resumed.Log); CheckEqual(1, resumed.Evaluations); } [AllocationTest] public void PausedInsideAPassageWithParams_AllocatesNothing() { script.Tick(gates, tick); CheckNoAllocations(); } } public class InheritedPassageTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void PassageOfTheBaseClass_RunsOnTheDerivedArc() { var script = new HoldingScript(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "first held 2", "second held 0" }, script.Log); CheckTrue(script.Finished); } public void PausedInsideIt_KeepsOnePathThroughTheCallersKeysAndTheBases() { var script = new HoldingScript(); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "first", "wait" }, script.Cursor); } public void TwoScriptsOfOneBase_KeepSeparateLocals() { var first = new HoldingScript(); var other = new OtherHoldingScript(); first.Tick(gates, TestTicks.Fresh()); first.Tick(gates, TestTicks.Fresh()); other.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); first.Tick(gates, TestTicks.Fresh()); other.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "first held 2", "second held 0" }, first.Log); CheckEqual(new[] { "only held 1" }, other.Log); } public void PassageOfABaseOfABase_RunsOnTheMostDerivedFrame() { var script = new DeepHoldingScript(); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "deep held 1" }, script.Log); } public void LoadedInsideIt_ResumesWithTheBasesLocals() { var original = new HoldingScript(); original.Tick(gates, TestTicks.Fresh()); original.Tick(gates, TestTicks.Fresh()); HoldingScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("go"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "first held 2", "second held 0" }, resumed.Log); } public void SaveInsideIt_HoldsTheBasesLocalsUnderTheirOwnKeys() { var script = new HoldingScript(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(script); CheckEqual(new[] { "Hold.name", "Hold.seen" }, record.Locals.Keys.OrderBy(key => key, StringComparer.Ordinal).ToArray()); CheckEqual(2, record.Locals["Hold.seen"]); } public void CalledAgainInTheTickItEnds_StartsFromTheTop() { var script = new HoldingScript(); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "first held 1", "second held 0" }, script.Log); } } public class RestartTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void PausedScript_KeepsNoLocalsFromBeforeTheRestart() { var script = new HoldingScript(); script.Tick(gates, TestTicks.Fresh()); script.Tick(gates, TestTicks.Fresh()); script.Restart(); script.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); script.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "first held 1", "second held 0" }, script.Log); } public void ScriptPausedInsideAnotherArcsPassage_StartsOverWithoutThatCall() { var client = new ClientScript(); client.Tick(gates, TestTicks.Fresh()); client.Restart(); client.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); client.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "added 5 after 1" }, client.First.Log); CheckEqual(5, client.First.Total); CheckTrue(client.Finished); } public void FinishedScript_LeavesTheFieldsOfArcsItCalledAsTheyWere() { var client = new ClientScript(); client.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); client.Tick(gates, TestTicks.Fresh()); client.Restart(); client.Tick(gates, TestTicks.Fresh()); CheckEqual(10, client.First.Total); CheckEqual(14, client.Second.Total); CheckEqual(new[] { "a 5 b 7 before 3", "a 10 b 14 before 3" }, client.Log); } } public class ForeignCallTests : TestFixture { private ArcTests.Gates gates; public override void TestSetup() { gates = new ArcTests.Gates(); } public void NewFrame_IsAFreshFrameOfTheArcsOwnClass() { var worker = new WorkerScript(); Frame frame = worker.NewFrame(); CheckEqual(typeof(WorkerScript.__Frame), frame.GetType()); } public void CalleesEffects_LandOnTheArcItRunsOn() { var client = new ClientScript(); client.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); client.Tick(gates, TestTicks.Fresh()); CheckEqual(5, client.First.Total); CheckEqual(new[] { "added 5 after 1" }, client.First.Log); CheckEqual(7, client.Second.Total); CheckEqual(new[] { "added 7 after 0" }, client.Second.Log); } public void Value_ComesBackToTheCaller() { var client = new ClientScript(); client.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); client.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "a 5 b 7 before 3" }, client.Log); CheckTrue(client.Finished); } public void PausedInsideTheCall_KeepsOneCursorPathThroughBothArcs() { var client = new ClientScript(); client.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "one", "wait" }, client.Cursor); CheckEqual(0, client.First.Cursor.Count); } public void PassageOfAnotherArc_LeavesNoPathBehindWhenItFinishes() { var client = new ClientScript(); client.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); client.Tick(gates, TestTicks.Fresh()); CheckEqual(0, client.Cursor.Count); } public void CallsOnTwoArcsOfOneClass_KeepTheirOwnLocals() { var client = new ClientScript(); client.Tick(gates, TestTicks.Fresh()); client.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); client.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "added 5 after 2" }, client.First.Log); CheckEqual(new[] { "added 7 after 0" }, client.Second.Log); } public void CallMadeAgainInTheTickItEnds_StartsFromTheTop() { var client = new RepeatingClientScript(); client.Tick(gates, TestTicks.Fresh()); gates.Open.Add("go"); client.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "added 10 after 1", "added 10 after 0" }, client.Worker.Log); CheckEqual(20, client.Worker.Total); } public void CallMadeFromAPassageOfTheCaller_RunsInsideIt() { var relay = new RelayScript(); relay.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "Relay", "Add", "wait" }, relay.Cursor); gates.Open.Add("go"); relay.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "added 1 after 1", "through 1" }, relay.Worker.Log); CheckTrue(relay.Finished); } public void LoadedInsideTheCall_ResumesItWithItsLocals() { var original = new ClientScript(); original.Tick(gates, TestTicks.Fresh()); original.Tick(gates, TestTicks.Fresh()); ClientScript resumed = NestedPauseTests.Reloaded(original); gates.Open.Add("go"); resumed.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "a 5 b 7 before 3" }, resumed.Log); CheckEqual(new[] { "added 5 after 2" }, resumed.First.Log); CheckTrue(resumed.Finished); } public void SavedAgainBeforeItsNextTick_KeepsTheCallsLocals() { var original = new ClientScript(); original.Tick(gates, TestTicks.Fresh()); original.Tick(gates, TestTicks.Fresh()); ClientScript twice = NestedPauseTests.Reloaded(NestedPauseTests.Reloaded(original)); gates.Open.Add("go"); twice.Tick(gates, TestTicks.Fresh()); CheckEqual(new[] { "added 5 after 2" }, twice.First.Log); } public void SaveHoldingTheCallsLocals_PutsThemInATableNamedForTheCall() { var client = new ClientScript(); client.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(client); CheckEqual(new[] { "Run.before" }, record.Locals.Keys.ToArray()); CheckEqual(new[] { "Run.one/" }, record.Scopes.Keys.ToArray()); CheckEqual(new[] { "Add.amount", "Add.seen", "receiver" }, record.Scopes["Run.one/"].Locals.Keys.OrderBy(key => key, StringComparer.Ordinal).ToArray()); } public void SaveMissingTheReceiver_LeavesTheCallsLocalsUnknown() { var client = new ClientScript(); client.Tick(gates, TestTicks.Fresh()); ArcRecord record = ArcRecord.Of(client); record.Scopes["Run.one/"].Locals.Remove("receiver"); CheckThrow(typeof(NotImplementedException)); LoadedArc.From(record).VerifyLoaded(record); } } /// /// Lists saved locals it then can't find, so the arc's own values stand. /// public class UnreadableLocals : IArcReader { public readonly List Keys = new List(); public List Cursor { get; } = new List(); IReadOnlyList IArcReader.Cursor { get { return Cursor; } } public bool Finished { get { return false; } } public IEnumerable LocalKeys { get { return Keys; } } public IEnumerable ScopeNames { get { return new string[0]; } } public bool TryField(string key, out T value) { value = default(T); return false; } public bool HasField(string key) { return false; } public bool TryLocal(string key, out T value) { value = default(T); return false; } public bool TryScope(string name, out IArcReader scope) { scope = null; return false; } public bool TryTypeArguments(out Type[] types) { types = null; return false; } public void Publish(object built) { } } } }