using System.Linq;
using Microsoft.CodeAnalysis;
using UnitTestSharp;
namespace Ariadne.Generator.UnitTests
{
public class CodeBehindWriterTests : TestFixture
{
public class ComposeTests : TestFixture
{
public const string Header = "// \r\n" +
"// Ariadne.Generator's copy of the scripts in this file: the code that actually runs. " +
"Edits here are overwritten.\r\n" +
"// In the copy beside the script, #line paths are relative to it; the compiled code uses full " +
"paths.\r\n" +
"#pragma warning disable\r\n";
public static ArcComposition ComposeIn(string projectDirectory, params ArcOutput[] outputs)
{
return CodeBehindWriter.Compose(new EquatableArray(
System.Collections.Immutable.ImmutableArray.Create(outputs)), projectDirectory);
}
public static ArcComposition Compose(params ArcOutput[] outputs)
{
return ComposeIn("", outputs);
}
public static string[] Described(ArcComposition composition)
{
return composition.Diagnostics.Items.Select(data => data.Descriptor.Id + ":" + data.Message).ToArray();
}
public void NoArcs_WritesNothing()
{
ArcComposition composition = Compose();
CheckEqual(0, composition.Files.Items.Length);
CheckEqual(0, composition.Diagnostics.Items.Length);
}
public void ScriptFile_IsNamedAfterItAndHoldsItsHeaderUsingsAndClasses()
{
ArcFile file = Compose(ArcOutputTests.Variant(filePath: "Intro.cs")).Files.Items.Single();
CheckEqual("Intro.CodeBehind.cs", file.FileName);
CheckEqual(Header + "using System;\r\n\r\n" + ArcOutputTests.Class, file.Text);
}
public void ScriptFileInAFolder_IsNamedAfterItsPathBelowTheProject()
{
ArcFile file = ComposeIn(@"C:\Game", ArcOutputTests.Variant(filePath: @"C:\Game\Missions\Intro.cs"))
.Files.Items.Single();
CheckEqual("Missions/Intro.CodeBehind.cs", file.FileName);
}
public void SameFileNameInTwoFolders_GetsACodeBehindEach()
{
ArcComposition composition = ComposeIn(@"C:\Game",
ArcOutputTests.Variant(filePath: @"C:\Game\A\Intro.cs", typeName: "First"),
ArcOutputTests.Variant(filePath: @"C:\Game\B\Intro.cs", typeName: "Second"));
CheckEqual(new[] { "A/Intro.CodeBehind.cs", "B/Intro.CodeBehind.cs" },
composition.Files.Items.Select(file => file.FileName));
CheckEqual(0, composition.Diagnostics.Items.Length);
}
public void ArcsOfOneFile_FollowEachOtherInTheOrderTheyAreDeclaredWithEachUsingOnce()
{
ArcOutput later = ArcOutputTests.Variant(typeName: "Later", start: 50,
usings: new[] { "using System;", "using System.Linq;" }, classText: "later\r\n");
ArcOutput earlier = ArcOutputTests.Variant(typeName: "Earlier", start: 5, classText: "earlier\r\n");
ArcFile file = Compose(later, earlier).Files.Items.Single();
CheckEqual(Header + "using System;\r\nusing System.Linq;\r\n\r\nearlier\r\n\r\nlater\r\n", file.Text);
}
public void FilesAreInPathOrderWhateverOrderTheArcsArrive()
{
ArcFile[] files = Compose(ArcOutputTests.Variant(filePath: "B.cs"),
ArcOutputTests.Variant(filePath: "A.cs")).Files.Items.ToArray();
CheckEqual(new[] { "A.CodeBehind.cs", "B.CodeBehind.cs" },
files.Select(file => file.FileName).ToArray());
}
public void FileWithNoName_IsNamedAfterItsFirstClass()
{
ArcOutput second = ArcOutputTests.Variant(filePath: "", typeName: "Second", start: 9);
ArcOutput first = ArcOutputTests.Variant(filePath: "", typeName: "First", start: 2);
CheckEqual("First.CodeBehind.cs", Compose(second, first).Files.Items.Single().FileName);
}
public void SecondFileWithTheSameCodeBehind_IsReportedAtItsClassesAndNotWritten()
{
ArcOutput first = ArcOutputTests.Variant(filePath: @"C:\GAME\Script.cs", typeName: "First");
ArcOutput second = ArcOutputTests.Variant(filePath: @"C:\Game\Script.cs", typeName: "Second");
ArcComposition composition = Compose(second, first);
CheckEqual(new[] { "C_/GAME/Script.CodeBehind.cs" },
composition.Files.Items.Select(file => file.FileName));
CheckEqual(1, composition.Diagnostics.Items.Length);
CheckEqual("ARI0007", composition.Diagnostics.Items[0].Descriptor.Id);
CheckEqual(second.TypePlace, composition.Diagnostics.Items[0].Place);
}
public void FileNamesThatDifferOnlyInCase_AreTheSameName()
{
ArcComposition composition = Compose(ArcOutputTests.Variant(filePath: "A.cs"),
ArcOutputTests.Variant(filePath: "a.cs"));
CheckEqual(1, composition.Files.Items.Length);
CheckEqual(new[] { "ARI0007" }, composition.Diagnostics.Items.Select(data => data.Descriptor.Id));
}
public void ClassProblems_AreReportedEvenForAClassWithNothingToWrite()
{
DiagnosticData problem = DiagnosticDataTests.Variant(message: "class problem");
ArcOutput output = ArcOutputTests.Variant(classText: null, classProblems: new[] { problem });
ArcComposition composition = Compose(output);
CheckEqual(0, composition.Files.Items.Length);
CheckEqual(new[] { problem }, composition.Diagnostics.Items);
}
public void ScriptProblems_AreReportedOnlyForAFileThatIsWritten()
{
DiagnosticData kept = DiagnosticDataTests.Variant(message: "kept");
DiagnosticData dropped = DiagnosticDataTests.Variant(message: "dropped");
ArcComposition composition = Compose(
ArcOutputTests.Variant(filePath: "A.cs", scriptProblems: new[] { kept }),
ArcOutputTests.Variant(filePath: "a.cs", scriptProblems: new[] { dropped }));
CheckTrue(composition.Diagnostics.Items.Contains(kept));
CheckFalse(composition.Diagnostics.Items.Contains(dropped));
}
}
public class HintNameOfTests : TestFixture
{
public void FileInTheProjectFolder_IsNamedAfterItsFileName()
{
CheckEqual("Intro.CodeBehind.cs", CodeBehindWriter.HintNameOf(@"C:\Game\Intro.cs", @"C:\Game"));
}
public void FileInAFolder_KeepsTheFoldersBelowTheProject()
{
CheckEqual("Missions/Act1/Intro.CodeBehind.cs",
CodeBehindWriter.HintNameOf(@"C:\Game\Missions\Act1\Intro.cs", @"C:\Game"));
}
public void ProjectFolder_MayEndInASlashOrDifferInCaseOrDirection()
{
CheckEqual("A/Intro.CodeBehind.cs", CodeBehindWriter.HintNameOf(@"C:\Game\A\Intro.cs", @"C:\Game\"));
CheckEqual("A/Intro.CodeBehind.cs", CodeBehindWriter.HintNameOf(@"C:\GAME\A\Intro.cs", @"c:/game"));
}
public void ProjectFolderNamingJustAPrefix_IsNotTheFilesProject()
{
CheckEqual("C_/Game2/Intro.CodeBehind.cs",
CodeBehindWriter.HintNameOf(@"C:\Game2\Intro.cs", @"C:\Game"));
}
public void FileOutsideTheProject_KeepsItsWholePathWithoutTheDriveColon()
{
CheckEqual("D_/Shared/Intro.CodeBehind.cs",
CodeBehindWriter.HintNameOf(@"D:\Shared\Intro.cs", @"C:\Game"));
}
public void NoProjectFolder_KeepsThePathAsGiven()
{
CheckEqual("A/Intro.CodeBehind.cs", CodeBehindWriter.HintNameOf(@"A\Intro.cs", ""));
CheckEqual("A/Intro.CodeBehind.cs", CodeBehindWriter.HintNameOf("/A/Intro.cs", ""));
}
public void StepsUpAndHere_AreLeftOutOfTheName()
{
CheckEqual("__/Shared/Intro.CodeBehind.cs", CodeBehindWriter.HintNameOf(@"..\Shared\.\Intro.cs", ""));
}
public void FileNameWithDots_OnlyLosesItsLastExtension()
{
CheckEqual("Intro.Act1.CodeBehind.cs", CodeBehindWriter.HintNameOf("Intro.Act1.cs", ""));
}
}
public class WriteFileTests : TestFixture
{
public void ScriptInANamespace_KeepsTheNamespacesUsings()
{
GeneratorRun run = GeneratorRun.Of(@"
namespace Missions
{
using System.Text;
public partial class Namespaced : Arc
{
protected override void Run(World world)
{
var text = new StringBuilder();
wait:
while (!world.Open.Contains(""wait""))
{
text.Append('.');
Yield();
}
}
}
}");
CheckEqual(0, run.Compiled.Count);
CheckTrue(run.CodeBehind.Contains("namespace Missions\r\n{\r\nusing System.Text;"));
}
}
public class FrameTests : TestFixture
{
public void ScriptWithLocals_DeclaresThemInItsFrame()
{
GeneratorRun run = GeneratorRun.Of(@"
public partial class Keeper : Arc
{
protected override void Run(World world)
{
var gates = new List();
wait:
while (gates.Count == 0)
{
Yield();
}
}
}");
CheckEqual(0, run.Compiled.Count);
CheckTrue(run.CodeBehind.Contains("public partial class __Frame : global::Ariadne.Frame"));
CheckTrue(run.CodeBehind.Contains(
"public global::System.Collections.Generic.List Run__gates;"));
CheckTrue(run.CodeBehind.Contains("protected override global::Ariadne.Frame __CreateFrame()"));
CheckTrue(run.CodeBehind.Contains("return new __Frame();"));
}
public void ScriptWithoutLocals_StillDeclaresAFrame()
{
GeneratorRun run = GeneratorRun.Of(@"
public partial class Plain : Arc
{
protected override void Run(World world)
{
wait:
while (world.Open.Count == 0)
{
Yield();
}
}
}");
CheckEqual(0, run.Compiled.Count);
CheckTrue(run.CodeBehind.Contains("public partial class __Frame : global::Ariadne.Frame"));
}
public void ArcThatIsNotAScript_DeclaresAFrame()
{
GeneratorRun run = GeneratorRun.Of(@"
public abstract partial class Mission : Arc
{
public int Reward;
}");
CheckEqual(0, run.Compiled.Count);
CheckTrue(run.CodeBehind.Contains("public partial class __Frame : global::Ariadne.Frame"));
CheckTrue(run.CodeBehind.Contains("return new __Frame();"));
}
public void ClassDerivedFromAGeneratedClass_ExtendsItsFrame()
{
GeneratorRun run = GeneratorRun.Of(@"
public abstract partial class Mission : Arc
{
public int Reward;
}
public partial class Derived : Mission
{
protected override void Run(World world)
{
wait:
while (world.Open.Count == 0)
{
Yield();
}
}
}");
CheckEqual(0, run.Compiled.Count);
CheckTrue(run.CodeBehind.Contains("public partial class __Frame : global::Mission.__Frame"));
}
public void ClassDerivedFromAnotherAssemblysFrame_ExtendsIt()
{
MetadataReference missions = GeneratorRun.CompileLibrary("Missions", @"
public abstract class Mission : Arc
{
public class __Frame : global::Ariadne.Frame
{
}
}");
GeneratorRun run = GeneratorRun.Of(missions, @"
public partial class Derived : Mission
{
protected override void Run(string context)
{
}
}");
CheckTrue(run.CodeBehind.Contains("public partial class __Frame : global::Mission.__Frame"));
}
public void ClassDerivedFromAnotherAssemblysClassWithoutAFrame_ExtendsTheBaseFrame()
{
MetadataReference missions = GeneratorRun.CompileLibrary("Missions",
"public abstract class Mission : Arc { public int Stage; }");
GeneratorRun run = GeneratorRun.Of(missions, @"
public partial class Derived : Mission
{
protected override void Run(string context)
{
}
}");
CheckTrue(run.CodeBehind.Contains("public partial class __Frame : global::Ariadne.Frame"));
}
public void ClassDerivedFromAHandBuiltClass_ExtendsTheBaseFrame()
{
GeneratorRun run = GeneratorRun.Of(@"
public abstract class Hand : Arc
{
protected override bool __Resume(World world, Ariadne.TickTime time)
{
return true;
}
protected override void Run(World world)
{
}
protected override void __SaveFields(IArcWriter writer)
{
}
}
public partial class Derived : Hand
{
protected override void Run(World world)
{
}
}");
CheckTrue(run.CodeBehind.Contains("public partial class __Frame : global::Ariadne.Frame"));
}
public void ClassDerivedFromAClassWithItsOwnResume_ExtendsTheBaseFrame()
{
GeneratorRun run = GeneratorRun.Of(@"
public abstract class Own : Arc
{
protected override bool __Resume(World world, Ariadne.TickTime time)
{
return true;
}
protected override void Run(World world)
{
}
}
public partial class Derived : Own
{
protected override void Run(World world)
{
}
}");
CheckTrue(run.CodeBehind.Contains("public partial class __Frame : global::Ariadne.Frame"));
}
}
}
}