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);
}
}
}
}