using System.Linq; using Microsoft.CodeAnalysis; using UnitTestSharp; namespace Ariadne.Generator.UnitTests { public class VirtualPassageTests : TestFixture { private const string Guarded = "public abstract partial class Guard : Arc\n" + "{\n" + " protected virtual void Greet(World world)\n" + " {\n" + " hello:\n" + " while (!world.Open.Contains(\"hello\"))\n" + " {\n" + " Yield();\n" + " }\n" + " }\n" + "\n" + " protected override void Run(World world)\n" + " {\n" + " Greet(world);\n" + " }\n" + "}\n" + "\n" + "public partial class Talkative : Guard\n" + "{\n" + " protected override void Greet(World world)\n" + " {\n" + " again:\n" + " while (!world.Open.Contains(\"again\"))\n" + " {\n" + " Yield();\n" + " }\n" + " }\n" + "}\n"; public class DetectionTests : TestFixture { public void VirtualMethodThatNeverPauses_IsAPassage() { GeneratorRun run = GeneratorRun.Of(@" public partial class Plain : Arc { protected virtual int Cost() { return 5; } protected override void Run(World world) { int cost = Cost(); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("protected virtual bool Cost__Passage(")); } public void CallerOfAVirtualMethod_BecomesAPassage() { GeneratorRun run = GeneratorRun.Of(@" public partial class Caller : Arc { protected virtual void Hook() { } private void Before() { Hook(); } protected override void Run(World world) { Before(); } }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("bool Before__Passage(")); } public void OverrideOfAPassage_IsAPassageEvenIfItNeverPauses() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Base : Arc { protected virtual void Hook() { } } public partial class Derived : Base { protected override void Hook() { } protected override void Run(World world) { Hook(); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehinds.Values.Any(text => text.Contains("protected override bool Hook__Passage("))); } public void AbstractMethod_IsAPassageWithABodylessCopy() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Trader : Arc { protected abstract void Barter(World world, int offer); protected override void Run(World world) { Barter(world, 3); } } public partial class Haggler : Trader { protected override void Barter(World world, int offer) { deal: while (!world.Open.Contains(""deal"")) { Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("protected abstract bool Barter__Passage(")); CheckTrue(run.CodeBehind.Contains("protected abstract void Barter__LocalsToSave(")); } public void ExpressionBodiedVirtualMethod_IsNotAPassage() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Base : Arc { protected virtual int Cost() => 5; protected override void Run(World world) { int cost = Cost(); } }"); CheckEqual(0, run.Reported.Count); CheckFalse(run.CodeBehind.Contains("Cost__Passage")); } public void OverrideOfAnArcMember_IsNotAPassage() { GeneratorRun run = GeneratorRun.Of(@" public partial class Naming : Arc { public override string ToString() { return ""named""; } protected override void Run(World world) { Yield(); } }"); CheckFalse(run.CodeBehind.Contains("ToString__Passage")); } } public class CopyTests : TestFixture { public void RootCopy_IsVirtualAndOverridesTakeTheRootsFrame() { GeneratorRun run = GeneratorRun.Of(Guarded); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains( "protected override bool Greet__Passage(global::Guard.__Frame __rootFrame")); CheckTrue(run.CodeBehind.Contains("var __frame = (__Frame)__rootFrame;")); } public void SavingMethod_FollowsTheCopyDownTheHierarchy() { GeneratorRun run = GeneratorRun.Of(Guarded); CheckTrue(run.CodeBehind.Contains("protected virtual void Greet__LocalsToSave(")); CheckTrue(run.CodeBehind.Contains( "protected override void Greet__LocalsToSave(global::Guard.__Frame __rootFrame")); } public void RunningFlag_IsDeclaredOnceByTheRootsFrame() { GeneratorRun run = GeneratorRun.Of(Guarded); string flag = "public object Greet__active;"; CheckEqual(1, (run.CodeBehind.Length - run.CodeBehind.Replace(flag, "").Length) / flag.Length); } public void SealedOverride_StaysSealed() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Base : Arc { protected virtual void Hook() { } } public partial class Derived : Base { protected sealed override void Hook() { } protected override void Run(World world) { Hook(); } }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehinds.Values.Any(text => text.Contains("protected sealed override bool Hook__Passage("))); } } private const string KeeperLibrary = @" public abstract partial class Keeper : Arc { protected virtual void Greet(string context) { hello: while (context != ""hello"") { Yield(); } } protected abstract void Weigh(string context); protected override void Run(string context) { greet: Greet(context); weigh: Weigh(context); } }"; public class OtherAssemblyTests : TestFixture { public void OverrideOfAPassageBuiltIntoAnotherAssembly_FollowsItsCopy() { MetadataReference library = GeneratorRun.CompileGeneratedLibrary("Keepers", KeeperLibrary); GeneratorRun run = GeneratorRun.Of(library, @" public partial class Local : Keeper { protected override void Greet(string context) { hi: while (context != ""hi"") { Yield(); } } protected override void Weigh(string context) { weighing: while (context != ""weighed"") { Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains( "protected override bool Greet__Passage(global::Keeper.__Frame __rootFrame")); CheckTrue(run.CodeBehind.Contains( "protected override bool Weigh__Passage(global::Keeper.__Frame __rootFrame")); } public void OverrideThatNeverPausesOfAPassageBuiltIntoAnotherAssembly_IsAPassageToo() { MetadataReference library = GeneratorRun.CompileGeneratedLibrary("Keepers", KeeperLibrary); GeneratorRun run = GeneratorRun.Of(library, @" public partial class Quiet : Keeper { protected override void Greet(string context) { } protected override void Weigh(string context) { } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("protected override bool Greet__Passage(")); } } public class GenericClassTests : TestFixture { public void OverrideInAClosedDerivedClass_TakesTheConstructedRootFrame() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Shelf : Arc { protected virtual void Stock(World world, ItemType item) { stocked: while (!world.Open.Contains(""stocked"")) { Yield(); } } protected override void Run(World world) { Stock(world, default(ItemType)); } } public partial class StringShelf : Shelf { protected override void Stock(World world, string item) { counting: while (!world.Open.Contains(""counted"")) { Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("global::Shelf.__Frame __rootFrame")); } public void OverrideInAnOpenDerivedClass_TakesTheRootFrameOfItsOwnArgument() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Shelf : Arc { protected virtual void Stock(World world, ItemType item) { stocked: while (!world.Open.Contains(""stocked"")) { Yield(); } } protected override void Run(World world) { Stock(world, default(ItemType)); } } public partial class AnyShelf : Shelf { protected override void Stock(World world, ValueType item) { counting: while (!world.Open.Contains(""counted"")) { Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("global::Shelf.__Frame __rootFrame")); } } public class OtherArcTests : TestFixture { public void VirtualPassageCalledOnAnotherArc_CompilesWithoutComplaint() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Door : Arc { public virtual void Open(World world) { swing: while (!world.Open.Contains(""swing"")) { Yield(); } } protected override void Run(World world) { first: Open(world); } } public partial class Mission : Arc { public Door Entrance; protected override void Run(World world) { enter: Entrance.Open(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } } public class DiagnosticTests : TestFixture { public void PausingOverrideOfAnExpressionBodiedMethod_SaysToGiveTheBaseABlockBody() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Base : Arc { protected virtual void Hook(World world) => world.Open.Clear(); } public partial class Derived : Base { protected override void Hook(World world) { wait: while (!world.Open.Contains(""go"")) { Yield(); } } protected override void Run(World world) { Hook(world); } }"); CheckEqual(new[] { "ARI0025:Hook" }, run.Reported); CheckTrue(run.Messages.Single().Contains("expression body")); CheckTrue(run.Messages.Single().Contains("Give it a block body")); } public void PausingOverrideOfAPlainMethodWithABlockBody_KeepsTheShortMessage() { 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.Messages.Contains( "ARI0025:'ToString' pauses, so it can't be an override of a method that isn't a passage.")); } public void OverloadedVirtualPassage_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Twice : Arc { protected virtual void Hook(int count) { } protected virtual void Hook(string text) { } protected override void Run(World world) { Hook(1); } }"); CheckEqual(new[] { "ARI0012:Hook", "ARI0012:Hook" }, run.Reported); } public void OverrideSharingItsRootsName_IsNotAnOverload() { GeneratorRun run = GeneratorRun.Of(Guarded); CheckFalse(run.Reported.Any(report => report.StartsWith("ARI0012"))); } public void GenericVirtualPassage_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Generic : Arc { protected virtual void Hook(ItemType item) { } protected override void Run(World world) { Hook(1); } }"); CheckTrue(run.Reported.Contains("ARI0038:Hook")); } public void GenericVirtualPassage_SaysToKeepTheTypeParameterInANonVirtualPassage() { GeneratorRun run = GeneratorRun.Of(@" public partial class Generic : Arc { protected virtual void Hook(ItemType item) { } protected override void Run(World world) { Hook(1); } }"); CheckTrue(run.Messages.Single().Contains("passage that isn't virtual")); } public void VirtualPassageThatCallsItself_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Descending : Arc { protected virtual void Hook(World world) { again: while (!world.Open.Contains(""again"")) { Yield(); } Hook(world); } protected override void Run(World world) { Hook(world); } }"); CheckTrue(run.Reported.Contains("ARI0038:Hook")); } } public class BaseCallTests : TestFixture { public void BaseCallToAVirtualPassage_RunsTheBaseCopyOnAFrameOfItsOwn() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Base : Arc { protected virtual void Hook(World world) { int count = 1; first: while (!world.Open.Contains(""first"")) { Yield(); } System.GC.KeepAlive(count); } } public partial class Derived : Base { protected override void Hook(World world) { string count = ""a""; before: base.Hook(world); System.GC.KeepAlive(count); } protected override void Run(World world) { Hook(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("base.Hook__Passage(")); CheckTrue(run.CodeBehind.Contains("base.Hook__LocalsToSave(")); } public void BaseCallToAnAbstractPassage_IsLeftToTheCompiler() { GeneratorRun run = GeneratorRun.Of(@" public abstract partial class Base : Arc { protected abstract void Hook(World world); } public partial class Derived : Base { protected override void Hook(World world) { before: base.Hook(world); } protected override void Run(World world) { Hook(world); } }"); CheckTrue(run.Compiled.Any(report => report.StartsWith("CS0205"))); } } } }