using UnitTestSharp; namespace Ariadne.Generator.UnitTests { public class RecursionTests : TestFixture { public const string Counting = @" public partial class Counting : Arc { private void Down(World world, int left) { wait: while (!world.Open.Contains(""go"")) { Yield(); } if (left > 0) { deeper: Down(world, left - 1); } } protected override void Run(World world) { start: Down(world, 2); } }"; public const string Mutual = @" public partial class Volleying : Arc { private void Ping(World world, int left) { wait: while (!world.Open.Contains(""ping"")) { Yield(); } if (left > 0) { back: Pong(world, left - 1); } } private void Pong(World world, int left) { int kept = left; wait: while (!world.Open.Contains(""pong"")) { Yield(); } if (kept > 0) { again: Ping(world, kept - 1); } } protected override void Run(World world) { start: Ping(world, 3); } }"; public static string Generic(string call) { return @" public partial class Nesting : Arc { private void Wrap(World world, X first, Y second, int left) { X kept = first; wait: while (!world.Open.Contains(""go"")) { Yield(); } if (left > 0) { deeper: " + call + @"; } } protected override void Run(World world) { start: Wrap(world, 1, ""two"", 2); } }"; } public class FrameTests : TestFixture { public void PassageCallingItself_OwnsAFrameWithoutTypeParameters() { GeneratorRun run = GeneratorRun.Of(Counting); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public partial class Down__Frame : global::Ariadne.Frame")); CheckTrue(run.CodeBehind.Contains("private bool Down__Passage(Down__Frame __frame, " + "__Frame __arcFrame, global::Ariadne.TickTime __time, World world, ")); } public void RecursiveCall_KeepsTheCalleesFrameInTheCallersFrame() { GeneratorRun run = GeneratorRun.Of(Counting); CheckTrue(run.CodeBehind.Contains("public global::Counting.Down__Frame Down____call_deeper_frame;")); CheckTrue(run.CodeBehind.Contains("public global::Counting.Down__Frame Run____call_start_frame;")); } public void RecursiveCall_RunsTheCopyOnTheFrameItKeeps() { GeneratorRun run = GeneratorRun.Of(Counting); CheckTrue(run.CodeBehind.Contains("Down__Passage(__frame.Lend(__frame.Down____call_deeper_frame), " + "__arcFrame, __time, ")); } public void MutualRecursion_GivesEachPassageAFrame() { GeneratorRun run = GeneratorRun.Of(Mutual); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public partial class Ping__Frame : global::Ariadne.Frame")); CheckTrue(run.CodeBehind.Contains("public partial class Pong__Frame : global::Ariadne.Frame")); CheckTrue(run.CodeBehind.Contains("public global::Volleying.Pong__Frame Ping____call_back_frame;")); CheckTrue(run.CodeBehind.Contains("public global::Volleying.Ping__Frame Pong____call_again_frame;")); } public void PassageOutsideTheCycle_KeepsItsLocalsInTheArcsFrame() { GeneratorRun run = GeneratorRun.Of(Counting); CheckFalse(run.CodeBehind.Contains("Run__Frame")); CheckTrue(run.CodeBehind.Contains("protected bool Run__Passage(__Frame __frame, ")); } } public const string Looping = @" public partial class Looping : Arc { private void Again(World world, int left) { int kept = left; wait: while (!world.Open.Contains(""wait"")) { Yield(); } if (kept > 0) { back: Run(world); } } protected override void Run(World world) { int lap = 0; again: Again(world, lap); } }"; public class RunFrameTests : TestFixture { public void RunInACycle_OwnsAFrameTheArcsFrameMakesWithIt() { GeneratorRun run = GeneratorRun.Of(Looping); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public partial class Run__Frame : global::Ariadne.Frame")); CheckTrue(run.CodeBehind.Contains("public Run__Frame __runFrame = new Run__Frame();")); } public void RunInACycle_IsRunOnTheFrameTheArcKeeps() { GeneratorRun run = GeneratorRun.Of(Looping); CheckTrue(run.CodeBehind.Contains("return Run__Passage(__arcFrame.Lend(__arcFrame.__runFrame), " + "__arcFrame, __time, context);")); } public void RunInACycle_IsSavedFromTheFrameTheArcKeeps() { GeneratorRun run = GeneratorRun.Of(Looping); CheckTrue(run.CodeBehind.Contains("Run__LocalsToSave(__arcFrame.Lend(__arcFrame.__runFrame), " + "__arcFrame, 0, writer, writer);")); } public void RunInACycle_IsLoadedFromTheLevelOfTheArc() { GeneratorRun run = GeneratorRun.Of(Looping); CheckTrue(run.CodeBehind.Contains("this.__runFrame.LoadLocals(reader);")); } public void RunInACycle_IsListedAtTheLevelOfTheArc() { GeneratorRun run = GeneratorRun.Of(Looping); CheckEqual(2, run.CodeBehind.Split(new[] { "Run__Frame.ListParts(lister);" }, System.StringSplitOptions.None).Length - 1); } public void RunCallingItselfDirectly_OwnsAFrame() { GeneratorRun run = GeneratorRun.Of(@" public partial class Repeating : Arc { protected override void Run(World world) { wait: while (!world.Open.Contains(""go"")) { Yield(); } again: Run(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public global::Repeating.Run__Frame Run____call_again_frame;")); } } public class StackGuardTests : TestFixture { public void RecursiveCall_ChecksTheStackBeforeGoingDeeper() { GeneratorRun run = GeneratorRun.Of(Counting); CheckTrue(run.CodeBehind.Contains("using (__frame.Enter(\"deeper\")) { " + "global::System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack(); ")); } public void CallOutsideTheCycle_ChecksNothing() { GeneratorRun run = GeneratorRun.Of(Counting); CheckTrue(run.CodeBehind.Contains("using (__frame.Enter(\"start\")) { if (")); } public void CallOnAnotherArc_ChecksTheStack() { GeneratorRun run = GeneratorRun.Of(ForeignCallTests.Generators + ForeignCallTests.Mission); CheckTrue(run.CodeBehind.Contains("using (__frame.Enter(\"update\")) { " + "global::System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack(); ")); } } public class SaveAndLoadTests : TestFixture { public void LoadingAFrame_MakesTheNextLevelOnlyWhereTheSaveHasOne() { GeneratorRun run = GeneratorRun.Of(Counting); CheckTrue(run.CodeBehind.Contains( "if (reader.TryScope(\"Down.deeper/\", out global::Ariadne.IArcReader scope))")); CheckTrue(run.CodeBehind.Contains( "this.Down____call_deeper_frame = new global::Counting.Down__Frame();")); } public void LoadingAFrameThatHasCalls_ChecksTheStackFirst() { GeneratorRun run = GeneratorRun.Of(Counting); CheckTrue(run.CodeBehind.Contains( "public override void LoadLocals(global::Ariadne.IArcReader reader)\r\n {\r\n" + " global::System.Runtime.CompilerServices.RuntimeHelpers." + "EnsureSufficientExecutionStack();")); } public void SavingARecursiveCall_OpensATableForEachLevel() { GeneratorRun run = GeneratorRun.Of(Counting); CheckTrue(run.CodeBehind.Contains("writer.Scope(\"Down.deeper/\"); Down__LocalsToSave(" + "__frame.Lend(__frame.Down____call_deeper_frame), __arcFrame, depth + 1, scope, arcWriter);")); } public void ListingARecursiveCall_StopsWhereTheListerAlreadyHasThoseFrames() { GeneratorRun run = GeneratorRun.Of(Counting); CheckTrue(run.CodeBehind.Contains( "lister.Scope(\"Down.deeper/\", typeof(global::Counting.Down__Frame));")); CheckTrue(run.CodeBehind.Contains("if (scope != null)")); } } public class PolymorphicTests : TestFixture { public void CallBuildingATypeArgumentFromTheParameters_KeepsItsFrameAsABaseFrame() { GeneratorRun run = GeneratorRun.Of(Generic("Wrap(world, new List(), second, left - 1)")); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public global::Ariadne.Frame Wrap____call_deeper_frame;")); CheckTrue(run.CodeBehind.Contains("Wrap__Passage, Y>(" + "__frame.Lend(((global::Nesting.Wrap__Frame, Y>)" + "__frame.Wrap____call_deeper_frame)), ")); } public void CallBuildingATypeArgumentFromTheParameters_ChecksTheStackBeforeGoingDeeper() { GeneratorRun run = GeneratorRun.Of(Generic("Wrap(world, new List(), second, left - 1)")); CheckTrue(run.CodeBehind.Contains("using (__frame.Enter(\"deeper\")) { " + "global::System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack(); ")); } public void CallBuildingATypeArgumentFromTheParameters_SavesTheTypeArguments() { GeneratorRun run = GeneratorRun.Of(Generic("Wrap(world, new List(), second, left - 1)")); CheckTrue(run.CodeBehind.Contains("scope.TypeArguments(" + "typeof(global::System.Collections.Generic.List), typeof(Y));")); } public void CallBuildingATypeArgumentFromTheParameters_BuildsTheFrameFromTheSaveWhenLoading() { GeneratorRun run = GeneratorRun.Of(Generic("Wrap(world, new List(), second, left - 1)")); CheckTrue(run.CodeBehind.Contains("this.Wrap____call_deeper_frame = global::Ariadne.Frame.Build(" + "typeof(global::Nesting.Wrap__Frame<,>), scope);")); } public void CallBuildingATypeArgumentFromTheParameters_IsListedAsPolymorphic() { GeneratorRun run = GeneratorRun.Of(Generic("Wrap(world, new List(), second, left - 1)")); CheckFalse(run.CodeBehind.Contains("lister.Scope(\"Wrap.deeper/\"")); CheckTrue(run.CodeBehind.Contains( "lister.Polymorphic(\"Wrap.deeper/\", typeof(global::Nesting.Wrap__Frame<,>));")); } public void CallPassingTheParametersOnAsTheyAre_KeepsItsFrameAsTheExactType() { GeneratorRun run = GeneratorRun.Of(Generic("Wrap(world, kept, second, left - 1)")); CheckTrue(run.CodeBehind.Contains( "public global::Nesting.Wrap__Frame Wrap____call_deeper_frame;")); CheckFalse(run.CodeBehind.Contains("Frame.Build(")); } public void CallInADifferentPassageOfTheCycle_IsPolymorphicToo() { GeneratorRun run = GeneratorRun.Of(@" public partial class Alternating : Arc { private void Left(World world, X item) { wait: while (!world.Open.Contains(""left"")) { Yield(); } Right(world, new List()); } private void Right(World world, Y item) { Left(world, item); } protected override void Run(World world) { Left(world, 1); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public global::Ariadne.Frame Left____call_Right_frame;")); CheckTrue(run.CodeBehind.Contains("public global::Alternating.Left__Frame Right____call_Left_frame;")); } } public class DiagnosticTests : TestFixture { public void Recursion_IsNotReported() { CheckEqual(0, GeneratorRun.Of(Counting).Reported.Count); CheckEqual(0, GeneratorRun.Of(Mutual).Reported.Count); } public void TypeParametersPassedOnAsTheyAre_AreNotReported() { GeneratorRun run = GeneratorRun.Of(Generic("Wrap(world, kept, second, left - 1)")); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void TypeParametersSwapped_AreNotReported() { GeneratorRun run = GeneratorRun.Of(Generic("Wrap(world, second, kept, left - 1)")); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void TypeArgumentsThatDontUseTheParameters_AreNotReported() { GeneratorRun run = GeneratorRun.Of(Generic("Wrap(world, 5, \"five\", left - 1)")); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void TypeArgumentBuiltFromAParameter_IsNotReported() { GeneratorRun run = GeneratorRun.Of(Generic("Wrap(world, new List(), second, left - 1)")); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void TypeArgumentBuiltFromAParameterInACallOutsideTheCycle_IsNotReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Branching : Arc { private void Outer(World world, X item) { Inner(world, new List()); } private void Inner(World world, Y item) { wait: while (!world.Open.Contains(""go"")) { Yield(); } } protected override void Run(World world) { Outer(world, 1); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } } } }