using System.Linq; using UnitTestSharp; namespace Ariadne.Generator.UnitTests { public class ScriptChecksTests : TestFixture { public class ReportTests : TestFixture { public void PlumbingUsedByAScript_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Meddling : Arc { protected override void Run(World world) { if (__ResumeKey == null) { __Mark(""elsewhere""); __SaveFields(null); } } }"); CheckEqual(new[] { "ARI0011:__ResumeKey", "ARI0011:__Mark", "ARI0011:__SaveFields" }, run.Reported); } public void ScriptsOwnMembersNamedLikeThePlumbing_DontCollide() { GeneratorRun run = GeneratorRun.Of(@" public partial class OwnNames : Arc { private void Mark(string key) { } private void Enter(string key) { } private bool Resume(World world) { return true; } private string ResumeKey { get { return """"; } } private void Act(World world) { wait: while (!world.Open.Contains(ResumeKey)) { Mark(""wait""); Yield(); } } protected override void Run(World world) { Enter(""act""); Resume(world); Act(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void OverloadedPassages_AreBothReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Overloaded : Arc { private void Act(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } } private void Act(World world, string gate) { wait: while (!world.Open.Contains(gate)) { Yield(); } } protected override void Run(World world) { one: Act(world); two: Act(world, ""other""); } }"); CheckEqual(new[] { "ARI0012:Act", "ARI0012:Act" }, run.Reported); } public void PassageReturningAReference_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Valued : Arc { private int total; private ref int Count(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } return ref total; } protected override void Run(World world) { Count(world); } }"); CheckEqual(new[] { "ARI0013:Count" }, run.Reported); } public void ParamsParameterThatIsNotAnArray_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Listing : Arc { private void Say(World world, params List lines) { say: Yield(); } private void Count(World world, params IEnumerable numbers) { count: Yield(); } protected override void Run(World world) { one: Say(world, ""a""); two: Count(world, 1); } }"); CheckEqual(new[] { "ARI0028:lines", "ARI0028:numbers" }, run.Reported); } public void ParamsParameterThatIsAnArray_IsNotReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Listing : Arc { private void Say(World world, params string[] lines) { say: Yield(); } protected override void Run(World world) { Say(world, ""a""); } }"); CheckEqual(0, run.Reported.Count); } public void PausesOutsideAnyMethodBody_AreReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Scattered : Arc { public Scattered() { Yield(); } private bool Ready { get { Act(null); return true; } } private void Brief() => Yield(); private void Act(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } } protected override void Run(World world) { Act(world); } }"); CheckEqual(new[] { "ARI0003:Yield()", "ARI0004:Act(null)", "ARI0003:Yield()" }, run.Reported); } public void PassageCalledOnAnotherArcOfTheSameClass_IsAllowed() { GeneratorRun run = GeneratorRun.Of(@" public partial class Delegating : Arc { public Delegating Other; public void Act(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } } protected override void Run(World world) { Other.Act(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void PassageCalledThroughAConditionalAccess_IsReportedAsMisplaced() { GeneratorRun run = GeneratorRun.Of(@" public partial class Delegating : Arc { public Delegating Other; private void Act(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } } protected override void Run(World world) { Other?.Act(world); } }"); CheckEqual(new[] { "ARI0004:.Act(world)" }, run.Reported); } public void PassageCalledOnThis_IsAllowed() { GeneratorRun run = GeneratorRun.Of(@" public partial class Own : Arc { private void Act(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } } protected override void Run(World world) { this.Act(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void PassageCalledFromAClassDerivedFromAScript_RunsOnTheDerivedArcsFrame() { GeneratorRun run = GeneratorRun.Of(@" public partial class Parent : Arc { protected void Act(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } } protected override void Run(World world) { Act(world); } } public partial class Child : Parent { protected override void Run(World world) { Act(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void PlainMethodsOfBaseClasses_AreAllowed() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Plain : Arc { protected void Note(World world) { world.Open.Add(""noted""); } } public partial class Parent : Plain { protected void Count(World world) { world.Open.Add(""counted""); } protected override void Run(World world) { Note(world); Note(world); } } public partial class Child : Parent { protected override void Run(World world) { Count(world); Note(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void StaticPassage_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Static : Arc { private void Act(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } } private static void Hop(Static arc, World world) { arc.Act(world); } protected override void Run(World world) { Act(world); } }"); CheckTrue(run.Reported.Contains("ARI0025:Hop")); } public void AsyncPassage_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asynchronous : Arc { private async void Act(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } } protected override void Run(World world) { Act(world); } }"); CheckEqual(new[] { "ARI0025:Act" }, run.Reported); } public void UnsafePassage_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Pointers : Arc { private unsafe void Act(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } } protected override void Run(World world) { Act(world); } }"); CheckEqual(new[] { "ARI0025:Act" }, run.Reported); } public void IteratorPassage_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Iterating : Arc { private IEnumerable Act(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } yield return 1; } protected override void Run(World world) { Act(world); } }"); CheckEqual(new[] { "ARI0025:Act" }, run.Reported); } public void LocalFunctionsYieldInsideAPassage_DontMakeItAnIterator() { GeneratorRun run = GeneratorRun.Of(@" public partial class Local : Arc { private void Act(World world) { IEnumerable Numbers() { yield return 1; } System.Func> lambda = () => Numbers(); world.Open.Add(lambda().Count().ToString()); wait: while (!world.Open.Contains(""wait"")) { Yield(); } } protected override void Run(World world) { Act(world); } }"); CheckEqual(0, run.Reported.Count); } public void OverrideOfAMethodThatIsNotAPassage_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Naming : Arc { public override string ToString() { wait: while (true) { Yield(); } } protected override void Run(World world) { ToString(); } }"); CheckTrue(run.Reported.Contains("ARI0025:ToString")); } public void RunItself_IsNotReportedAsAnOverride() { GeneratorRun run = GeneratorRun.Of(@" public partial class Plain : Arc { protected override void Run(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } } }"); CheckEqual(0, run.Reported.Count); } public void PassageOverloadedByAPlainMethod_IsAllowed() { GeneratorRun run = GeneratorRun.Of(@" public partial class HalfOverloaded : Arc { private int calls; private void Act(int count) { calls += count; } private void Act(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } } protected override void Run(World world) { Act(1); Act(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } } public class ReportRunByNameTests : TestFixture { public const string Looping = @" public abstract partial class Basis : Arc { public int Laps; protected override void Run(World world) { int lap = Laps++; wait: while (!world.Open.Contains(""go"" + lap)) { Yield(); } if (lap < 1) { again: Run(world); } } }"; public void BaseRunThatCallsRunByName_IsReportedAtTheCallToIt() { GeneratorRun run = GeneratorRun.Of(Looping + @" public partial class Derived : Basis { protected override void Run(World world) { Laps += 10; base.Run(world); } }"); CheckEqual(new[] { "ARI0036:base.Run(world)" }, run.Reported); } public void Message_NamesTheClassWhoseRunTheCallStaysOn() { GeneratorRun run = GeneratorRun.Of(Looping + @" public partial class Derived : Basis { protected override void Run(World world) { base.Run(world); } }"); CheckTrue(run.Messages.Single().Contains("'Basis'")); } public void BaseRunThatCallsThisRun_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Basis : Arc { public int Laps; protected override void Run(World world) { int lap = Laps++; wait: while (!world.Open.Contains(""go"" + lap)) { Yield(); } if (lap < 1) { again: this.Run(world); } } } public partial class Derived : Basis { protected override void Run(World world) { base.Run(world); } }"); CheckEqual(new[] { "ARI0036:base.Run(world)" }, run.Reported); } public void InheritedPassageThatCallsRunByName_IsReportedAtTheCallToIt() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Basis : Arc { protected void Again(World world) { Run(world); } protected override void Run(World world) { wait: while (!world.Open.Contains(""go"")) { Yield(); } } } public partial class Derived : Basis { protected override void Run(World world) { Again(world); } }"); CheckEqual(new[] { "ARI0036:Again(world)" }, run.Reported); } public void CallThatReachesRunByNameThroughAnotherInheritedPassage_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Basis : Arc { protected void Again(World world) { Run(world); } protected override void Run(World world) { wait: while (!world.Open.Contains(""go"")) { Yield(); } } } public abstract partial class Middle : Basis { protected void Relay(World world) { Again(world); } } public partial class Derived : Middle { protected override void Run(World world) { Relay(world); } }"); CheckEqual(new[] { "ARI0036:Relay(world)" }, run.Reported); } public void EveryRunInAChainOfThreeThatReachesTheCall_IsReported() { GeneratorRun run = GeneratorRun.Of(Looping + @" public abstract partial class Middle : Basis { protected override void Run(World world) { base.Run(world); } } public partial class Leaf : Middle { protected override void Run(World world) { base.Run(world); } }"); CheckEqual(new[] { "ARI0036:base.Run(world)", "ARI0036:base.Run(world)" }, run.Reported); } public void DerivedRunThatNeverReachesTheBasesCall_IsNotReported() { GeneratorRun run = GeneratorRun.Of(Looping + @" public partial class Derived : Basis { private void Push(World world, int kept) { wait: while (!world.Open.Contains(""push"" + kept)) { Yield(); } if (kept < 1) { again: Run(world); } } protected override void Run(World world) { int lap = Laps++; start: Push(world, lap); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void BaseRunThatDoesntCallRunByName_IsNotReported() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Basis : Arc { protected override void Run(World world) { wait: while (!world.Open.Contains(""go"")) { Yield(); } } } public partial class Derived : Basis { protected override void Run(World world) { base.Run(world); } }"); CheckEqual(0, run.Reported.Count); } public void RunThatCallsItselfByNameWithNothingOverridingIt_IsNotReported() { GeneratorRun run = GeneratorRun.Of(Looping.Replace("abstract ", "")); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } private const string LoopingLibrary = @" public abstract partial class Basis : Arc { public int Laps; protected void Again(string context) { Run(context); } protected void Relay(string context) { Again(context); } protected void Quiet(string context) { wait: while (context.Length == 0) { Yield(); } } protected override void Run(string context) { int lap = Laps++; wait: while (context.Length == 0) { Yield(); } if (lap < 1) { again: Run(context); } } }"; private static GeneratorRun DerivedFromLibrary(string body) { Microsoft.CodeAnalysis.MetadataReference library = GeneratorRun.CompileGeneratedLibrary("Bases", LoopingLibrary); return GeneratorRun.Of(library, @" public partial class Derived : Basis { protected override void Run(string context) { " + body + @" } }"); } public void BaseRunFromAnotherAssemblyThatCallsRunByName_IsReported() { GeneratorRun run = DerivedFromLibrary("base.Run(context);"); CheckEqual(new[] { "ARI0036:base.Run(context)" }, run.Reported); CheckTrue(run.Messages.Single().Contains("'Basis'")); } public void InheritedPassageFromAnotherAssemblyThatCallsRunByName_IsReported() { GeneratorRun run = DerivedFromLibrary("Again(context);"); CheckEqual(new[] { "ARI0036:Again(context)" }, run.Reported); } public void InheritedPassageFromAnotherAssemblyThatReachesRunByNameThroughAnother_IsReported() { GeneratorRun run = DerivedFromLibrary("Relay(context);"); CheckEqual(new[] { "ARI0036:Relay(context)" }, run.Reported); } public void InheritedPassageFromAnotherAssemblyThatNeverReachesRunByName_IsNotReported() { GeneratorRun run = DerivedFromLibrary("Quiet(context);"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void CopyThatReachesRunByName_IsMarkedWithTheClassItStaysOn() { GeneratorRun run = GeneratorRun.Of(Looping); CheckTrue(run.CodeBehind.Contains("[global::Ariadne.ReachesRunByName(\"Basis\")]")); } public void CopyThatNeverReachesRunByName_IsNotMarked() { GeneratorRun run = GeneratorRun.Of(@" public partial class Quiet : Arc { protected override void Run(World world) { wait: while (!world.Open.Contains(""go"")) { Yield(); } } }"); CheckFalse(run.CodeBehind.Contains("ReachesRunByName")); } } public class ReportPassageAsDelegateTests : TestFixture { public const string Plant = @" public partial class Plant : Arc { public void Grow(World world) { wait: while (!world.Open.Contains(""go"")) { Yield(); } } public int Measure(World world) { return 1; } protected override void Run(World world) { } }"; public void MethodGroupAssignedToADelegate_IsReported() { GeneratorRun run = GeneratorRun.Of(Plant + @" public partial class Gardener : Arc { private void Act(World world) { wait: while (!world.Open.Contains(""go"")) { Yield(); } } protected override void Run(World world) { System.Action stored = Act; } }"); CheckEqual(new[] { "ARI0037:Act" }, run.Reported); } public void MethodGroupPassedToADelegateConstructor_IsReported() { GeneratorRun run = GeneratorRun.Of(Plant + @" public partial class Gardener : Arc { private void Act(World world) { wait: while (!world.Open.Contains(""go"")) { Yield(); } } protected override void Run(World world) { var stored = new System.Action(Act); } }"); CheckEqual(new[] { "ARI0037:Act" }, run.Reported); } public void MethodGroupPassedAsAnArgument_IsReported() { GeneratorRun run = GeneratorRun.Of(Plant + @" public partial class Gardener : Arc { private void Act(World world) { wait: while (!world.Open.Contains(""go"")) { Yield(); } } private void Store(System.Action action) { } protected override void Run(World world) { Store(Act); } }"); CheckEqual(new[] { "ARI0037:Act" }, run.Reported); } public void PassageOfAnotherArc_IsReportedWithItsReceiver() { GeneratorRun run = GeneratorRun.Of(Plant + @" public partial class Gardener : Arc { public Plant Target; protected override void Run(World world) { System.Action stored = Target.Grow; } }"); CheckEqual(new[] { "ARI0037:Target.Grow" }, run.Reported); } public void PassageInheritedFromABase_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Host : Arc { protected void Hold(World world) { hold: while (!world.Open.Contains(""go"")) { Yield(); } } } public partial class Gardener : Host { protected override void Run(World world) { System.Action stored = Hold; } }"); CheckEqual(new[] { "ARI0037:Hold" }, run.Reported); } public void PassageOfAClassBuiltIntoAnotherAssembly_IsReported() { Microsoft.CodeAnalysis.MetadataReference library = GeneratorRun.CompileGeneratedLibrary("Plants", @" public partial class Plant : Arc { public void Grow(string context) { wait: while (context.Length == 0) { Yield(); } } protected override void Run(string context) { } }"); GeneratorRun run = GeneratorRun.Of(library, @" public partial class Gardener : Arc { public Plant Target; protected override void Run(string context) { System.Action stored = Target.Grow; } }"); CheckEqual(new[] { "ARI0037:Target.Grow" }, run.Reported); } public void DelegateMadeInAConstructor_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Gardener : Arc { private System.Action stored; public Gardener() { stored = Act; } private void Act(World world) { wait: while (!world.Open.Contains(""go"")) { Yield(); } } protected override void Run(World world) { } }"); CheckEqual(new[] { "ARI0037:Act" }, run.Reported); } public void MethodGroupOfAnOrdinaryMethod_IsNotReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Gardener : Arc { private void Tidy(World world) { } protected override void Run(World world) { System.Action stored = Tidy; } }"); CheckEqual(0, run.Reported.Count); } public void CallingAPassage_IsNotReported() { GeneratorRun run = GeneratorRun.Of(Plant + @" public partial class Gardener : Arc { public Plant Target; private void Act(World world) { wait: while (!world.Open.Contains(""go"")) { Yield(); } } protected override void Run(World world) { first: Act(world); second: Target.Grow(world); } }"); CheckEqual(0, run.Reported.Count); } public void NameofAPassage_IsNotReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Gardener : Arc { public string Label = nameof(Act); private void Act(World world) { wait: while (!world.Open.Contains(""go"")) { Yield(); } } protected override void Run(World world) { Act(world); } }"); CheckEqual(0, run.Reported.Count); } public void LambdaThatCallsAPassage_IsReportedAsAMisplacedCall() { GeneratorRun run = GeneratorRun.Of(@" public partial class Gardener : Arc { private void Act(World world) { wait: while (!world.Open.Contains(""go"")) { Yield(); } } protected override void Run(World world) { System.Action later = () => Act(world); } }"); CheckEqual(new[] { "ARI0004:Act(world)" }, run.Reported); } } public class ReportPausesInNonScriptTests : TestFixture { public void PauseInAMethodOfAClassWithoutRun_IsAPassageOfThatClass() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Shared : Arc { protected void WaitForGo(World world) { wait: while (!world.Open.Contains(""go"")) { Yield(); } } } public partial class Mission : Shared { protected override void Run(World world) { WaitForGo(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void PauseInAPropertyOfAClassWithoutRun_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Shared : Arc { protected bool Ready { get { Yield(); return true; } } }"); CheckEqual(new[] { "ARI0003:Yield()" }, run.Reported); CheckTrue(run.Messages.Contains("ARI0003:Yield() can't pause inside a member that isn't a method " + "with a block body. Only a method with a block body can pause.")); } public void PauseInAClassThatIsntPartial_IsReportedWithTheClass() { GeneratorRun run = GeneratorRun.Of(@" public abstract class Shared : Arc { protected bool Ready { get { Yield(); return true; } } }"); CheckEqual(new[] { "ARI0018:Shared", "ARI0003:Yield()" }, run.Reported); } public void ClassWithoutPauses_ReportsNothing() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Shared : Arc { public int Reward; protected void Note(World world) { world.Open.Add(""noted""); } }"); CheckEqual(0, run.Reported.Count); } } } }