using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using UnitTestSharp;
namespace Vellum.Generator.UnitTests
{
public class SaveGeneratorTests : TestFixture
{
public class SavedWithFileTests : TestFixture
{
public void HandWrittenAndGeneratedConverters_AreNamedInOrder()
{
VellumRun run = VellumRun.Of(ConverterScanTests.Point +
ConverterScanTests.Converter("PointConverter", "Point") +
"[Saveable] public partial struct Marked { public Point At; }\n");
CheckEqual(0, run.Compiled.Count);
CheckEqual("// \r\n" +
"// The save converters Vellum.Generator found, for this assembly to save with.\r\n" +
"[assembly: global::Vellum.SavedWith(typeof(global::Marked.__Converter))]\r\n" +
"[assembly: global::Vellum.SavedWith(typeof(global::PointConverter))]\r\n", run.SavedWith);
}
public void ConverterInAReferencedAssembly_IsNamed()
{
MetadataReference library = VellumRun.CompileLibrary("Cargo",
"[Saveable] public partial struct Crate { public int Count; }");
VellumRun run = VellumRun.Of(library, "[Saveable] public partial struct Hold { public Crate Crate; }");
CheckEqual(0, run.Reported.Count);
CheckEqual(0, run.Compiled.Count);
CheckTrue(run.SavedWith.Contains("typeof(global::Crate.__Converter)"));
}
public void NoConverters_WritesNoFile()
{
VellumRun run = VellumRun.Of("public class Plain { }");
CheckEqual("", run.SavedWith);
}
public void GenericConverters_AreNamedOpen()
{
VellumRun run = VellumRun.Of(ConverterScanTests.Box +
ConverterScanTests.Converter("BoxConverter", "Box") +
"public class Pair { }\n" +
ConverterScanTests.Converter("PairConverter",
"Pair"));
CheckEqual(0, run.Reported.Count);
CheckEqual(0, run.Compiled.Count);
CheckEqual("// \r\n" +
"// The save converters Vellum.Generator found, for this assembly to save with.\r\n" +
"[assembly: global::Vellum.SavedWith(typeof(global::BoxConverter<>))]\r\n" +
"[assembly: global::Vellum.SavedWith(typeof(global::PairConverter<,>))]\r\n", run.SavedWith);
}
public void FamilyConverter_IsNamedOpenLikeAnyGenericConverter()
{
VellumRun run = VellumRun.Of(ConverterScanTests.DescribeTests.Creature +
ConverterScanTests.Family("CreatureConverter", "CreatureType", "Creature"));
CheckEqual(0, run.Reported.Count);
CheckEqual(0, run.Compiled.Count);
CheckTrue(run.SavedWith.Contains("typeof(global::CreatureConverter<>)"));
}
public void GenericConverterNestedInAType_IsNamedOpen()
{
VellumRun run = VellumRun.Of(ConverterScanTests.Box + "public class Holder\n{\n" +
ConverterScanTests.Converter("BoxConverter", "Box") + "}\n");
CheckEqual(0, run.Compiled.Count);
CheckTrue(run.SavedWith.Contains("typeof(global::Holder.BoxConverter<>)"));
}
}
public class InitializeTests : TestFixture
{
public void ConverterWithoutAParameterlessConstructor_IsReported()
{
VellumRun run = VellumRun.Of(ConverterScanTests.Point + ConverterScanTests.Converter("PointConverter",
"Point", constructor: " public PointConverter(int unused) { }\n"));
CheckEqual(new[] { "VEL0001:PointConverter" }, run.Reported);
CheckEqual("", run.SavedWith);
}
public void ConverterForABuiltInType_IsReported()
{
VellumRun run = VellumRun.Of(ConverterScanTests.Converter("IntConverter", "int"));
CheckEqual(new[] { "VEL0003:IntConverter" }, run.Reported);
}
public void ConverterForACollectionVellumSaves_IsReportedAndNotNamed()
{
VellumRun run = VellumRun.Of(
ConverterScanTests.Converter("ListConverter", "List") +
ConverterScanTests.Converter("ArrayConverter", "ItemType[]") +
"public class Unknown { }\n" +
ConverterScanTests.Converter("UnknownsConverter", "List"));
CheckEqual(new[] { "VEL0003:ListConverter", "VEL0003:ArrayConverter", "VEL0003:UnknownsConverter" },
run.Reported);
CheckEqual("", run.SavedWith);
}
public void MemberHoldingCollections_IsNotReported()
{
VellumRun run = VellumRun.Of(ConverterScanTests.Point +
ConverterScanTests.Converter("PointConverter", "Point") +
"[Saveable] public partial struct Hold { public List Stops; public int[] Counts; " +
"public Dictionary> Zones; }");
CheckEqual(0, run.Reported.Count);
CheckEqual(0, run.Compiled.Count);
}
public void GenericConverterWhoseTypeCantBeMatched_IsReportedAndNotNamed()
{
VellumRun run = VellumRun.Of(ConverterScanTests.Converter("AnyConverter", "ItemType",
marked: false));
CheckEqual(new[] { "VEL0007:AnyConverter" }, run.Reported);
CheckEqual("", run.SavedWith);
}
public void TwoGenericConvertersForOneGenericType_AreBothReported()
{
VellumRun run = VellumRun.Of(ConverterScanTests.Box +
ConverterScanTests.Converter("BoxConverter", "Box") +
ConverterScanTests.Converter("OtherBoxConverter", "Box"));
CheckEqual(new[] { "VEL0002:BoxConverter", "VEL0002:OtherBoxConverter" }, run.Reported);
CheckEqual("", run.SavedWith);
}
public void GenericConverterInAReferencedAssembly_IsNamedAndUsedForMembers()
{
MetadataReference library = VellumRun.CompileLibrary("Boxes", ConverterScanTests.Box +
ConverterScanTests.Converter("BoxConverter", "Box"));
VellumRun run = VellumRun.Of(library,
"[Saveable] public partial struct Hold { public Box Count; public Box> Names; }");
CheckEqual(0, run.Reported.Count);
CheckEqual(0, run.Compiled.Count);
CheckTrue(run.SavedWith.Contains("typeof(global::BoxConverter<>)"));
}
public void MemberHoldingAGenericTypeOfSomethingUnsaveable_IsReportedOnTheMember()
{
VellumRun run = VellumRun.Of(ConverterScanTests.Box +
ConverterScanTests.Converter("BoxConverter", "Box") +
"public class Unknown { }\n" +
"[Saveable] public partial struct Hold { public Box Stops; }");
CheckEqual(new[] { "VEL0004:Stops" }, run.Reported);
}
public void TwoConvertersForOneType_AreBothReported()
{
VellumRun run = VellumRun.Of(ConverterScanTests.Point +
ConverterScanTests.Converter("PointConverter", "Point") +
ConverterScanTests.Converter("OtherPointConverter", "Point"));
CheckEqual(new[] { "VEL0002:PointConverter", "VEL0002:OtherPointConverter" }, run.Reported);
CheckEqual("", run.SavedWith);
}
public void LibraryAndLocalConverterOfOneType_AreBothReportedTheLibrarysWithoutALocation()
{
MetadataReference bridge = VellumRun.CompileLibrary("Bridge",
ConverterScanTests.Point + ConverterScanTests.Converter("PointConverter", "Point"));
VellumRun run = VellumRun.Of(bridge, ConverterScanTests.Converter("OtherPointConverter", "Point"));
CheckEqual(new[] { "VEL0002", "VEL0002:OtherPointConverter" }, run.Reported);
}
public void ConverterInAReferencedAssembly_IsNamedAndNotReported()
{
MetadataReference bridge = VellumRun.CompileLibrary("Bridge",
ConverterScanTests.Point + ConverterScanTests.Converter("PointConverter", "Point"));
VellumRun run = VellumRun.Of(bridge, "[Saveable] public partial struct Hold { public Point At; }");
CheckEqual(0, run.Reported.Count);
CheckEqual(0, run.Compiled.Count);
CheckTrue(run.SavedWith.Contains("typeof(global::PointConverter)"));
}
public void ConverterOfALibraryThatNeverRanTheGenerator_IsNotFound()
{
MetadataReference bridge = VellumRun.CompileLibrary("Bridge",
ConverterScanTests.Point + ConverterScanTests.Converter("PointConverter", "Point"),
runGenerator: false);
VellumRun run = VellumRun.Of(bridge, "[Saveable] public partial struct Hold { public Point At; }");
CheckEqual(new[] { "VEL0004:At" }, run.Reported);
}
public void SaveableTypeOfAnUnsupportedShape_IsReportedAndNotGenerated()
{
VellumRun run = VellumRun.Of("[Saveable] public struct Whole { }");
CheckEqual(new[] { "VEL0005:Whole" }, run.Reported);
CheckEqual(0, run.Saveables.Count);
CheckEqual("", run.SavedWith);
}
public void NonPrivateOwnLoader_IsReported()
{
VellumRun run = VellumRun.Of(
"[Saveable] public partial struct Open { public Open(SaveReader reader) { } }");
CheckEqual(new[] { "VEL0006:Open" }, run.Reported);
}
public void HandWrittenConverterForASaveableType_IsReportedAtTheConverter()
{
VellumRun run = VellumRun.Of("[Saveable] public partial struct Point { }\n" +
ConverterScanTests.Converter("PointConverter", "Point"));
CheckEqual(new[] { "VEL0002:PointConverter" }, run.Reported);
}
public void SaveableTypeThatAlreadyHasAConverter_GeneratesNothing()
{
VellumRun run = VellumRun.Of(
"[Saveable] public partial struct Marked { public class __Converter { } }");
CheckEqual(0, run.Saveables.Count);
CheckEqual(0, run.Reported.Count);
}
public void MemberThatCantBeSaved_IsReportedWhereItsDeclared()
{
VellumRun run = VellumRun.Of(
"[Saveable] public partial struct Route { public int Length; public object Stops; }");
CheckEqual(new[] { "VEL0004:Stops" }, run.Reported);
}
public void MembersOfSaveableTypes_ThemselvesIncluded_AreSaveable()
{
VellumRun run = VellumRun.Of(@"
[Saveable] public partial struct Point { public int X; }
[Saveable] public sealed partial class Node { public Point At; public Node Next; public Point? Maybe; }");
CheckEqual(0, run.Reported.Count);
CheckEqual(0, run.Compiled.Count);
}
public const string Wolves = ConverterScanTests.DescribeTests.Creature +
"public class Wolf : Creature { }\npublic class Fish { }\n" +
"public class Herd { }\npublic class NameHerd : Herd { }\n";
public static readonly string CreatureFamily = ConverterScanTests.Family(
"CreatureConverter", "CreatureType", "Creature");
public static readonly string CreatureOnly = ConverterScanTests.Converter("CreatureOnlyConverter",
"Creature");
public static readonly string HerdFamily =ConverterScanTests.Family(
"HerdConverter", "HerdType", "Herd");
public const string Crates = ConverterScanTests.Box + "public class Foo { }\n";
public void MemberOfAGenericTypeWithAMarkedParameterNothingSaves_IsReported()
{
VellumRun run = VellumRun.Of(Crates +
ConverterScanTests.Converter("BoxConverter", "Box") +
"[Saveable] public partial struct Hold { public Box Lost; public Box Fine; }");
CheckEqual(new[] { "VEL0004:Lost" }, run.Reported);
}
public void MemberOfAGenericTypeWithNoMarkedParameter_IsAllowedWhateverItIsMadeOf()
{
VellumRun run = VellumRun.Of(Crates +
ConverterScanTests.Converter("BoxConverter", "Box", marked: false) +
"[Saveable] public partial struct Hold { public Box Free; }");
CheckEqual(0, run.Reported.Count);
CheckEqual(0, run.Compiled.Count);
}
public void MarkedParameterOfAMethod_IsReported()
{
VellumRun run = VellumRun.Of("public class Helper { public void Use<[Saveable] ItemType>() { } }");
CheckEqual(new[] { "VEL0012:ItemType" }, run.Reported);
}
public void MarkedParameterOfAClassThatIsNotAConverter_IsReported()
{
VellumRun run = VellumRun.Of("public class Holder<[Saveable] ItemType> { }");
CheckEqual(new[] { "VEL0012:ItemType" }, run.Reported);
}
public void MarkedParameterOfAGenericSaveable_IsReported()
{
VellumRun run = VellumRun.Of("[Saveable] public partial struct Holder<[Saveable] ItemType> { }");
CheckEqual(new[] { "VEL0012:ItemType" }, run.Reported);
}
public void MarkedParameterOfAnAbstractConverter_IsAllowed()
{
VellumRun run = VellumRun.Of(Crates + ConverterScanTests.Converter("BoxConverter",
"Box", declaration: "public abstract class"));
CheckEqual(0, run.Reported.Count);
}
public void MarkedParameterAFamilyStandsFor_IsReported()
{
VellumRun run = VellumRun.Of(ConverterScanTests.DescribeTests.Creature +
ConverterScanTests.Family("CreatureConverter", "CreatureType", "Creature")
.Replace("CreatureConverter", "CreatureConverter<[Saveable] CreatureType>"));
CheckTrue(run.Reported.Contains("VEL0013:CreatureType"));
}
public void MemberDerivedFromAFamilysBaseWithAnUnsaveableMarkedArgument_IsReported()
{
VellumRun run = VellumRun.Of("public class Plankton { }\npublic class Herd { }\n" +
"public class IntHerd : Herd { }\npublic class PlanktonHerd : Herd { }\n" +
ConverterScanTests.Family("HerdConverter", "HerdType",
"Herd") +
"[Saveable] public partial struct Hold { public IntHerd Fine; public PlanktonHerd Lost; }");
CheckEqual(new[] { "VEL0004:Lost" }, run.Reported);
}
public static readonly string PetFamily = ConverterScanTests.Family("PetConverter", "PetType",
"IPet");
public static readonly string DogFamily = ConverterScanTests.Family("DogConverter", "DogType",
"IDog");
public static readonly string AquaticFamily = ConverterScanTests.Family("AquaticConverter",
"AquaticType", "IAquatic");
public const string Animals = ConverterScanTests.DescribeTests.Pets + "public interface IAquatic { }\n" +
"public class Hound : IDog { }\npublic class Otter : IAquatic { }\n" +
"public class Seal : IPet, IAquatic { }\npublic struct Pup : IPet { }\n";
public void InterfaceFamily_IsNamedOpenLikeAnyGenericConverter()
{
VellumRun run = VellumRun.Of(Animals + PetFamily);
CheckEqual(0, run.Reported.Count);
CheckTrue(run.SavedWith.Contains("typeof(global::PetConverter<>)"));
}
public void MemberHoldingTypesAnInterfaceFamilySaves_IsAllowed()
{
VellumRun run = VellumRun.Of(Animals + PetFamily + @"
[Saveable] public partial struct Hold { public IPet Any; public IDog Dog; public Hound Hound; public Pup Pup; }");
CheckEqual(0, run.Reported.Count);
CheckEqual(0, run.Compiled.Count);
}
public void MemberHoldingATypeNoInterfaceFamilySaves_IsReported()
{
VellumRun run = VellumRun.Of(Animals + PetFamily + @"
[Saveable] public partial struct Hold { public Otter Lost; public IAquatic AlsoLost; public Pup Found; }");
CheckEqual(new[] { "VEL0004:Lost", "VEL0004:AlsoLost" }, run.Reported);
}
public void MemberHoldingABaseInterfaceOfAFamilysInterface_IsReported()
{
VellumRun run = VellumRun.Of(Animals + DogFamily + @"
[Saveable] public partial struct Hold { public IPet Base; public IDog Found; }");
CheckEqual(new[] { "VEL0004:Base" }, run.Reported);
}
public void MemberHoldingAnObject_IsReportedWhateverFamiliesExist()
{
VellumRun run = VellumRun.Of(Animals + PetFamily + @"
[Saveable] public partial struct Hold { public object Anything; public IPet Found; }");
CheckEqual(new[] { "VEL0004:Anything" }, run.Reported);
}
public void TypeInTwoUnrelatedInterfaceFamilies_IsReported()
{
VellumRun run = VellumRun.Of(Animals + PetFamily + AquaticFamily);
CheckEqual(new[] { "VEL0014:Seal" }, run.Reported);
}
public static VellumRun RunWithLibraries(string animals, string source = "")
{
MetadataReference pets = VellumRun.CompileLibrary("Pets", ConverterScanTests.DescribeTests.Pets +
"public interface IAquatic { }");
MetadataReference bridge = VellumRun.CompileLibrary("Bridge", PetFamily + AquaticFamily,
runGenerator: true, libraries: new[] { pets });
MetadataReference zoo = VellumRun.CompileLibrary("Zoo", animals, runGenerator: false,
libraries: new[] { pets });
return VellumRun.Of(new[] { pets, bridge, zoo }, withAriadne: false, files: ("Source.cs", source));
}
public void TypeInTwoUnrelatedInterfaceFamiliesOfReferencedAssemblies_IsReportedWithoutALocation()
{
VellumRun run = RunWithLibraries("public class Goldfish : IPet, IAquatic { }");
CheckEqual(new[] { "VEL0014" }, run.Reported);
}
public void ReferencedTypesNoInterfaceFamilyAmbiguouslySaves_AreNotReported()
{
VellumRun run = RunWithLibraries("public class Lynx : IPet { }\npublic class Otter : IAquatic { }\n" +
"public class Hound : IDog { }");
CheckEqual(0, run.Reported.Count);
}
public void TypeInTwoUnrelatedInterfaceFamilies_IsReportedWhetherLocalOrReferenced()
{
VellumRun run = RunWithLibraries("public class Goldfish : IPet, IAquatic { }",
"public class Seal : IPet, IAquatic { }");
CheckEqual(new[] { "VEL0014", "VEL0014:Seal" }, run.Reported.OrderBy(text => text,
System.StringComparer.Ordinal).ToList());
}
public void TypeInAnInterfaceFamilyAndTheOneItExtends_IsNotAmbiguous()
{
VellumRun run = VellumRun.Of(Animals + PetFamily + DogFamily);
CheckEqual(0, run.Reported.Count);
}
public void ClassFamily_SettlesWhatWouldBeAmbiguousBetweenInterfaces()
{
VellumRun run = VellumRun.Of(Animals + PetFamily + AquaticFamily + ConverterScanTests.Family(
"SealConverter", "SealType", "Seal"));
CheckEqual(0, run.Reported.Count);
}
public void TypeImplementingAFamilysGenericInterfaceTwice_IsReported()
{
VellumRun run = VellumRun.Of(ConverterScanTests.DescribeTests.Pets +
"public class Kennel : IBowl, IBowl { }\n" +
ConverterScanTests.Family("BowlConverter", "BowlType", "IBowl"));
CheckEqual(new[] { "VEL0015:Kennel" }, run.Reported);
}
public void ClassImplementingAnInterfaceAFamilySaves_IsNotReportedAsUnsavedSubtype()
{
VellumRun run = VellumRun.Of(Wolves + CreatureOnly + ConverterScanTests.DescribeTests.Pets +
"public class Hybrid : Creature, IPet { }\n" + PetFamily);
CheckEqual(new[] { "VEL0010:Wolf" }, run.Reported);
}
public void MarkedOtherParameterOfAFamily_IsAllowed()
{
VellumRun run = VellumRun.Of("public class Herd { }\n" + ConverterScanTests.Family(
"HerdConverter", "HerdType", "Herd"));
CheckEqual(0, run.Reported.Count);
}
public void MemberHoldingATypeAFamilySaves_IsAllowed()
{
VellumRun run = VellumRun.Of(Wolves + CreatureFamily + HerdFamily + @"
[Saveable] public partial struct Hold
{
public Creature Base; public Wolf Derived; public NameHerd Names; public Herd Fishy;
public List Pack; public Dictionary> Herds;
}");
CheckEqual(0, run.Reported.Count);
CheckEqual(0, run.Compiled.Count);
}
public void MemberHoldingATypeNoFamilySaves_IsReportedWithFamiliesAround()
{
VellumRun run = VellumRun.Of(Wolves + CreatureFamily + @"
[Saveable] public partial struct Hold { public Fish Lost; public Herd AlsoLost; public Wolf Found; }");
CheckEqual(new[] { "VEL0004:Lost", "VEL0004:AlsoLost" }, run.Reported);
}
public void FamilyAndConverterOfItsBaseClass_AreNotDuplicates()
{
VellumRun run = VellumRun.Of(Wolves + CreatureFamily + CreatureOnly);
CheckEqual(0, run.Reported.Count);
}
public void TwoFamiliesOfOneBaseClass_AreDuplicates()
{
VellumRun run = VellumRun.Of(Wolves + CreatureFamily +
ConverterScanTests.Family("OtherCreatureConverter", "CreatureType", "Creature"));
CheckEqual(new[] { "VEL0002:CreatureConverter", "VEL0002:OtherCreatureConverter" },
run.Reported);
}
public void ClassDerivedFromAConvertedOneAFamilyAlsoSaves_IsNotReported()
{
VellumRun run = VellumRun.Of(Wolves + CreatureOnly + CreatureFamily);
CheckEqual(0, run.Reported.Count);
}
public void ClassDerivedFromAConvertedOneNoFamilySaves_IsStillReported()
{
VellumRun run = VellumRun.Of(Wolves + CreatureOnly);
CheckEqual(new[] { "VEL0010:Wolf" }, run.Reported);
}
public void FamilyOfABaseNoOneCanCreateAConverterFor_IsReportedUnmatchable()
{
VellumRun run = VellumRun.Of(Wolves + ConverterScanTests.Family("CreatureConverter",
"CreatureType", "Creature, System.IDisposable"));
CheckEqual(new[] { "VEL0007:CreatureConverter" }, run.Reported);
}
public void MembersOfAnUnsupportedShape_AreStillChecked()
{
VellumRun run = VellumRun.Of("[Saveable] public struct Whole { public object Anything; }");
CheckEqual(new[] { "VEL0005:Whole", "VEL0004:Anything" }, run.Reported);
}
public void TypeParameterAndMissingType_AreLeftToTheirOwnErrors()
{
VellumRun run = VellumRun.Of(
"[Saveable] public partial struct Boxed { public T Value; public Missing Lost; }");
CheckEqual(0, run.Reported.Count);
}
public void GenericSaveableUsedWithAnUnsaveableArgument_IsReportedWhereItIsUsed()
{
VellumRun run = VellumRun.Of(@"
[Saveable] public partial struct Duo { public FirstType First; public SecondType Second; }
[Saveable] public partial struct Hold { public Duo Pair; public Duo Fine; }");
CheckEqual(new[] { "VEL0004:Pair" }, run.Reported);
}
public void GenericSaveableUsedWithAnArgumentOnlyANoSaveMemberUses_IsAllowed()
{
VellumRun run = VellumRun.Of(@"
[Saveable] public partial struct Tagged { public int Value; [NoSave] public TagType Tag; }
[Saveable] public partial struct Hold { public Tagged