using Amphora; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Threading; using UnitTestSharp; namespace Ariadne.UnitTests { public class RecursionTests : TestFixture { public static ScriptType Reloaded(ScriptType script) where ScriptType : Arc { return LoadedArc.From(ArcRecord.Of(script)); } public class DirectTests : TestFixture { private ArcTests.Gates gates; private TickContext tick; public override void TestSetup() { gates = new ArcTests.Gates(); tick = TestTicks.Fresh(); } [IgnoreTest] private Countdown PausedAtTheThirdLevel() { var script = new Countdown(); script.Tick(gates, tick); foreach (string gate in new[] { "go1", "go2" }) { gates.Open.Add(gate); script.Tick(gates, tick); } return script; } public void EachLevel_KeepsItsOwnLocalsAcrossThePause() { Countdown script = PausedAtTheThirdLevel(); gates.Open.Add("go3"); script.Tick(gates, tick); CheckEqual("30;20;10;", script.Log); CheckTrue(script.Finished); } public void EachLevel_AddsAKeyToTheCursor() { var script = new Countdown(); script.Tick(gates, tick); CheckEqual(new[] { "start", "wait" }, script.Cursor); gates.Open.Add("go1"); script.Tick(gates, tick); CheckEqual(new[] { "start", "deeper", "wait" }, script.Cursor); gates.Open.Add("go2"); script.Tick(gates, tick); CheckEqual(new[] { "start", "deeper", "deeper", "wait" }, script.Cursor); } public void ReloadedAtEachPause_ResumesEveryLevel() { var script = new Countdown(); script.Tick(gates, tick); foreach (string gate in new[] { "go1", "go2", "go3" }) { script = Reloaded(script); gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("30;20;10;", script.Log); CheckTrue(script.Finished); } public void Save_HoldsATableForEachLevelInsideTheLevelAbove() { ArcRecord record = ArcRecord.Of(PausedAtTheThirdLevel()); CheckEqual(new[] { "Run.start/Descend.deeper/Descend.deeper/Descend.level", "Run.start/Descend.deeper/Descend.deeper/Descend.mine", "Run.start/Descend.deeper/Descend.level", "Run.start/Descend.deeper/Descend.mine", "Run.start/Descend.level", "Run.start/Descend.mine", }, record.AllLocals().Keys.OrderBy(key => key, StringComparer.Ordinal).ToArray()); CheckEqual(30, record.AllLocals()["Run.start/Descend.deeper/Descend.deeper/Descend.mine"]); } } public class MutualTests : TestFixture { private ArcTests.Gates gates; private TickContext tick; public override void TestSetup() { gates = new ArcTests.Gates(); tick = TestTicks.Fresh(); } public void PassagesCallingEachOther_RunToTheEnd() { var script = new Rally(); script.Tick(gates, tick); foreach (string gate in new[] { "ping3", "pong2", "ping1", "pong0" }) { gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("ping3;pong2;ping1;pong0;", script.Log); CheckTrue(script.Finished); } public void ReloadedAtEachPause_ResumesTheOtherPassage() { var script = new Rally(); script.Tick(gates, tick); foreach (string gate in new[] { "ping3", "pong2", "ping1", "pong0" }) { script = Reloaded(script); gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("ping3;pong2;ping1;pong0;", script.Log); CheckTrue(script.Finished); } public void CursorAtThePause_NamesEachPassageInTurn() { var script = new Rally(); script.Tick(gates, tick); gates.Open.Add("ping3"); script.Tick(gates, tick); gates.Open.Add("pong2"); script.Tick(gates, tick); CheckEqual(new[] { "start", "back", "again", "wait" }, script.Cursor); } } public class GenericTests : TestFixture { private ArcTests.Gates gates; private TickContext tick; public override void TestSetup() { gates = new ArcTests.Gates(); tick = TestTicks.Fresh(); } public void CallingItselfWithItsOwnTypeArguments_RunsToTheEnd() { var script = new Layering(); script.Tick(gates, tick); foreach (string gate in new[] { "layer2", "layer1", "layer0" }) { gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("box@0;box@1;box@2;", script.Log); CheckTrue(script.Finished); } public void CallingItselfWithItsOwnTypeArguments_ResumesAfterEachReload() { var script = new Layering(); script.Tick(gates, tick); foreach (string gate in new[] { "layer2", "layer1", "layer0" }) { script = Reloaded(script); gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("box@0;box@1;box@2;", script.Log); } public void CallingItselfWithItsTypeArgumentsSwapped_AlternatesBetweenTwoFrameTypes() { var script = new Trading(); script.Tick(gates, tick); foreach (string gate in new[] { "trade2", "trade1", "trade0" }) { gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("1/one;one/1;1/one;", script.Log); CheckTrue(script.Finished); } public void CallingItselfWithItsTypeArgumentsSwapped_ResumesAfterEachReload() { var script = new Trading(); script.Tick(gates, tick); foreach (string gate in new[] { "trade2", "trade1", "trade0" }) { script = Reloaded(script); gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("1/one;one/1;1/one;", script.Log); } } public class CalledOnAnotherArcTests : TestFixture { private ArcTests.Gates gates; private TickContext tick; public override void TestSetup() { gates = new ArcTests.Gates(); tick = TestTicks.Fresh(); } public void RecursivePassageOfAnotherArc_RunsOnThatArc() { var gardener = new Orchardist(); gardener.Tick(gates, tick); foreach (string gate in new[] { "ring2", "ring1" }) { gates.Open.Add(gate); gardener.Tick(gates, tick); } CheckEqual("1;2;", gardener.Trees.Log); CheckTrue(gardener.Finished); } public void RecursivePassageOfAnotherArc_ResumesAfterEachReload() { var gardener = new Orchardist(); gardener.Tick(gates, tick); foreach (string gate in new[] { "ring2", "ring1" }) { gardener = Reloaded(gardener); gates.Open.Add(gate); gardener.Tick(gates, tick); } CheckEqual("1;2;", gardener.Trees.Log); } public void Save_HoldsTheCallsLevelsBesideTheReceiverInTheCallsTable() { var gardener = new Orchardist(); gardener.Tick(gates, tick); gates.Open.Add("ring2"); gardener.Tick(gates, tick); CheckEqual(new[] { "Run.plant/Grow.deeper/Grow.mine", "Run.plant/Grow.deeper/Grow.rings", "Run.plant/Grow.mine", "Run.plant/Grow.rings", "Run.plant/receiver" }, ArcRecord.Of(gardener).AllLocals().Keys .OrderBy(key => key, StringComparer.Ordinal).ToArray()); } } public class InheritedTests : TestFixture { private ArcTests.Gates gates; private TickContext tick; public override void TestSetup() { gates = new ArcTests.Gates(); tick = TestTicks.Fresh(); } public void PassageOfABaseClassCallingItself_RunsOnTheDerivedArc() { var script = new Shaft(); script.Tick(gates, tick); foreach (string gate in new[] { "dig1", "dig2" }) { gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("2;1;", script.Log); CheckTrue(script.Finished); } public void PassageOfABaseClassCallingItself_ResumesAfterEachReload() { var script = new Shaft(); script.Tick(gates, tick); foreach (string gate in new[] { "dig1", "dig2" }) { script = Reloaded(script); gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("2;1;", script.Log); } } public class OtherArcTests : TestFixture { private ArcTests.Gates gates; private TickContext tick; private Volley first; private Volley second; public override void TestSetup() { gates = new ArcTests.Gates(); tick = TestTicks.Fresh(); first = new Volley(); second = new Volley(); first.Partner = second; second.Partner = first; } public void PassageHandedBackAndForthBetweenTwoArcs_RunsEachServeOnTheOtherArc() { first.Tick(gates, tick); foreach (string gate in new[] { "hit3", "hit2", "hit1" }) { gates.Open.Add(gate); first.Tick(gates, tick); } CheckEqual("serve3;serve1;", first.Log); CheckEqual("serve2;", second.Log); CheckTrue(first.Finished); } public void Save_HoldsATableForEachServeInsideTheOneAbove() { first.Tick(gates, tick); gates.Open.Add("hit3"); first.Tick(gates, tick); gates.Open.Add("hit2"); first.Tick(gates, tick); CheckEqual(new[] { "start", "back", "back", "wait" }, first.Cursor); CheckEqual(1, ArcRecord.Of(first).AllLocals()["Serve.back/Serve.back/Serve.shot"]); } } public class PolymorphicTests : TestFixture { private ArcTests.Gates gates; private TickContext tick; public override void TestSetup() { gates = new ArcTests.Gates(); tick = TestTicks.Fresh(); } public void CallingItselfWithItsTypeArgumentWrapped_RunsAFrameOfANewTypeAtEachLevel() { var script = new Widening(); script.Tick(gates, tick); foreach (string gate in new[] { "wrap2", "wrap1", "wrap0" }) { gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("0@2;1@1;2@0;up2;up1;up0;", script.Log); CheckTrue(script.Finished); } public void CallingItselfWithItsTypeArgumentWrapped_ResumesAfterEachReload() { var script = new Widening(); script.Tick(gates, tick); foreach (string gate in new[] { "wrap2", "wrap1", "wrap0" }) { script = Reloaded(script); gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("0@2;1@1;2@0;up2;up1;up0;", script.Log); CheckTrue(script.Finished); } public void Save_NamesTheTypeArgumentsOfEachLevelBesideItsLocals() { var script = new Widening(); script.Tick(gates, tick); foreach (string gate in new[] { "wrap2", "wrap1" }) { gates.Open.Add(gate); script.Tick(gates, tick); } ArcRecord first = ArcRecord.Of(script).Scopes["Run.start/"]; ArcRecord second = first.Scopes["Wrap.deeper/"]; ArcRecord third = second.Scopes["Wrap.deeper/"]; CheckNull(first.SavedTypeArguments); CheckEqual(new[] { typeof(List) }, second.SavedTypeArguments); CheckEqual(new[] { typeof(List>) }, third.SavedTypeArguments); CheckEqual(7, first.Locals["Wrap.kept"]); CheckEqual(typeof(List>), third.Types["Wrap.kept"]); } } public class BuildTests : TestFixture { public class ConstrainedFrame : Frame where X : class { } [IgnoreTest] private static ArcRecord Saved(params Type[] types) { var returnMe = new ArcRecord(); returnMe.TypeArguments(types); return returnMe; } public void TypeArgumentsThatFit_MakeAFrameOfThatType() { Frame built = Frame.Build(typeof(ConstrainedFrame<>), Saved(typeof(string))); CheckEqual(typeof(ConstrainedFrame), built.GetType()); } public void NoTypeArguments_Throw() { CheckThrow(typeof(InvalidDataException)); Frame.Build(typeof(ConstrainedFrame<>), new ArcRecord()); } public void TypeArgumentThatBreaksAConstraint_Throws() { CheckThrow(typeof(InvalidDataException)); Frame.Build(typeof(ConstrainedFrame<>), Saved(typeof(int))); } public void WrongNumberOfTypeArguments_Throws() { CheckThrow(typeof(InvalidDataException)); Frame.Build(typeof(ConstrainedFrame<>), Saved(typeof(string), typeof(string))); } } public class RunTests : TestFixture { private ArcTests.Gates gates; private TickContext tick; public override void TestSetup() { gates = new ArcTests.Gates(); tick = TestTicks.Fresh(); } public void RunCalledFromAPassageItCalls_GetsAFrameForEachLap() { var script = new Relapsing(); script.Tick(gates, tick); foreach (string gate in new[] { "lap0", "lap1", "lap2" }) { gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("lap0;lap1;lap2;end2;end1;end0;", script.Log); CheckTrue(script.Finished); } public void RunCalledFromAPassageItCalls_ResumesAfterEachReload() { var script = new Relapsing(); script.Tick(gates, tick); foreach (string gate in new[] { "lap0", "lap1", "lap2" }) { script = Reloaded(script); gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("lap0;lap1;lap2;end2;end1;end0;", script.Log); CheckTrue(script.Finished); } public void RunCalledFromAPassageItCalls_AddsAKeyToTheCursorForEachLap() { var script = new Relapsing(); script.Tick(gates, tick); gates.Open.Add("lap0"); script.Tick(gates, tick); gates.Open.Add("lap1"); script.Tick(gates, tick); CheckEqual(new[] { "start", "back", "start", "back", "start", "wait" }, script.Cursor); } public void RunCalledFromAPassageItCalls_SavesEachLapInsideTheOneAbove() { var script = new Relapsing(); script.Tick(gates, tick); foreach (string gate in new[] { "lap0", "lap1" }) { gates.Open.Add(gate); script.Tick(gates, tick); } ArcRecord record = ArcRecord.Of(script); CheckEqual(new[] { "Run.lap", "Run.start/Again.back/Run.lap", "Run.start/Again.back/Run.start/Again.back/Run.lap", "Run.start/Again.back/Run.start/Again.back/Run.start/Again.kept", }, record.AllLocals().Keys.OrderBy(key => key, StringComparer.Ordinal).ToArray()); CheckEqual(0, record.AllLocals()["Run.lap"]); CheckEqual(1, record.AllLocals()["Run.start/Again.back/Run.lap"]); CheckEqual(2, record.AllLocals()["Run.start/Again.back/Run.start/Again.back/Run.lap"]); } public void RunCallingItself_RunsEachLapOnAFrameOfItsOwn() { var script = new Repeating(); script.Tick(gates, tick); foreach (string gate in new[] { "round0", "round1", "round2" }) { gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("round0;round1;round2;done2;done1;done0;", script.Log); CheckTrue(script.Finished); } public void RunCallingItself_ResumesAfterEachReload() { var script = new Repeating(); script.Tick(gates, tick); foreach (string gate in new[] { "round0", "round1", "round2" }) { script = Reloaded(script); gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("round0;round1;round2;done2;done1;done0;", script.Log); } public void RunCallingItself_AddsAKeyToTheCursorForEachLap() { var script = new Repeating(); script.Tick(gates, tick); gates.Open.Add("round0"); script.Tick(gates, tick); gates.Open.Add("round1"); script.Tick(gates, tick); CheckEqual(new[] { "again", "again", "wait" }, script.Cursor); } public void RunCallingItselfNeverTicked_SavesNothingToTheLevelOfTheArc() { CheckEqual(0, ArcRecord.Of(new Repeating()).AllLocals().Count); } } public class OverriddenRunTests : TestFixture { private ArcTests.Gates gates; private TickContext tick; public override void TestSetup() { gates = new ArcTests.Gates(); tick = TestTicks.Fresh(); } public void BaseClassRunInACycle_RunsOnItsOwn() { var script = new Cycling(); script.Tick(gates, tick); foreach (string gate in new[] { "pass0", "pass1" }) { gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("pass0;pass1;", script.Log); CheckTrue(script.Finished); } public void DerivedClassRunInACycle_RunsInPlaceOfTheBases() { var script = new CyclingChild(); script.Tick(gates, tick); foreach (string gate in new[] { "push0", "push1" }) { gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("push0;push1;", script.Log); CheckTrue(script.Finished); } public void DerivedClassRunInACycle_ResumesAfterEachReload() { var script = new CyclingChild(); script.Tick(gates, tick); foreach (string gate in new[] { "push0", "push1" }) { script = Reloaded(script); gates.Open.Add(gate); script.Tick(gates, tick); } CheckEqual("push0;push1;", script.Log); CheckTrue(script.Finished); } } public class RunByNameMarkTests : TestFixture { [IgnoreTest] private static ReachesRunByNameAttribute MarkOf(Type type, string copy) { MethodInfo method = type.GetMethod(copy, BindingFlags.Instance | BindingFlags.NonPublic); return (ReachesRunByNameAttribute)Attribute.GetCustomAttribute(method, typeof(ReachesRunByNameAttribute)); } public void CopyOfARunThatCallsItselfByName_NamesItsClass() { CheckEqual("Repeating", MarkOf(typeof(Repeating), "Run__Passage").ClassName); } public void CopyOfAPassageThatNeverReachesTheCall_HasNoMark() { CheckNull(MarkOf(typeof(Countdown), "Descend__Passage")); } } public class StackTests : TestFixture { public void CycleThatNeverPausesOrEnds_IsStoppedBeforeTheStackRunsOut() { ExceptionSandbox sandbox = TestTicks.NewSandbox(); var script = new Bottomless(); script.Tick(new ArcTests.Gates(), TestTicks.With(sandbox)); CheckEqual(1, sandbox.Entries.Count); CheckEqual(typeof(InsufficientExecutionStackException), sandbox.Entries[0].Exception.GetType()); CheckTrue(script.Reached > 50); CheckFalse(script.Finished); } public void CycleOfNewFrameTypesThatNeverPausesOrEnds_IsStoppedBeforeTheStackRunsOut() { ExceptionSandbox sandbox = TestTicks.NewSandbox(); var script = new Fathomless(); // Every level is a new type to build, so a small stack keeps the test short. var thread = new Thread(() => script.Tick(new ArcTests.Gates(), TestTicks.With(sandbox)), maxStackSize: 32 * 1024); thread.Start(); thread.Join(); CheckEqual(1, sandbox.Entries.Count); CheckEqual(typeof(InsufficientExecutionStackException), sandbox.Entries[0].Exception.GetType()); CheckTrue(script.Reached > 20); } } public class AllocationTests : TestFixture { private ArcTests.Gates gates; private TickContext tick; private Plunging script; public override void TestSetup() { gates = new ArcTests.Gates(); tick = TestTicks.Fresh(); script = new Plunging(); script.Tick(gates, tick); } public void PausedAtTheThirdLevel_KeepsTheLevelsAboveItPausedInTheirCalls() { CheckEqual(new[] { "start", "deeper", "deeper", "wait" }, script.Cursor); } [AllocationTest] public void PausedThreeLevelsDown_AllocatesNothing() { script.Tick(gates, tick); CheckNoAllocations(); } public void ReloadedPausedThreeLevelsDown_RunsOnToTheEnd() { Plunging resumed = Reloaded(script); gates.Open.Add("go"); resumed.Tick(gates, tick); CheckEqual(7, resumed.Spins); CheckTrue(resumed.Finished); } } } }