using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using UnitTestSharp;
namespace Vellum.Generator.UnitTests
{
public class ConverterModelTests : TestFixture
{
///
/// Returns a converter that differs from the default one only in the arguments given.
///
public static ConverterModel Variant(string assemblyName = "Assembly", string metadataName = "Meta",
string name = "Name", string convertedName = "Converted", ConverterProblem problem = ConverterProblem.None,
string filePath = "File.cs", int start = 1, int line = 1, string familyBase = null,
int[] saveablePositions = null, bool familyIsInterface = false, string[] familyExtends = null,
string familyBaseAssembly = null, string problemDetail = null)
{
return new ConverterModel(assemblyName, metadataName, name, convertedName, problem, filePath,
new TextSpan(start, 4), new LinePositionSpan(new LinePosition(line, 0), new LinePosition(line, 4)),
familyBase, new EquatableArray((saveablePositions ?? new int[0]).ToImmutableArray()),
familyIsInterface, new EquatableArray((familyExtends ?? new string[0]).ToImmutableArray()),
familyBaseAssembly, problemDetail);
}
public class CtorTests : TestFixture
{
public void KeepsEveryArgument()
{
var span = new TextSpan(3, 4);
var lineSpan = new LinePositionSpan(new LinePosition(1, 2), new LinePosition(1, 6));
var model = new ConverterModel("Assembly", "Outer+Meta", "Name", "Converted", ConverterProblem.BuiltIn,
"File.cs", span, lineSpan);
CheckEqual("Assembly", model.AssemblyName);
CheckEqual("Outer+Meta", model.MetadataName);
CheckEqual("Name", model.Name);
CheckEqual("Converted", model.ConvertedName);
CheckEqual(ConverterProblem.BuiltIn, model.Problem);
CheckEqual("File.cs", model.FilePath);
CheckEqual(span, model.Span);
CheckEqual(lineSpan, model.LineSpan);
CheckNull(model.FamilyBase);
}
public void FamilyBase_IsKept()
{
CheckEqual("Creature", Variant(familyBase: "Creature").FamilyBase);
}
public void InterfaceFamilyDetails_AreKept()
{
ConverterModel model = Variant(familyIsInterface: true, familyExtends: new[] { "IPet" });
CheckTrue(model.FamilyIsInterface);
CheckEqual(new[] { "IPet" }, model.FamilyExtends.Items.ToArray());
}
public void SaveablePositions_AreKept()
{
ConverterModel model = Variant(saveablePositions: new[] { 1, 2 });
CheckEqual(new[] { 1, 2 }, model.SaveablePositions.Items.ToArray());
}
public void ProblemDetail_IsKept()
{
CheckEqual("T has a class constraint",
Variant(problem: ConverterProblem.Unmatchable, problemDetail: "T has a class constraint")
.ProblemDetail);
CheckNull(Variant().ProblemDetail);
}
public void FamilyBaseAssembly_IsKept()
{
CheckEqual("Pets", Variant(familyBase: "IPet", familyBaseAssembly: "Pets").FamilyBaseAssembly);
CheckNull(Variant().FamilyBaseAssembly);
}
}
public class MetadataNameOfTests : TestFixture
{
public static string NameOf(string source, string metadataName)
{
return ConverterModel.MetadataNameOf(
VellumRun.Compile(("Source.cs", source)).GetTypeByMetadataName(metadataName));
}
public void TypeInTheGlobalNamespace_IsItsName()
{
CheckEqual("Plain", NameOf("public class Plain { }", "Plain"));
}
public void TypeInANamespace_IsQualified()
{
CheckEqual("Games.Core.Plain", NameOf("namespace Games.Core { public class Plain { } }",
"Games.Core.Plain"));
}
public void NestedType_IsJoinedToItsContainersWithAPlus()
{
CheckEqual("Games.Outer+Middle+Plain", NameOf(
"namespace Games { public class Outer { public class Middle { public class Plain { } } } }",
"Games.Outer+Middle+Plain"));
}
public void GenericType_HasItsNumberOfTypeParameters()
{
CheckEqual("Games.Plain`2", NameOf("namespace Games { public class Plain { } }",
"Games.Plain`2"));
}
public void TypeNestedInAGenericType_HasTheContainersNumberOfTypeParametersToo()
{
CheckEqual("Games.Outer`1+Plain", NameOf(
"namespace Games { public class Outer { public class Plain { } } }", "Games.Outer`1+Plain"));
}
}
public class TypeofNameTests : TestFixture
{
public void TypeInTheGlobalNamespace_IsGloballyQualified()
{
CheckEqual("global::PointConverter", Variant(metadataName: "PointConverter").TypeofName);
}
public void TypeInANamespace_IsQualifiedByIt()
{
CheckEqual("global::Game.Core.PointConverter",
Variant(metadataName: "Game.Core.PointConverter").TypeofName);
}
public void NestedType_IsJoinedToItsContainersWithADot()
{
CheckEqual("global::Game.Holder.PointConverter",
Variant(metadataName: "Game.Holder+PointConverter").TypeofName);
}
public void GenericType_HasItsTypeArgumentsLeftOpen()
{
CheckEqual("global::BoxConverter<>", Variant(metadataName: "BoxConverter`1").TypeofName);
CheckEqual("global::PairConverter<,>", Variant(metadataName: "PairConverter`2").TypeofName);
CheckEqual("global::Wide<,,,,,,,,,,>", Variant(metadataName: "Wide`11").TypeofName);
}
public void TypeNestedInAGenericType_HasEachOfTheirsLeftOpen()
{
CheckEqual("global::Outer<>.Inner<,>", Variant(metadataName: "Outer`1+Inner`2").TypeofName);
}
public void MatchesWhatTheCompilerNames()
{
CSharpCompilation compilation = VellumRun.Compile(("Source.cs",
"namespace Game { public class Outer { public class Inner " +
"{ } } }"));
INamedTypeSymbol inner = compilation.GetTypeByMetadataName("Game.Outer`1+Inner`2");
CheckEqual("global::Game.Outer<>.Inner<,>",
Variant(metadataName: ConverterModel.MetadataNameOf(inner)).TypeofName);
CheckEqual("global::Game.Outer<>.Inner<,>",
inner.ConstructUnboundGenericType().ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
}
}
public class WithProblemTests : TestFixture
{
public void ChangesOnlyTheProblem()
{
ConverterModel original = Variant();
ConverterModel changed = original.WithProblem(ConverterProblem.Duplicate);
CheckEqual(ConverterProblem.Duplicate, changed.Problem);
CheckEqual(Variant(problem: ConverterProblem.Duplicate), changed);
CheckEqual(ConverterProblem.None, original.Problem);
}
public void KeepsTheFamilyDetails()
{
ConverterModel changed = Variant(familyBase: "IPet", familyIsInterface: true,
familyExtends: new[] { "IAnimal" }, saveablePositions: new[] { 0 }, familyBaseAssembly: "Pets",
problemDetail: "T has no constraint").WithProblem(ConverterProblem.Duplicate);
CheckEqual("T has no constraint", changed.ProblemDetail);
CheckEqual("Pets", changed.FamilyBaseAssembly);
CheckEqual("IPet", changed.FamilyBase);
CheckTrue(changed.FamilyIsInterface);
CheckEqual(new[] { "IAnimal" }, changed.FamilyExtends.Items.ToArray());
CheckEqual(new[] { 0 }, changed.SaveablePositions.Items.ToArray());
}
}
public class ToDiagnosticTests : TestFixture
{
public const string FileText = "aaa\nbbb\nccc\nddd\n";
///
/// Returns a compilation holding File.cs, four lines of four characters each.
///
public static CSharpCompilation SourceFile()
{
return CSharpCompilation.Create("Test",
new[] { CSharpSyntaxTree.ParseText(FileText, path: "File.cs") });
}
public void NoProblem_IsNull()
{
CheckNull(Variant().ToDiagnostic(SourceFile()));
}
public void Unusable_IsVEL0001()
{
CheckEqual("VEL0001", Variant(problem: ConverterProblem.Unusable).ToDiagnostic(SourceFile()).Id);
}
public void Duplicate_IsVEL0002()
{
CheckEqual("VEL0002", Variant(problem: ConverterProblem.Duplicate).ToDiagnostic(SourceFile()).Id);
}
public void BuiltIn_IsVEL0003()
{
CheckEqual("VEL0003", Variant(problem: ConverterProblem.BuiltIn).ToDiagnostic(SourceFile()).Id);
}
public void Unmatchable_IsVEL0007()
{
CheckEqual("VEL0007", Variant(problem: ConverterProblem.Unmatchable).ToDiagnostic(SourceFile()).Id);
}
public void Unmatchable_SaysWhatKeepsItFromBeingMatched()
{
Diagnostic diagnostic = Variant(problem: ConverterProblem.Unmatchable,
problemDetail: "ItemType has a class constraint").ToDiagnostic(SourceFile());
CheckTrue(diagnostic.GetMessage().StartsWith(
"'Name' converts a Converted, but ItemType has a class constraint. A generic converter must"));
}
public void Interface_IsVEL0011()
{
CheckEqual("VEL0011", Variant(problem: ConverterProblem.Interface).ToDiagnostic(SourceFile()).Id);
}
public void Always_NamesTheConverterAndTheTypeItConverts()
{
Diagnostic diagnostic = Variant(problem: ConverterProblem.BuiltIn).ToDiagnostic(SourceFile());
CheckTrue(diagnostic.GetMessage().StartsWith("'Name' converts a Converted,"));
}
public void ConverterInAFile_PointsAtWhereItIsDeclared()
{
Diagnostic diagnostic = Variant(problem: ConverterProblem.Unusable, start: 8)
.ToDiagnostic(SourceFile());
FileLinePositionSpan span = diagnostic.Location.GetLineSpan();
CheckEqual("File.cs", span.Path);
CheckEqual(new TextSpan(8, 4), diagnostic.Location.SourceSpan);
CheckEqual(new LinePosition(2, 0), span.StartLinePosition);
}
public void ConverterInAFile_IsLocatedInTheCompilationsOwnSyntaxTree()
{
CSharpCompilation compilation = SourceFile();
Diagnostic diagnostic = Variant(problem: ConverterProblem.Unusable).ToDiagnostic(compilation);
CheckTrue(diagnostic.Location.IsInSource);
CheckTrue(diagnostic.Location.SourceTree == compilation.SyntaxTrees.Single());
}
public void ConverterInAFileTheCompilationLacks_FallsBackToItsPath()
{
Diagnostic diagnostic = Variant(problem: ConverterProblem.Unusable, filePath: "Gone.cs", start: 7,
line: 3).ToDiagnostic(SourceFile());
FileLinePositionSpan span = diagnostic.Location.GetLineSpan();
CheckEqual(LocationKind.ExternalFile, diagnostic.Location.Kind);
CheckEqual("Gone.cs", span.Path);
CheckEqual(new LinePosition(3, 0), span.StartLinePosition);
}
public void ConverterInAReferencedAssembly_HasNoLocation()
{
Diagnostic diagnostic = Variant(problem: ConverterProblem.Duplicate, filePath: null)
.ToDiagnostic(SourceFile());
CheckEqual(LocationKind.None, diagnostic.Location.Kind);
}
}
public class EqualsTests : TestFixture
{
public void SameValues_AreEqual()
{
CheckTrue(Variant().Equals(Variant()));
CheckTrue(Variant().Equals((object)Variant()));
}
public void Null_IsNotEqual()
{
CheckFalse(Variant().Equals((ConverterModel)null));
CheckFalse(Variant().Equals((object)null));
}
public void OtherType_IsNotEqual()
{
CheckFalse(Variant().Equals("Name"));
}
public void DifferentAssemblyName_IsNotEqual()
{
CheckFalse(Variant().Equals(Variant(assemblyName: "Other")));
}
public void DifferentMetadataName_IsNotEqual()
{
CheckFalse(Variant().Equals(Variant(metadataName: "Other")));
}
public void DifferentName_IsNotEqual()
{
CheckFalse(Variant().Equals(Variant(name: "Other")));
}
public void DifferentConvertedName_IsNotEqual()
{
CheckFalse(Variant().Equals(Variant(convertedName: "Other")));
}
public void DifferentProblem_IsNotEqual()
{
CheckFalse(Variant().Equals(Variant(problem: ConverterProblem.Unusable)));
}
public void DifferentFilePath_IsNotEqual()
{
CheckFalse(Variant().Equals(Variant(filePath: "Other.cs")));
}
public void DifferentSpan_IsNotEqual()
{
CheckFalse(Variant().Equals(Variant(start: 2)));
}
public void DifferentLineSpan_IsNotEqual()
{
CheckFalse(Variant().Equals(Variant(line: 2)));
}
public void DifferentFamilyBase_IsNotEqual()
{
CheckFalse(Variant().Equals(Variant(familyBase: "Creature")));
}
public void DifferentFamilyKind_IsNotEqual()
{
CheckFalse(Variant().Equals(Variant(familyIsInterface: true)));
}
public void DifferentExtendedInterfaces_AreNotEqual()
{
CheckFalse(Variant().Equals(Variant(familyExtends: new[] { "IPet" })));
}
public void DifferentSaveablePositions_AreNotEqual()
{
CheckFalse(Variant().Equals(Variant(saveablePositions: new[] { 0 })));
}
public void DifferentProblemDetail_IsNotEqual()
{
CheckFalse(Variant().Equals(Variant(problemDetail: "T has no constraint")));
}
public void DifferentFamilyBaseAssembly_IsNotEqual()
{
CheckFalse(Variant().Equals(Variant(familyBaseAssembly: "Pets")));
}
}
public class GetHashCodeTests : TestFixture
{
public void EqualModels_HaveEqualHashCodes()
{
CheckEqual(Variant().GetHashCode(), Variant().GetHashCode());
}
}
}
}