using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnitTestSharp;
namespace Vellum.UnitTests
{
///
/// [Saveable] types, saved and loaded through the converters Vellum.Generator writes for them.
///
public class SaveableTests : TestFixture
{
public static SaveSession Session()
{
return SaveConverters.For(typeof(SaveableTests).Assembly).CreateSession();
}
public static T RoundTrip(T value)
{
SaveSession session = Session();
return (T)session.FromNode(session.ToNode(value, typeof(T)), typeof(T));
}
public static T LoadedFrom(SaveNode node)
{
return (T)Session().FromNode(node, typeof(T));
}
///
/// Returns the message of the InvalidDataException loading the node throws, or null if it loads.
///
public static IReadOnlyList IssuesOf(SaveNode node, Type type)
{
SaveSession session = Session();
session.FromNode(node, type);
return session.Sandbox.Issues;
}
public static IReadOnlyList IssuePathsOf(SaveNode node, Type type)
{
return IssuesOf(node, type).Select(issue => issue.Path).ToArray();
}
///
/// Returns a chain of crates, each inside the last, saved with the number of the
/// deepest one spoiled.
///
public static SaveNode CrateChainWithABadNumberAtTheBottom(int count)
{
var top = new Crate();
Crate at = top;
for (int i = 1; i < count; ++i)
{
at.Inside = new Crate();
at = at.Inside;
}
SaveNode node = Session().ToNode(top, typeof(Crate));
SaveNode deepest = node;
while (deepest.Entries["Inside"].Kind == SaveNode.Kinds.Table)
{
deepest = deepest.Entries["Inside"];
}
deepest.Entries["Opened"] = SaveNode.OfText("not a number");
return node;
}
public static string Repeated(string text, int times)
{
return string.Concat(Enumerable.Repeat(text, times));
}
public class SaveTests : TestFixture
{
public void WritesOneEntryPerSavedMember()
{
SaveNode node = Session().ToNode(new Crate(), typeof(Crate));
CheckEqualCommutative(new[] { "Name", "At", "Inside", "event", "Opened", "secret" },
node.Entries.Keys);
}
public void NoSaveMember_IsLeftOut()
{
SaveNode node = Session().ToNode(new Vector { X = 1, Length = 9 }, typeof(Vector));
CheckEqualCommutative(new[] { "X", "Y" }, node.Entries.Keys);
}
public void NullClass_IsNull()
{
CheckEqual(SaveNode.Kinds.Null, Session().ToNode(null, typeof(Crate)).Kind);
}
public void ChainFarDeeperThanTheStackAllows_Saves()
{
Chained chain = null;
for (int i = 0; i < 5000; ++i)
{
chain = new Chained { Next = chain };
}
SaveNode node = Session().ToNode(chain, typeof(Chained));
CheckEqual(5000, SaveConvertersTests.DepthOf(node, "Next"));
}
}
public class TryListChildrenTests : TestFixture
{
public void MembersNotOfABuiltInType_AreListedWithTheirDeclaredTypes()
{
SaveNode node = Session().ToNode(new Crate { Inside = new Crate() }, typeof(Crate));
var children = new ChildList();
CheckTrue(new Crate.__Converter().TryListChildren(node, children));
CheckEqual(2, children.Count);
CheckTrue(ReferenceEquals(node.Entries["At"], children.NodeAt(0)));
CheckEqual(typeof(Vector), children.TypeAt(0));
CheckTrue(ReferenceEquals(node.Entries["Inside"], children.NodeAt(1)));
CheckEqual(typeof(Crate), children.TypeAt(1));
}
public void NullMember_IsStillListed()
{
SaveNode node = Session().ToNode(new Crate(), typeof(Crate));
var children = new ChildList();
new Crate.__Converter().TryListChildren(node, children);
CheckEqual(SaveNode.Kinds.Null, children.NodeAt(1).Kind);
}
public void MemberMissingFromTheNode_IsNotListed()
{
SaveNode node = Session().ToNode(new Crate { Inside = new Crate() }, typeof(Crate));
node.Entries.Remove("Inside");
var children = new ChildList();
CheckTrue(new Crate.__Converter().TryListChildren(node, children));
CheckEqual(1, children.Count);
CheckEqual(typeof(Vector), children.TypeAt(0));
}
public void ExtraEntryInTheNode_IsLeftOut()
{
SaveNode node = Session().ToNode(new Crate(), typeof(Crate));
node.Entries["somethingElse"] = SaveNode.NewTable();
var children = new ChildList();
new Crate.__Converter().TryListChildren(node, children);
CheckEqual(2, children.Count);
}
public void TypeWithOnlyBuiltInMembers_ListsNothingAndSucceeds()
{
SaveNode node = Session().ToNode(new Vector { X = 1 }, typeof(Vector));
var children = new ChildList();
CheckTrue(new Vector.__Converter().TryListChildren(node, children));
CheckEqual(0, children.Count);
}
public void NodeThatIsNotATable_ReturnsFalseAndAddsNothing()
{
var children = new ChildList();
CheckFalse(new Crate.__Converter().TryListChildren(SaveNode.NewList(), children));
CheckEqual(0, children.Count);
}
public void NullNode_ReturnsFalse()
{
CheckFalse(new Crate.__Converter().TryListChildren(SaveNode.Null, new ChildList()));
}
public void Hierarchy_ListsTheBaseClassMembersFirst()
{
var truck = new Truck { Top = new Crate(), Spare = new Crate() };
SaveNode node = Session().ToNode(truck, typeof(Truck));
var children = new ChildList();
CheckTrue(new Truck.__Converter().TryListChildren(node, children));
CheckEqual(3, children.Count);
CheckTrue(ReferenceEquals(node.Entries["Top"], children.NodeAt(0)));
CheckEqual(typeof(Crate), children.TypeAt(0));
CheckTrue(ReferenceEquals(node.Entries["Crates"], children.NodeAt(1)));
CheckEqual(typeof(List), children.TypeAt(1));
CheckTrue(ReferenceEquals(node.Entries["Spare"], children.NodeAt(2)));
CheckEqual(typeof(Crate), children.TypeAt(2));
}
public void ClassOthersDeriveFrom_ListsItsOwnAndItsBaseMembers()
{
SaveNode node = Session().ToNode(new Pallet(), typeof(Pallet));
var children = new ChildList();
CheckTrue(new Pallet.__Converter().TryListChildren(node, children));
CheckEqual(2, children.Count);
CheckEqual(typeof(List), children.TypeAt(1));
}
public void SubtypeTable_IsListedFromItsOwnMembersIgnoringTheTypeEntry()
{
SaveNode node = Session().ToNode(new Truck(), typeof(Pallet));
var children = new ChildList();
CheckEqual("Vellum.UnitTests.Truck", node.Entries["__type"].Text);
CheckTrue(new Truck.__Converter().TryListChildren(node, children));
CheckEqual(3, children.Count);
}
public void GenericStruct_ListsMembersOfItsTypeArguments()
{
SaveNode node = Session().ToNode(new Duo { Second = new Crate() },
typeof(Duo));
var children = new ChildList();
CheckTrue(new Duo.__Converter().TryListChildren(node, children));
CheckEqual(2, children.Count);
CheckEqual(typeof(Vector), children.TypeAt(0));
CheckEqual(typeof(Crate), children.TypeAt(1));
}
public void CollectionMembers_AreListedAsTheirCollectionTypes()
{
SaveNode node = Session().ToNode(new Cargo(), typeof(Cargo));
var children = new ChildList();
CheckTrue(new Cargo.__Converter().TryListChildren(node, children));
CheckEqual(11, children.Count);
CheckEqual(typeof(List), children.TypeAt(0));
CheckEqual(typeof(int[]), children.TypeAt(1));
CheckEqual(typeof(Dictionary>), children.TypeAt(2));
CheckEqual(typeof(double[,,,]), children.TypeAt(10));
}
}
public class LoadTests : TestFixture
{
public void BadValue_IsRecordedUnderItsKey()
{
IReadOnlyList issues = IssuesOf(CrateChainWithABadNumberAtTheBottom(1), typeof(Crate));
CheckEqual(1, issues.Count);
CheckEqual("Opened", issues[0].Path);
CheckEqual(LoadIssueKind.BadData, issues[0].Kind);
}
public void BadValue_KeepsTheMembersInitialValueAndLoadsTheRest()
{
SaveNode node = Session().ToNode(new Crate { Name = "kept", Opened = 5 }, typeof(Crate));
node.Entries["Opened"] = SaveNode.OfText("not a number");
var loaded = (Crate)Session().FromNode(node, typeof(Crate));
CheckEqual("kept", loaded.Name);
CheckEqual(new Crate().Opened, loaded.Opened);
}
public void BadValueDeepInAChain_IsRecordedBeneathEachKeyThatLeadsToIt()
{
CheckEqual(new[] { Repeated("Inside: ", 39) + "Opened" },
IssuePathsOf(CrateChainWithABadNumberAtTheBottom(40), typeof(Crate)));
}
public void BadValueFarDeeperThanTheStackAllows_StillNamesThePath()
{
CheckEqual(new[] { Repeated("Inside: ", 4999) + "Opened" },
IssuePathsOf(CrateChainWithABadNumberAtTheBottom(5000), typeof(Crate)));
}
public void BadValueInAListItem_IsRecordedBeneathTheItemsIndex()
{
var value = new Cargo { Route = new List { new Vector(), new Vector() } };
SaveNode node = Session().ToNode(value, typeof(Cargo));
node.Entries["Route"].Items[1].Entries["X"] = SaveNode.OfText("not a number");
CheckEqual(new[] { "Route: [1]: X" }, IssuePathsOf(node, typeof(Cargo)));
}
public void BadValueInAnObjectInsideAStruct_IsRecordedBeneathBothKeys()
{
var value = new Pairing { Pair = new Duo { Second = new Crate() } };
SaveNode node = Session().ToNode(value, typeof(Pairing));
node.Entries["Pair"].Entries["Second"].Entries["Opened"] = SaveNode.OfText("not a number");
CheckEqual(new[] { "Pair: Second: Opened" }, IssuePathsOf(node, typeof(Pairing)));
}
public void BadValueInASubtypeChain_IsRecordedBeneathTheKeysToIt()
{
var value = new Hound { Prey = new Hound { Prey = new Hound() } };
SaveNode node = Session().ToNode(value, typeof(Hound));
node.Entries["Prey"].Entries["Prey"].Entries["Barks"] = SaveNode.OfText("maybe");
CheckEqual(new[] { "Prey: Prey: Barks" }, IssuePathsOf(node, typeof(Hound)));
}
public void BadValueInALoop_IsRecordedBeneathTheKeysFromWhereTheLoadStarted()
{
var clan = new Clan();
clan.Scouts.Add(new Scout { Clan = clan });
clan.Scouts.Add(new Scout { Clan = clan });
SaveNode node = Session().ToNode(clan, typeof(Clan));
node.Entries["Scouts"].Items[1].Entries["Rank"] = SaveNode.OfText("not a number");
CheckEqual(new[] { "Scouts: [1]: Rank" }, IssuePathsOf(node, typeof(Clan)));
}
public void Struct_RoundTrips()
{
Vector loaded = RoundTrip(new Vector { X = 1.5, Y = -2, Length = 9 });
CheckEqual(1.5, loaded.X);
CheckEqual(-2.0, loaded.Y);
CheckEqual(0.0, loaded.Length);
}
public void ReadonlyFieldsAndGetOnlyAutoProperties_RoundTrip()
{
Fixed loaded = RoundTrip(new Fixed(3, "three"));
CheckEqual(3, loaded.Count);
CheckEqual("three", loaded.Label);
}
public void ClassWithPrivateNestedAndKeywordNamedMembers_RoundTrips()
{
var crate = new Crate { Name = "outer", At = new Vector { X = 2 }, Inside = new Crate(), @event = 7 };
crate.Hide(11);
Crate loaded = RoundTrip(crate);
CheckEqual("outer", loaded.Name);
CheckEqual(2.0, loaded.At.X);
CheckEqual("unnamed", loaded.Inside.Name);
CheckNull(loaded.Inside.Inside);
CheckEqual(7, loaded.@event);
CheckEqual(11, loaded.Secret);
}
public void NestedType_RoundTrips()
{
CheckEqual(4, RoundTrip(new Outer.Inner { Value = 4 }).Value);
CheckEqual(6, RoundTrip(new StaticOuter.Inner { Value = 6 }).Value);
}
public void GenericStruct_RoundTripsWithEachArgumentClosed()
{
Duo loaded = RoundTrip(new Duo { First = 3, Second = "three" });
Duo nested = RoundTrip(
new Duo { First = 1.5, Second = new Vector { X = 2 } });
CheckEqual(3, loaded.First);
CheckEqual("three", loaded.Second);
CheckEqual(1.5, nested.First);
CheckEqual(2.0, nested.Second.X);
}
public void GenericClass_RoundTripsItsCollectionOfTheArgument()
{
var grid = new Grid { Width = 2 };
grid.Cells.Add(new Vector { X = 4 });
grid.Cells.Add(new Vector { Y = 5 });
Grid loaded = RoundTrip(grid);
CheckEqual(2, loaded.Width);
CheckEqual(2, loaded.Cells.Count);
CheckEqual(5.0, loaded.Cells[1].Y);
}
public void GenericTypesHoldingGenericTypes_RoundTripThroughLuaText()
{
var grid = new Grid>();
grid.Cells.Add(new Duo { First = 7, Second = "seven" });
SaveSession session = Session();
SaveNode saved = session.ToNode(grid, typeof(Grid>));
var loaded = (Grid>)session.FromNode(
LuaSaveFormat.Parse(LuaSaveFormat.Format(saved)), typeof(Grid>));
CheckEqual("seven", loaded.Cells[0].Second);
}
public void TypeNestedInAGenericType_RoundTrips()
{
Crate.Label loaded = RoundTrip(new Crate.Label { Content = "fragile", Copies = 3 });
CheckEqual("fragile", loaded.Content);
CheckEqual(3, loaded.Copies);
}
public void GenericTypeNestedInGenericTypes_RoundTripsWithEveryArgument()
{
var slot = new Crate.Shelf.Slot
{
Content = 1,
Shelf = "top",
Held = new Vector { X = 2 },
};
Crate.Shelf.Slot loaded = RoundTrip(slot);
CheckEqual(1, loaded.Content);
CheckEqual("top", loaded.Shelf);
CheckEqual(2.0, loaded.Held.X);
}
public void TypeNestedInAGenericType_IsNamedWhenHeldAsAnObject()
{
SaveSession session = Session();
SaveNode node = session.ToNode(new Crate.Label { Content = "x" }, typeof(object));
var loaded = (Crate.Label)session.FromNode(node, typeof(object));
CheckEqual("Vellum.UnitTests.Crate+Label", node.RequireEntry("__type").Text);
CheckEqual("x", loaded.Content);
}
public void ClassDerivedFromAClosedGenericBase_RoundTripsTheBasesMembers()
{
Barge loaded = RoundTrip(new Barge { Cargo = "grain", Draft = 4 });
CheckEqual("grain", loaded.Cargo);
CheckEqual(4, loaded.Draft);
}
public void GenericClassDerivedFromAGenericBase_RoundTripsTheBasesMembers()
{
Freighter loaded = RoundTrip(new Freighter { Cargo = new List { 3, 4 }, Tonnage = 9 });
CheckEqual(new[] { 3, 4 }, loaded.Cargo.ToArray());
CheckEqual(9, loaded.Tonnage);
}
public void GenericStructImplementingAnInterface_RoundTrips()
{
CheckEqual("gem", RoundTrip(new Tag { Item = "gem" }).Item);
}
public void SubtypesHeldAsGenericBasesAndInterfaces_AreNamedAndLoadedThroughLuaText()
{
var fleet = new Fleet
{
Plain = new Barge { Cargo = "grain", Draft = 4 },
Loaded = new Freighter { Cargo = new List { 3 }, Tonnage = 9 },
Label = new Tag { Item = "gem" },
};
SaveSession session = Session();
SaveNode saved = session.ToNode(fleet, typeof(Fleet));
var loaded = (Fleet)session.FromNode(LuaSaveFormat.Parse(LuaSaveFormat.Format(saved)),
typeof(Fleet));
CheckEqual("Vellum.UnitTests.Barge", saved.RequireEntry("Plain").RequireEntry("__type").Text);
CheckEqual("Vellum.UnitTests.Freighter", saved.RequireEntry("Loaded").RequireEntry("__type").Text);
CheckEqual("Vellum.UnitTests.Tag", saved.RequireEntry("Label").RequireEntry("__type").Text);
CheckEqual("grain", loaded.Plain.Cargo);
CheckEqual(9, ((Freighter)loaded.Loaded).Tonnage);
CheckEqual("gem", ((Tag)loaded.Label).Item);
}
public void MissingValues_KeepWhatTheParameterlessConstructorSets()
{
Crate loaded = LoadedFrom(SaveNode.NewTable());
CheckEqual("unnamed", loaded.Name);
CheckEqual(-1, loaded.Opened);
CheckEqual(3, loaded.Secret);
}
public void WithoutAParameterlessConstructor_MissingValuesKeepTheirInitializers()
{
WithoutParameterlessConstructor loaded = LoadedFrom(
SaveNode.NewTable());
CheckEqual(5, loaded.Value);
CheckEqual(0, loaded.Other);
}
public void ClassWithoutConstructors_KeepsItsParameterlessOne()
{
CheckEqual(5, new WithImplicitConstructor().Value);
CheckEqual(5, LoadedFrom(SaveNode.NewTable()).Value);
}
public void UnknownValue_IsIgnored()
{
SaveNode node = SaveNode.NewTable();
node.Entries["Removed"] = SaveNode.OfText("1");
node.Entries["Value"] = SaveNode.OfText("8");
CheckEqual(8, LoadedFrom(node).Value);
}
public void OwnLoaderChainingToTheGeneratedOne_AdjustsWhatItLoaded()
{
CheckEqual("LOUD", RoundTrip(new Shouting { Label = "loud" }).Label);
}
public void OwnLoaderNotChaining_IsAllThatRuns()
{
CheckEqual(42, RoundTrip(new Forgetful { Count = 7 }).Count);
}
public void NonTable_Throws()
{
CheckThrow(typeof(InvalidDataException));
LoadedFrom(SaveNode.OfText("crate"));
}
public static T ThroughLuaText(T value)
{
SaveSession session = Session();
SaveNode saved = session.ToNode(value, typeof(T));
return (T)session.FromNode(LuaSaveFormat.Parse(LuaSaveFormat.Format(saved)), typeof(T));
}
public void SubclassInABaseField_RoundTripsAsItsOwnTypeWithTheBaseFields()
{
var kennel = new Kennel { Resident = new Puppy { Name = "Rex", Barks = true, Weeks = 3 } };
var loaded = ThroughLuaText(kennel).Resident as Puppy;
CheckNotNull(loaded);
CheckEqual("Rex", loaded.Name);
CheckTrue(loaded.Barks);
CheckEqual(3, loaded.Weeks);
}
public void ObjectHeldByTwoFields_IsOneObjectAfterASaveFile()
{
var hound = new Hound { Name = "Rex" };
var kennel = new Kennel { Resident = hound };
kennel.Pack.Add(hound);
Kennel loaded = ThroughLuaText(kennel);
CheckTrue(ReferenceEquals(loaded.Resident, loaded.Pack[0]));
CheckEqual("Rex", loaded.Resident.Name);
}
public void ParentAndChildrenPointingAtEachOther_LoadAsTheSameCycle()
{
var root = new Folder { Name = "root" };
for (int i = 0; i < 2; ++i)
{
root.Children.Add(new Folder { Name = "child" + i, Parent = root });
}
Folder loaded = ThroughLuaText(root);
CheckEqual("root", loaded.Name);
CheckEqual(2, loaded.Children.Count);
CheckTrue(ReferenceEquals(loaded, loaded.Children[0].Parent));
CheckTrue(ReferenceEquals(loaded, loaded.Children[1].Parent));
CheckEqual("child1", loaded.Children[1].Name);
}
public void ObjectHoldingItself_LoadsHoldingItself()
{
var folder = new Folder { Name = "loop" };
folder.Parent = folder;
folder.Children.Add(folder);
Folder loaded = ThroughLuaText(folder);
CheckTrue(ReferenceEquals(loaded, loaded.Parent));
CheckTrue(ReferenceEquals(loaded, loaded.Children[0]));
}
public void CycleOfSubclassesHeldAsTheirBase_LoadsAsTheSameCycle()
{
var first = new Hound { Name = "first" };
var second = new Hound { Name = "second", Prey = first };
first.Prey = second;
Kennel loaded = ThroughLuaText(new Kennel { Resident = first });
var loadedFirst = (Hound)loaded.Resident;
var loadedSecond = (Hound)loadedFirst.Prey;
CheckEqual("second", loadedSecond.Name);
CheckTrue(ReferenceEquals(loadedFirst, loadedSecond.Prey));
}
public void NodeThatHoldsItself_LoadsAsAnObjectThatHoldsItself()
{
SaveNode forever = SaveNode.NewTable();
forever.Entries["Next"] = forever;
var loaded = (Chained)Session().FromNode(forever, typeof(Chained));
CheckTrue(ReferenceEquals(loaded, loaded.Next));
}
public void StringHeldByTwoObjects_IsOneStringAfterASaveFile()
{
string name = new string('n', 3);
var root = new Folder { Name = name };
root.Children.Add(new Folder { Name = name });
Folder loaded = ThroughLuaText(root);
CheckTrue(ReferenceEquals(loaded.Name, loaded.Children[0].Name));
CheckEqual("nnn", loaded.Name);
}
public void EqualStringsThatAreTwoObjects_AreTwoStringsAfterASaveFile()
{
var root = new Folder { Name = new string('n', 3) };
root.Children.Add(new Folder { Name = new string('n', 3) });
Folder loaded = ThroughLuaText(root);
CheckFalse(ReferenceEquals(loaded.Name, loaded.Children[0].Name));
}
public void SameLiteralInManyObjects_IsWrittenOnceAndReferredToByNumber()
{
var root = new Folder { Name = "unnamed" };
for (int i = 0; i < 3; ++i)
{
root.Children.Add(new Folder { Name = "unnamed" });
}
string text = LuaSaveFormat.Format(Session().ToNode(root, typeof(Folder)));
CheckTrue(text.Contains("shared[1] = leaf(\"unnamed\")"));
CheckEqual(4, text.Split(new[] { "Name = shared[1]," }, StringSplitOptions.None).Length - 1);
}
public void ObjectHeldByTwoFields_IsWrittenOnceInTheSaveText()
{
var hound = new Hound { Name = "Rex" };
var kennel = new Kennel { Resident = hound };
kennel.Pack.Add(hound);
SaveSession session = Session();
string text = LuaSaveFormat.Format(session.ToNode(kennel, typeof(Kennel)));
CheckEqual(1, text.Split(new[] { "Name = \"Rex\"" }, StringSplitOptions.None).Length - 1);
}
public void DistinctObjectsWithEqualFields_StayTwoObjects()
{
var kennel = new Kennel { Resident = new Hound { Name = "Rex" } };
kennel.Pack.Add(new Hound { Name = "Rex" });
Kennel loaded = ThroughLuaText(kennel);
CheckFalse(ReferenceEquals(loaded.Resident, loaded.Pack[0]));
}
public void SubclassesInAListOfTheBase_RoundTripAsTheirOwnTypes()
{
var kennel = new Kennel();
kennel.Pack.Add(new Hound { Name = "Old" });
kennel.Pack.Add(new Kitten { Name = "Tom", Lives = 3 });
kennel.Pack.Add(new Puppy { Name = "Rex" });
List loaded = ThroughLuaText(kennel).Pack;
CheckEqual(new[] { typeof(Hound), typeof(Kitten), typeof(Puppy) },
loaded.Select(beast => beast.GetType()));
CheckEqual(3, ((Kitten)loaded[1]).Lives);
CheckEqual("Tom", loaded[1].Name);
}
public void ClassThatInheritsTheMark_RoundTrips()
{
var kennel = new Kennel { Resident = new Wolfhound { Name = "Fenrir", Barks = true, Height = 90 } };
var loaded = (Wolfhound)ThroughLuaText(kennel).Resident;
CheckEqual("Fenrir", loaded.Name);
CheckTrue(loaded.Barks);
CheckEqual(90, loaded.Height);
}
public void SubclassHeldByItsOwnBaseOfAnotherLevel_RoundTrips()
{
var hound = new Hound { Name = "Old", Prey = new Kitten { Name = "Tom" } };
Hound loaded = ThroughLuaText(hound);
CheckEqual(typeof(Kitten), loaded.Prey.GetType());
CheckEqual("Tom", loaded.Prey.Name);
}
public void ConcreteBaseHeldAsItself_IsNotNamed()
{
SaveNode node = Session().ToNode(new Hound { Name = "Old" }, typeof(Hound));
CheckFalse(node.Entries.ContainsKey("__type"));
CheckEqualCommutative(new[] { "Name", "Barks", "Prey" }, node.Entries.Keys);
}
public void SubclassHeldAsItsBase_IsNamedAndSavesEveryLevelsFields()
{
SaveNode node = Session().ToNode(new Puppy(), typeof(Beast));
CheckEqual("Vellum.UnitTests.Puppy", node.RequireEntry("__type").Text);
CheckEqualCommutative(new[] { "__type", "Name", "Barks", "Prey", "Weeks" }, node.Entries.Keys);
}
public void StructInAnInterfaceField_RoundTripsAsItsOwnType()
{
var menagerie = new Menagerie { Star = new Sprite { Glow = 7 } };
ICreature loaded = ThroughLuaText(menagerie).Star;
CheckEqual(typeof(Sprite), loaded.GetType());
CheckEqual(7, ((Sprite)loaded).Glow);
}
public void ImplementersInAListOfTheInterface_RoundTripAsTheirOwnTypes()
{
var menagerie = new Menagerie();
menagerie.Cages.Add(new Dragon { Name = "Smaug", Wingspan = 30 });
menagerie.Cages.Add(new Sprite { Glow = 2 });
List loaded = ThroughLuaText(menagerie).Cages;
CheckEqual(new[] { typeof(Dragon), typeof(Sprite) }, loaded.Select(creature => creature.GetType()));
CheckEqual("Smaug", ((Dragon)loaded[0]).Name);
CheckEqual(30, ((Dragon)loaded[0]).Wingspan);
}
public void ClassImplementingAnInterfaceThatExtendsTheMarkedOne_IsNamedByItsOwnType()
{
SaveNode node = Session().ToNode(new Menagerie { Star = new Dragon() }, typeof(Menagerie));
CheckEqual("Vellum.UnitTests.Dragon", node.RequireEntry("Star").RequireEntry("__type").Text);
}
public void NullInterfaceField_StaysNull()
{
Menagerie loaded = ThroughLuaText(new Menagerie { Star = new Sprite() });
CheckNull(loaded.Empty);
}
public void SubclassLoadsThroughItsBasesOwnLoader()
{
Grumpy loaded = RoundTrip(new Grumpy { Mood = 1, Grumbles = 5 });
CheckEqual(101, loaded.Mood);
CheckEqual(5, loaded.Grumbles);
}
public void NullBaseField_StaysNull()
{
CheckNull(ThroughLuaText(new Kennel()).Resident);
}
public void SubclassNamedByAnUnregisteredType_Throws()
{
SaveNode node = SaveNode.NewTable();
node.Entries["__type"] = SaveNode.OfText("Vellum.UnitTests.Beast");
CheckThrow(typeof(InvalidDataException));
LoadedFrom(node);
}
public void CollectionMembers_RoundTripThroughLuaText()
{
var cargo = new Cargo();
cargo.Route.Add(new Vector { X = 1.5, Y = -2 });
cargo.Route.Add(new Vector { X = 0, Y = 4 });
cargo.Counts = new[] { 3, 1, 2 };
cargo.Tags["fragile"] = new HashSet(StringComparer.OrdinalIgnoreCase) { "glass", "eggs" };
cargo.Names[new Vector { X = 1.5, Y = -2 }] = "home";
cargo.Line.Enqueue(new Vector { X = 7 });
cargo.Line.Enqueue(new Vector { X = 8 });
cargo.Pile.Push("bottom");
cargo.Pile.Push("top");
cargo.Grid = new[,] { { 1, 2 }, { 3, 4 }, { 5, 6 } };
cargo.Chain.AddLast("first");
cargo.Chain.AddLast("second");
cargo.Tally["b"] = 2;
cargo.Tally["a"] = 1;
cargo.Marks.Add(9);
cargo.Marks.Add(4);
cargo.Cube = new double[1, 2, 1, 2];
cargo.Cube[0, 1, 0, 1] = 2.5;
SaveSession session = Session();
SaveNode saved = session.ToNode(cargo, typeof(Cargo));
var loaded = (Cargo)session.FromNode(LuaSaveFormat.Parse(LuaSaveFormat.Format(saved)),
typeof(Cargo));
CheckEqual(2, loaded.Route.Count);
CheckEqual(-2.0, loaded.Route[0].Y);
CheckEqual(new[] { 3, 1, 2 }, loaded.Counts);
CheckTrue(loaded.Tags["fragile"].Contains("GLASS"));
CheckEqual("home", loaded.Names[new Vector { X = 1.5, Y = -2 }]);
CheckEqual(7.0, loaded.Line.Dequeue().X);
CheckEqual("top", loaded.Pile.Pop());
CheckEqual(6, loaded.Grid[2, 1]);
CheckEqual("second", loaded.Chain.Last.Value);
CheckEqual(new[] { "a", "b" }, loaded.Tally.Keys.ToArray());
CheckEqual(new[] { 4, 9 }, loaded.Marks.ToArray());
CheckEqual(2.5, loaded.Cube[0, 1, 0, 1]);
}
}
public class FillTests : TestFixture
{
public static SaveNode LedgerNode()
{
var ledger = new Ledger("Q3", 7, "memo") { Plain = 9 };
ledger.Entries.Add(4);
return Session().ToNode(ledger, typeof(Ledger));
}
public void ReadonlyFieldsAndGetOnlyProperties_AreWrittenBeforeAnyConstructorRuns()
{
var converter = new Ledger.__Converter();
SaveNode node = LedgerNode();
Ledger blank = converter.Allocate(node, Session());
converter.FillBlank(blank, node, Session());
CheckEqual("Q3", blank.Title);
CheckEqual(7, blank.Count);
CheckEqual("memo", blank.Note);
CheckEqual(9, blank.Plain);
CheckEqual(new[] { 4 }, blank.Entries.ToArray());
CheckNull(blank.Cache);
}
public void AfterComplete_TheConstructorHasRunAndTheDataIsStillThere()
{
var converter = new Ledger.__Converter();
SaveNode node = LedgerNode();
Ledger blank = converter.Allocate(node, Session());
converter.FillBlank(blank, node, Session());
converter.CompleteBlank(blank, node, Session());
CheckEqual("built", blank.Cache);
CheckEqual("Q3", blank.Title);
CheckEqual(7, blank.Count);
CheckEqual("memo", blank.Note);
CheckEqual(new[] { 4 }, blank.Entries.ToArray());
}
public void KeyMissingFromTheSave_LeavesTheMemberAlone()
{
var node = SaveNode.NewTable();
node.Entries["Plain"] = SaveNode.OfText("5");
var converter = new Ledger.__Converter();
Ledger blank = converter.Allocate(node, Session());
converter.FillBlank(blank, node, Session());
CheckEqual(5, blank.Plain);
CheckNull(blank.Title);
CheckNull(blank.Entries);
}
public void NodeThatIsNotATable_Throws()
{
var converter = new Ledger.__Converter();
Ledger blank = converter.Allocate(SaveNode.NewTable(), Session());
CheckThrow(typeof(InvalidDataException));
converter.FillBlank(blank, SaveNode.OfText("3"), Session());
}
public void ClassInAHierarchy_WritesTheMembersOfEveryClassAbove()
{
var truck = new Truck { Weight = 12, Plate = "ABC", Spare = new Crate { Name = "spare" } };
truck.Crates.Add(new Crate { Name = "one" });
SaveNode node = Session().ToNode(truck, typeof(Truck));
var converter = new Truck.__Converter();
Truck blank = converter.Allocate(node, Session());
converter.FillBlank(blank, node, Session());
CheckEqual(12, blank.Weight);
CheckEqual("ABC", blank.Plate);
CheckEqual("spare", blank.Spare.Name);
CheckEqual("one", blank.Crates[0].Name);
}
public void ClassInAHierarchy_ReadsLikeItAlwaysDid()
{
var truck = new Truck { Weight = 12, Plate = "ABC" };
var loaded = RoundTrip(truck);
CheckEqual(12, loaded.Weight);
CheckEqual("ABC", loaded.Plate);
}
public void GenericClass_WritesItsMembers()
{
var freighter = new Freighter { Tonnage = 30, Cargo = new List { new Crate() } };
SaveNode node = Session().ToNode(freighter, typeof(Freighter));
var converter = new Freighter.__Converter();
Freighter blank = converter.Allocate(node, Session());
converter.FillBlank(blank, node, Session());
CheckEqual(30, blank.Tonnage);
CheckEqual(1, blank.Cargo.Count);
}
}
}
}