using System; using System.Linq; using UnitTestSharp; namespace Vellum.Generator.UnitTests { public class SaveableWriterTests : TestFixture { public class WriteTests : TestFixture { public void Struct_CompilesAndIsNamedForTheRuntime() { VellumRun run = VellumRun.Of("[Saveable] public partial struct Point { public int X; }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.SavedWith.Contains( "[assembly: global::Vellum.SavedWith(typeof(global::Point.__Converter))]")); } public void Struct_IsWrittenAsExactlyThisText() { VellumRun run = VellumRun.Of("[Saveable] public partial struct Point { public int X; }"); CheckEqual( "// \r\n" + "// Vellum.Generator's loading constructors and SaveConverter for a [Saveable] type.\r\n" + "partial struct Point\r\n" + "{\r\n" + " private Point(global::Vellum.SaveReader reader, " + "global::Vellum.AutoLoadEveryField load) : this()\r\n" + " {\r\n" + " { if (reader.TryRead(\"X\", out int loaded)) { this.X = loaded; } }\r\n" + " }\r\n" + "\r\n" + " private Point(global::Vellum.SaveReader reader)\r\n" + " : this(reader, default(global::Vellum.AutoLoadEveryField))\r\n" + " {\r\n" + " }\r\n" + "\r\n" + " [global::System.ComponentModel.EditorBrowsable(" + "global::System.ComponentModel.EditorBrowsableState.Never)]\r\n" + " public sealed class __Converter : global::Vellum.SaveConverter\r\n" + " {\r\n" + " public override global::Vellum.SaveNode Write(global::Point value, " + "global::Vellum.SaveSession session)\r\n" + " {\r\n" + " global::Vellum.SaveNode returnMe = global::Vellum.SaveNode.NewTable();\r\n" + " returnMe.Entries[\"X\"] = session.ToNode(value.X);\r\n" + " return returnMe;\r\n" + " }\r\n" + "\r\n" + " public override global::Point Read(global::Vellum.SaveNode node, " + "global::Vellum.SaveSession session)\r\n" + " {\r\n" + " return new global::Point(new global::Vellum.SaveReader(node, session));\r\n" + " }\r\n" + "\r\n" + " public override bool TryListChildren(global::Vellum.SaveNode node, " + "global::Vellum.ChildList children)\r\n" + " {\r\n" + " if (node.Kind != global::Vellum.SaveNode.Kinds.Table)\r\n" + " {\r\n" + " return false;\r\n" + " }\r\n" + "\r\n" + " return true;\r\n" + " }\r\n" + " }\r\n" + "}\r\n", run.Saveable); } public void Members_AreWrittenAndLoadedAsTheirDeclaredTypes() { VellumRun run = VellumRun.Of( "[Saveable] public sealed partial class Crate { public readonly string Name = \"\"; }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.Saveable.Contains( "returnMe.Entries[\"Name\"] = session.ToNode(value.Name);")); CheckTrue(run.Saveable.Contains( "{ if (reader.TryRead(\"Name\", out string loaded)) { this.Name = loaded; } }")); } public void TypesInNamespacesAndOtherTypes_Compile() { VellumRun run = VellumRun.Of(@" namespace Game.Cargo { public static partial class Holds { public partial class Bay { [Saveable] public readonly partial struct Slot { public readonly int Index; } } } }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.SavedWith.Contains("typeof(global::Game.Cargo.Holds.Bay.Slot.__Converter)")); CheckTrue(run.Saveables.ContainsKey("Game.Cargo.Holds.Bay.Slot.Saveable.g.cs")); } public void GenericStructAndClass_CompileAndAreRegisteredOpen() { VellumRun run = VellumRun.Of(@" [Saveable] public partial struct Pair where A : struct { public A First; public B Second; } [Saveable] public sealed partial class Grid { public System.Collections.Generic.List Cells; }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.SavedWith.Contains("typeof(global::Pair<,>.__Converter)")); CheckTrue(run.SavedWith.Contains("typeof(global::Grid<>.__Converter)")); CheckEqual(new[] { "Grid`1.Saveable.g.cs", "Pair`2.Saveable.g.cs" }, run.Saveables.Keys.OrderBy(name => name, System.StringComparer.Ordinal)); } public void TypesNestedInGenericTypes_CompileAndAreRegisteredOpen() { VellumRun run = VellumRun.Of(@" public partial class Outer { [Saveable] public partial struct Inner { public A Value; } public partial class Middle { [Saveable] public sealed partial class Slot { public A First; public B Second; public C Third; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.SavedWith.Contains("typeof(global::Outer<>.Inner.__Converter)")); CheckTrue(run.SavedWith.Contains("typeof(global::Outer<>.Middle<>.Slot<>.__Converter)")); CheckEqual(new[] { "Outer`1.Inner.Saveable.g.cs", "Outer`1.Middle`1.Slot`1.Saveable.g.cs" }, run.Saveables.Keys.OrderBy(name => name, System.StringComparer.Ordinal)); } public void OwnLoader_IsntWrittenAgain() { VellumRun run = VellumRun.Of("[Saveable] public partial struct Point { public int X; " + "private Point(SaveReader reader) : this(reader, default(AutoLoadEveryField)) { X++; } }"); CheckEqual(0, run.Compiled.Count); CheckFalse(run.Saveable.Contains("private Point(global::Vellum.SaveReader reader)\r\n")); } public void ClassWithoutAParameterlessConstructor_ChainsToNothing() { VellumRun run = VellumRun.Of( "[Saveable] public sealed partial class Crate { public int X; public Crate(int x) { X = x; } }"); CheckEqual(0, run.Compiled.Count); CheckFalse(run.Saveable.Contains(": this()")); CheckFalse(run.Saveable.Contains("public Crate()")); } public const string Zoo = @" [Saveable] public abstract partial class Animal { public string Name; } [Saveable] public partial class Dog : Animal { public bool Barks; } [Saveable] public sealed partial class Puppy : Dog { public int Weeks; } [Saveable] public sealed partial class Cat : Animal { }"; public void Hierarchy_CompilesWithoutWarnings() { VellumRun run = VellumRun.Of(Zoo); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckEqual(4, run.Saveables.Count); } public const string Pound = @" [Saveable] public abstract partial class Animal { public string Name; public Animal Friend; } [Saveable] public partial class Dog : Animal { public bool Barks; public System.Collections.Generic.List Pack; } [Saveable] public sealed partial class Puppy : Dog { public int Weeks; public Dog Parent; }"; public void MembersNotOfABuiltInType_AreListedAsChildrenWithTheirTypes() { VellumRun run = VellumRun.Of( "[Saveable] public sealed partial class Crate { public string Name; public Crate Inside; }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.Saveable.Contains("{ if (node.Entries.TryGetValue(\"Inside\", out " + "global::Vellum.SaveNode child)) { children.Add(child, typeof(global::Crate)); } }")); CheckFalse(run.Saveable.Contains("TryGetValue(\"Name\"")); } public void ClassWithoutAHierarchy_ListsItsChildrenInTheConverter() { VellumRun run = VellumRun.Of("[Saveable] public sealed partial class Crate { public Crate Inside; }"); CheckFalse(run.Saveable.Contains("__ListFields")); CheckTrue(run.Saveable.Contains("public override bool TryListChildren(")); } public void NodeThatIsNotATable_ListsNothingAndSaysSo() { VellumRun run = VellumRun.Of("[Saveable] public sealed partial class Crate { public Crate Inside; }"); string text = run.Saveable; CheckTrue(text.IndexOf("return false;", StringComparison.Ordinal) < text.IndexOf("TryGetValue(\"Inside\"", StringComparison.Ordinal)); } public void HierarchyWithChildren_CompilesWithoutWarnings() { VellumRun run = VellumRun.Of(Pound); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void HierarchyWithChildren_ListsEachClassOwnMembersAndChainsUpward() { VellumRun run = VellumRun.Of(Pound); string animal = run.Saveables["Animal.Saveable.g.cs"]; string dog = run.Saveables["Dog.Saveable.g.cs"]; string puppy = run.Saveables["Puppy.Saveable.g.cs"]; CheckTrue(animal.Contains("protected static void __ListFields(global::Vellum.SaveNode node, " + "global::Vellum.ChildList children)")); CheckFalse(animal.Contains("global::Animal.__ListFields")); CheckTrue(dog.Contains("protected static new void __ListFields(")); CheckTrue(dog.Contains("global::Animal.__ListFields(node, children);")); CheckTrue(puppy.Contains("private static new void __ListFields(")); CheckTrue(puppy.Contains("global::Dog.__ListFields(node, children);")); CheckTrue(dog.Contains("typeof(global::System.Collections.Generic.List)")); CheckFalse(dog.Contains("TryGetValue(\"Barks\"")); } public void ConverterInAHierarchy_ListsThroughItsOwnClassMethod() { VellumRun run = VellumRun.Of(Pound); CheckTrue(run.Saveables["Puppy.Saveable.g.cs"].Contains("global::Puppy.__ListFields(node, children);")); CheckTrue(run.Saveables["Dog.Saveable.g.cs"].Contains("global::Dog.__ListFields(node, children);")); } public void AbstractClass_GetsAListingMethodButNoConverter() { VellumRun run = VellumRun.Of(Pound); CheckFalse(run.Saveables["Animal.Saveable.g.cs"].Contains("TryListChildren")); CheckTrue(run.Saveables["Animal.Saveable.g.cs"].Contains("__ListFields")); } public void GenericClassDerivingFromAGenericClass_CallsTheBaseWithItsOwnArguments() { VellumRun run = VellumRun.Of(@" [Saveable] public partial class Base { public T Value; } [Saveable] public sealed partial class Derived : Base { public U Extra; }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.Saveables["Derived`1.Saveable.g.cs"].Contains( "global::Base.__ListFields(node, children);")); } public void ClassLoader_PublishesItselfBeforeReadingAnyField() { VellumRun run = VellumRun.Of(Zoo); foreach (string file in new[] { "Animal.Saveable.g.cs", "Dog.Saveable.g.cs", "Puppy.Saveable.g.cs" }) { string text = run.Saveables[file]; CheckTrue(text.IndexOf("reader.Publish(this);", StringComparison.Ordinal) < text.IndexOf("reader.TryRead", StringComparison.Ordinal)); } } public void StructLoader_DoesNotPublishItself() { CheckFalse(VellumRun.Of("[Saveable] public partial struct Point { public int X; }") .Saveable.Contains("Publish")); } public void AbstractClass_GetsNoConverterAndIsntNamed() { VellumRun run = VellumRun.Of(Zoo); CheckFalse(run.Saveables["Animal.Saveable.g.cs"].Contains("__Converter")); CheckFalse(run.SavedWith.Contains("global::Animal.__Converter")); CheckTrue(run.SavedWith.Contains("global::Dog.__Converter")); CheckTrue(run.SavedWith.Contains("global::Puppy.__Converter")); } public void AbstractClass_KeepsAProtectedImplicitConstructor() { VellumRun run = VellumRun.Of(Zoo); CheckTrue(run.Saveables["Animal.Saveable.g.cs"].Contains("protected Animal()\r\n")); CheckTrue(run.Saveables["Dog.Saveable.g.cs"].Contains("public Dog()\r\n")); } public void ClassOthersCanDeriveFrom_LoadsThroughProtectedConstructors() { VellumRun run = VellumRun.Of(Zoo); string dog = run.Saveables["Dog.Saveable.g.cs"]; CheckTrue(dog.Contains("protected Dog(global::Vellum.SaveReader reader, " + "global::Vellum.AutoLoadEveryField load) : base(reader)")); CheckTrue(dog.Contains("protected Dog(global::Vellum.SaveReader reader)\r\n")); CheckTrue(run.Saveables["Puppy.Saveable.g.cs"].Contains( "private Puppy(global::Vellum.SaveReader reader, " + "global::Vellum.AutoLoadEveryField load) : base(reader)")); } public void ClassOthersCanDeriveFrom_WritesItsFieldsInAMethodItsSubclassesCall() { VellumRun run = VellumRun.Of(Zoo); CheckTrue(run.Saveables["Animal.Saveable.g.cs"].Contains( "protected virtual void __WriteFields(global::Vellum.SaveNode node, " + "global::Vellum.SaveSession session)")); CheckTrue(run.Saveables["Animal.Saveable.g.cs"].Contains( "node.Entries.Add(\"Name\", session.ToNode(this.Name));")); CheckTrue(run.Saveables["Dog.Saveable.g.cs"].Contains("protected override void __WriteFields(")); CheckTrue(run.Saveables["Dog.Saveable.g.cs"].Contains("base.__WriteFields(node, session);")); CheckTrue(run.Saveables["Puppy.Saveable.g.cs"].Contains("base.__WriteFields(node, session);")); CheckTrue(run.Saveables["Dog.Saveable.g.cs"].Contains("value.__WriteFields(returnMe, session);")); } public void SealedClassWithoutABase_StillWritesItsFieldsInTheConverter() { VellumRun run = VellumRun.Of("[Saveable] public sealed partial class Crate { public int X; }"); CheckFalse(run.Saveable.Contains("__WriteFields")); } public void ConverterBelowAConcreteSaveableClass_HidesTheOneAbove() { VellumRun run = VellumRun.Of(Zoo); CheckTrue(run.Saveables["Puppy.Saveable.g.cs"].Contains("public new sealed class __Converter")); CheckTrue(run.Saveables["Dog.Saveable.g.cs"].Contains("public sealed class __Converter")); } public void OwnLoaderOfAClassOthersCanDeriveFrom_RunsWhenASubclassLoads() { VellumRun run = VellumRun.Of(@" [Saveable] public partial class Animal { public int X; public Animal() { } protected Animal(SaveReader reader) : this(reader, default(AutoLoadEveryField)) { X++; } } [Saveable] public sealed partial class Dog : Animal { }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.Saveables["Dog.Saveable.g.cs"].Contains(" : base(reader)")); } public void ClassBelowAnUnmarkedOne_Compiles() { VellumRun run = VellumRun.Of(@" [Saveable] public partial class Animal { public string Name; } public partial class Wolf : Animal { } public sealed partial class Pack : Wolf { public int Size; }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckEqual(3, run.Saveables.Count); } public void ClassWithoutConstructors_StillHasItsParameterlessOne() { VellumRun run = VellumRun.Of("[Saveable] public sealed partial class Crate { public int X; }\n" + "public static class User { public static Crate Make() { return new Crate(); } }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.Saveable.Contains(": this()")); } public void MembersCSharpLetsTheTypeAssign_AreFilledDirectly() { VellumRun run = VellumRun.Of("[Saveable] public sealed partial class Crate " + "{ public int X; public string Y { get; set; } public int Z { get; private set; } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.Saveable.Contains("public override void Fill(global::Crate blank, " + "global::Vellum.SaveNode node, global::Vellum.SaveSession session)")); CheckTrue(run.Saveable.Contains("{ if (reader.TryRead(\"X\", out int loaded)) " + "{ blank.X = loaded; } }")); CheckTrue(run.Saveable.Contains("{ blank.Y = loaded; }")); CheckTrue(run.Saveable.Contains("{ blank.Z = loaded; }")); CheckFalse(run.Saveable.Contains("FieldWriter")); } public void ReadonlyField_IsFilledThroughAFieldWriterTheTypeDeclares() { VellumRun run = VellumRun.Of( "[Saveable] public sealed partial class Crate { public readonly string Name = \"\"; }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.Saveable.Contains("private static readonly " + "global::Vellum.FieldWriter __Fill_Name = " + "new global::Vellum.FieldWriter(\"Name\");")); CheckTrue(run.Saveable.Contains("{ __Fill_Name.Set(blank, loaded); }")); CheckFalse(run.Saveable.Contains("blank.Name")); } public void PropertyWithOnlyAGetter_IsFilledThroughAFieldWriter() { VellumRun run = VellumRun.Of( "[Saveable] public sealed partial class Crate { public int Count { get; } }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.Saveable.Contains("{ __Fill_Count.Set(blank, loaded); }")); CheckTrue(run.Saveable.Contains("(\"Count\");")); } public void KeywordNamedMembers_AreFilledWithTheirOwnNames() { VellumRun run = VellumRun.Of("[Saveable] public sealed partial class Crate " + "{ public readonly int @event; public int @class; }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.Saveable.Contains("new global::Vellum.FieldWriter(\"event\");")); CheckTrue(run.Saveable.Contains("{ __Fill_event.Set(blank, loaded); }")); CheckTrue(run.Saveable.Contains("{ blank.@class = loaded; }")); } public void Struct_GetsNoFill() { VellumRun run = VellumRun.Of("[Saveable] public partial struct Point { public readonly int X; }"); CheckEqual(0, run.Compiled.Count); CheckFalse(run.Saveable.Contains("Fill")); } public void ClassWithoutAHierarchy_FillsInTheConverter() { VellumRun run = VellumRun.Of("[Saveable] public sealed partial class Crate { public int X; }"); CheckFalse(run.Saveable.Contains("__FillFields")); CheckTrue(run.Saveable.Contains( "global::Vellum.SaveReader reader = new global::Vellum.SaveReader(node, session);")); } public void ClassWithNothingToSave_StillCompiles() { VellumRun run = VellumRun.Of("[Saveable] public sealed partial class Crate { }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void ClassWithItsOwnLoader_IsStillFilled() { VellumRun run = VellumRun.Of("[Saveable] public sealed partial class Crate { public int X; " + "private Crate(SaveReader reader) : this(reader, default(AutoLoadEveryField)) { X++; } " + "public Crate() { } }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.Saveable.Contains("public override void Fill(")); } public const string Kennel = @" [Saveable] public abstract partial class Animal { public readonly string Name = """"; } [Saveable] public partial class Dog : Animal { public bool Barks; } [Saveable] public sealed partial class Puppy : Dog { public readonly int Weeks; }"; public void HierarchyWithReadonlyMembers_CompilesWithoutWarnings() { VellumRun run = VellumRun.Of(Kennel); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void Hierarchy_FillsEachClassOwnMembersAndChainsUpward() { VellumRun run = VellumRun.Of(Kennel); string animal = run.Saveables["Animal.Saveable.g.cs"]; string dog = run.Saveables["Dog.Saveable.g.cs"]; string puppy = run.Saveables["Puppy.Saveable.g.cs"]; CheckTrue(animal.Contains("protected static void __FillFields(global::Animal blank, " + "global::Vellum.SaveReader reader)")); CheckFalse(animal.Contains("global::Animal.__FillFields")); CheckTrue(dog.Contains("protected static void __FillFields(global::Dog blank, ")); CheckTrue(dog.Contains("global::Animal.__FillFields(blank, reader);")); CheckTrue(puppy.Contains("private static void __FillFields(global::Puppy blank, ")); CheckTrue(puppy.Contains("global::Dog.__FillFields(blank, reader);")); CheckTrue(animal.Contains("__Fill_Name")); CheckTrue(puppy.Contains("__Fill_Weeks")); CheckFalse(dog.Contains("FieldWriter")); } public void ConverterInAHierarchy_FillsThroughItsOwnClassMethod() { VellumRun run = VellumRun.Of(Kennel); CheckTrue(run.Saveables["Dog.Saveable.g.cs"].Contains("global::Dog.__FillFields(blank, reader);")); CheckTrue(run.Saveables["Puppy.Saveable.g.cs"].Contains( "global::Puppy.__FillFields(blank, reader);")); } public void AbstractClass_GetsAFillingMethodButNoConverter() { VellumRun run = VellumRun.Of(Kennel); CheckFalse(run.Saveables["Animal.Saveable.g.cs"].Contains("public override void Fill(")); CheckTrue(run.Saveables["Animal.Saveable.g.cs"].Contains("__FillFields")); } public void GenericClassDerivingFromAGenericClass_FillsTheBaseWithItsOwnArguments() { VellumRun run = VellumRun.Of(@" [Saveable] public partial class Base { public readonly T Value; } [Saveable] public sealed partial class Derived : Base { public U Extra; }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.Saveables["Derived`1.Saveable.g.cs"].Contains( "global::Base.__FillFields(blank, reader);")); CheckTrue(run.Saveables["Base`1.Saveable.g.cs"].Contains( "global::Vellum.FieldWriter, T> __Fill_Value")); } public void ReadonlyMembersOfTypesNestedInGenericTypes_Compile() { VellumRun run = VellumRun.Of(@" public partial class Outer { public partial class Middle { [Saveable] public sealed partial class Slot { public readonly A First; public readonly C Third; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } } public class FileNameTests : TestFixture { public void IsTheTypesFullName() { VellumRun run = VellumRun.Of("namespace Game { [Saveable] public partial struct Point { } }"); CheckEqual(new[] { "Game.Point.Saveable.g.cs" }, run.Saveables.Keys); } } } }