using System; using System.Collections.Generic; using System.IO; using UnitTestSharp; namespace Vellum.UnitTests { public class LoadPlannerTests : TestFixture { public static LoadPlanner NewPlanner() { return new LoadPlanner(SaveConvertersTests.TestConverters()); } public static SaveNode NodeOf(T value) { return SaveConvertersTests.Session().ToNode(value); } public class CountTests : TestFixture { public void NewPlanner_HoldsNothing() { CheckEqual(0, NewPlanner().Count); } } public class IndexerTests : TestFixture { public void OutOfRange_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); LoadStep step = NewPlanner()[0]; } } public class PlanTests : TestFixture { public void NullRoot_PlansNothing() { LoadPlanner planner = NewPlanner(); planner.Plan(SaveNode.Null, typeof(Bead)); CheckEqual(0, planner.Count); } public void TextOfABuiltInType_PlansNothing() { LoadPlanner planner = NewPlanner(); planner.Plan(SaveNode.OfText("5"), typeof(int)); CheckEqual(0, planner.Count); } public void ChainOfBeads_ListsEachBeadDeepestFirst() { SaveNode root = NodeOf(SaveSessionTests.FromNodeTests.BeadChain(3)); LoadPlanner planner = NewPlanner(); planner.Plan(root, typeof(Bead)); CheckEqual(3, planner.Count); CheckEqual(SaveNode.Kinds.Null, planner[0].Node.RequireEntry("next").Kind); CheckTrue(ReferenceEquals(planner[0].Node, planner[1].Node.RequireEntry("next").Resolved)); CheckTrue(ReferenceEquals(planner[1].Node, planner[2].Node.RequireEntry("next").Resolved)); CheckTrue(ReferenceEquals(root.Resolved, planner[2].Node)); CheckEqual(typeof(Bead), planner[0].Type); } public void BeadsAlreadyLoaded_AreLeftOut() { SaveNode root = NodeOf(SaveSessionTests.FromNodeTests.BeadChain(3)); var loaded = new Dictionary(); var planner = new LoadPlanner(SaveConvertersTests.TestConverters(), loaded); planner.Plan(root, typeof(Bead)); SaveNode secondBead = planner[1].Node; loaded[secondBead] = new Bead(); planner.Plan(root, typeof(Bead)); CheckEqual(1, planner.Count); CheckTrue(ReferenceEquals(root.Resolved, planner[0].Node)); } public void RootAlreadyLoaded_PlansNothing() { SaveNode root = NodeOf(SaveSessionTests.FromNodeTests.BeadChain(3)); var loaded = new Dictionary { { root.Resolved, new Bead() } }; var planner = new LoadPlanner(SaveConvertersTests.TestConverters(), loaded); planner.Plan(root, typeof(Bead)); CheckEqual(0, planner.Count); } public void ChainFarDeeperThanTheStackAllows_ListsEachBead() { LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(SaveSessionTests.FromNodeTests.BeadChain(10000)), typeof(Bead)); CheckEqual(10000, planner.Count); } public void BeadHeldTwice_IsListedOnceBeforeBothHolders() { Bead shared = SaveSessionTests.FromNodeTests.BeadChain(1); var beads = new List { new Bead { Next = shared }, new Bead { Next = shared } }; LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(beads), typeof(List)); CheckEqual(4, planner.Count); CheckEqual(typeof(List), planner[3].Type); CheckTrue(ReferenceEquals(planner[0].Node, planner[1].Node.RequireEntry("next").Resolved)); CheckTrue(ReferenceEquals(planner[0].Node, planner[2].Node.RequireEntry("next").Resolved)); } public void StructThatHoldsABead_IsLeftOutAndTheChainItHoldsIsListed() { LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(new BeadHolder { Head = SaveSessionTests.FromNodeTests.BeadChain(3) }), typeof(BeadHolder)); CheckEqual(3, planner.Count); CheckEqual(typeof(Bead), planner[2].Type); } public void ListOfStructsThatHoldABead_ListsTheListAndTheBeads() { var holders = new List { new BeadHolder { Head = SaveSessionTests.FromNodeTests.BeadChain(2) }, new BeadHolder(), }; LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(holders), typeof(List)); CheckEqual(3, planner.Count); CheckEqual(typeof(List), planner[2].Type); } public void StructsThatDontSayWhatTheyHold_AreNotListedAndNothingInsideThemIs() { var points = new List(); for (int i = 0; i < 100; ++i) { points.Add(new Point(i, 0)); } LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(points), typeof(List)); CheckEqual(1, planner.Count); CheckEqual(typeof(List), planner[0].Type); } public void ConverterThatDoesntSay_LeavesOutItsValue() { LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(SaveConvertersTests.Chain(3)), typeof(Link)); CheckEqual(0, planner.Count); } public void ConverterThatDoesntSayInsideAList_LeavesOutOnlyThatValue() { var links = new List { new Link(), new Link() }; LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(links), typeof(List)); CheckEqual(1, planner.Count); } public void ValueHeldBySubtypeName_IsListedAsTheTypeItIsLoadedAs() { SaveNode root = SaveConvertersTests.Session().ToNode(SaveSessionTests.FromNodeTests.SatchelChain(3), typeof(IPouch)); LoadPlanner planner = NewPlanner(); planner.Plan(root, typeof(IPouch)); CheckEqual(3, planner.Count); CheckEqual(typeof(IPouch), planner[0].Type); } public void SubtypeNameThatIsntAssignable_PlansNothing() { SaveNode node = SaveNode.NewTable(); node.Entries[SaveConverters.TypeKey] = SaveNode.OfText("Vellum.UnitTests.Dog"); LoadPlanner planner = NewPlanner(); planner.Plan(node, typeof(Bead)); CheckEqual(0, planner.Count); } public void SubtypeNameOfABuiltInType_PlansNothing() { SaveNode node = SaveNode.NewTable(); node.Entries[SaveConverters.TypeKey] = SaveNode.OfText("int"); node.Entries[SaveConverters.ValueKey] = SaveNode.OfText("5"); LoadPlanner planner = NewPlanner(); planner.Plan(node, typeof(object)); CheckEqual(0, planner.Count); } public void SubtypeNameThatDoesntResolve_Throws() { SaveNode node = SaveNode.NewTable(); node.Entries[SaveConverters.TypeKey] = SaveNode.OfText("Nowhere.Nothing"); CheckThrow(typeof(InvalidDataException)); NewPlanner().Plan(node, typeof(Bead)); } public void SubtypeNameThatIsntText_Throws() { SaveNode node = SaveNode.NewTable(); node.Entries[SaveConverters.TypeKey] = SaveNode.NewList(); CheckThrow(typeof(InvalidDataException)); NewPlanner().Plan(node, typeof(Bead)); } public void BeadThatHoldsItself_IsListedOnce() { SaveNode bead = SaveNode.NewTable(); bead.Entries["next"] = bead; LoadPlanner planner = NewPlanner(); planner.Plan(bead, typeof(Bead)); CheckEqual(1, planner.Count); } public void StructThatHoldsItself_StopsWhereItsNestingGetsTooDeep() { SaveNode coil = SaveNode.NewTable(); coil.Entries["inner"] = coil; LoadPlanner planner = NewPlanner(); planner.Plan(coil, typeof(Coil)); CheckEqual(0, planner.Count); } public void StructHoldingABeadThatHoldsItself_ListsTheBeadOnce() { SaveNode holder = SaveNode.NewTable(); SaveNode bead = SaveNode.NewTable(); holder.Entries["head"] = bead; bead.Entries["next"] = bead; LoadPlanner planner = NewPlanner(); planner.Plan(holder, typeof(BeadHolder)); CheckEqual(1, planner.Count); } public void SecondPlan_ReplacesTheFirst() { LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(SaveSessionTests.FromNodeTests.BeadChain(5)), typeof(Bead)); planner.Plan(NodeOf(SaveSessionTests.FromNodeTests.BeadChain(2)), typeof(Bead)); CheckEqual(2, planner.Count); } public void NodeHeldThroughAReference_IsListedAsTheNodeItStandsFor() { SaveNode node = NodeOf(SaveSessionTests.FromNodeTests.BeadChain(2)); SaveNode reference = SaveNode.NewRef(); reference.Resolve(node); LoadPlanner planner = NewPlanner(); planner.Plan(reference, typeof(Bead)); CheckEqual(2, planner.Count); CheckTrue(ReferenceEquals(node.Resolved, planner[1].Node)); } } public class PlanLoopTests : TestFixture { public static string ActionsOf(LoadPlanner planner) { var names = new List(); for (int i = 0; i < planner.Count; ++i) { names.Add(planner[i].Action.ToString()); } return string.Join(",", names); } public static int IndexOf(LoadPlanner planner, LoadAction action, Type type) { for (int i = 0; i < planner.Count; ++i) { if (planner[i].Action == action && planner[i].Type == type) { return i; } } return -1; } public void RingOfTwo_IsAllocatedFilledThenCompleted() { LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(SaveSessionTests.LoadLoopTests.RingOf(2, closed: true)), typeof(Ring)); CheckEqual("Allocate,Allocate,Fill,Fill,Complete,Complete", ActionsOf(planner)); } public void RingOfOne_IsAllocatedFilledThenCompleted() { LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(SaveSessionTests.LoadLoopTests.RingOf(1, closed: true)), typeof(Ring)); CheckEqual("Allocate,Fill,Complete", ActionsOf(planner)); } public void DoublyLinkedList_IsOneGroup() { LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(SaveSessionTests.LoadLoopTests.RingOf(1000, closed: false)), typeof(Ring)); CheckEqual(3000, planner.Count); CheckEqual(LoadAction.Allocate, planner[0].Action); CheckEqual(LoadAction.Allocate, planner[999].Action); CheckEqual(LoadAction.Fill, planner[1000].Action); CheckEqual(LoadAction.Complete, planner[2000].Action); } public void StepsOfAGroup_CarryTheConverterAndWhatItIsHanded() { LoadPlanner planner = NewPlanner(); SaveNode root = NodeOf(SaveSessionTests.LoadLoopTests.RingOf(2, closed: true)); planner.Plan(root, typeof(Ring)); CheckEqual(typeof(Ring), planner[0].Converter.ValueType); CheckTrue(ReferenceEquals(planner[0].Contents, planner[0].Node)); CheckTrue(ReferenceEquals(planner[0].Contents, planner[2].Contents)); CheckTrue(ReferenceEquals(planner[0].Contents, planner[4].Contents)); CheckTrue(ReferenceEquals(planner[0].Converter, planner[4].Converter)); } public void PairThatFillsBeforeCompleting_IsAllocatedFilledThenCompleted() { var ann = new Spouse("ann", null); var bob = new Spouse("bob", ann); SpouseConverter.PartnerField.Set(ann, bob); LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(ann), typeof(Spouse)); CheckEqual("Allocate,Allocate,Fill,Fill,Complete,Complete", ActionsOf(planner)); } public void ClassBuiltInOneGo_IsBuiltBetweenTheAllocationAndTheFillOfTheOther() { var plate = new Plate(null, "brass"); var knob = new Knob(plate); PlateConverter.KnobField.Set(plate, knob); LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(knob), typeof(Knob)); CheckEqual("Allocate,Build,Fill,Complete", ActionsOf(planner)); CheckEqual(typeof(Plate), planner[0].Type); CheckEqual(typeof(Knob), planner[1].Type); CheckEqual(typeof(Plate), planner[2].Type); } public void ClassesNeitherOfWhichCanBeMadeEarly_AreBuiltInTheOrderTheWalkFinished() { var gate = new Gate { Name = "g" }; gate.Latch = new Latch { Name = "l", Gate = gate }; LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(gate), typeof(Gate)); CheckEqual("Build,Build", ActionsOf(planner)); CheckEqual(typeof(Latch), planner[0].Type); CheckEqual(typeof(Gate), planner[1].Type); } public void HashSetOfItemsWithFilledData_IsFilledAfterTheItems() { var hub = new Hub(new HashSet()); hub.Badges.Add(new Badge("x", hub)); hub.Badges.Add(new Badge("y", hub)); LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(hub), typeof(Hub)); int setFill = IndexOf(planner, LoadAction.Fill, typeof(HashSet)); CheckEqual(12, planner.Count); CheckTrue(setFill > IndexOf(planner, LoadAction.Fill, typeof(Badge))); CheckTrue(setFill < IndexOf(planner, LoadAction.Complete, typeof(Hub))); } public void HashSetOfItemsThatHaveNoFillStep_IsBuiltAsBeforeGroups() { var first = new Mote("a", new HashSet()); var second = new Mote("b", new HashSet { first }); first.Near.Add(second); LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(first), typeof(Mote)); CheckEqual("Build,Build,Build,Build", ActionsOf(planner)); } public void HashSetOfGeneratedItems_IsFilledAfterTheItems() { var first = new Vertex { Name = "v1" }; var second = new Vertex { Name = "v2" }; first.Neighbors.Add(second); second.Neighbors.Add(first); LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(first), typeof(Vertex)); CheckEqual(12, planner.Count); CheckTrue(IndexOf(planner, LoadAction.Fill, typeof(HashSet)) > IndexOf(planner, LoadAction.Fill, typeof(Vertex))); } public void GroupHeldByAList_IsPlannedBeforeTheList() { SaveNode list = NodeOf(new List { SaveSessionTests.LoadLoopTests.RingOf(2, closed: true) }); LoadPlanner planner = NewPlanner(); planner.Plan(list, typeof(List)); CheckEqual("Allocate,Allocate,Fill,Fill,Complete,Complete,Build", ActionsOf(planner)); CheckEqual(typeof(List), planner[6].Type); } public void WishesThatCantAllBeMet_AreDropped() { var plate = new Plate(null, "brass"); var knob = new Knob(plate); PlateConverter.KnobField.Set(plate, knob); LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(plate), typeof(Plate)); CheckEqual("Allocate,Build,Fill,Complete", ActionsOf(planner)); } public void TwoSeparateLoops_AreEachAGroup() { var lists = new List { SaveSessionTests.LoadLoopTests.RingOf(2, closed: true), SaveSessionTests.LoadLoopTests.RingOf(2, closed: true), }; LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(lists), typeof(List)); CheckEqual("Allocate,Allocate,Fill,Fill,Complete,Complete,Allocate,Allocate,Fill,Fill,Complete," + "Complete,Build", ActionsOf(planner)); } } public class ClearTests : TestFixture { public void EmptiesThePlanAndForgetsWhatItLearned() { LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(SaveConvertersTests.Chain(2)), typeof(Link)); planner.Plan(NodeOf(SaveSessionTests.FromNodeTests.BeadChain(2)), typeof(Bead)); planner.Clear(); CheckEqual(0, planner.Count); CheckEqual("", planner.DescribeUnlisted()); } public void PlannerCanBeUsedAgain() { LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(SaveSessionTests.FromNodeTests.BeadChain(2)), typeof(Bead)); planner.Clear(); planner.Plan(NodeOf(SaveSessionTests.FromNodeTests.BeadChain(3)), typeof(Bead)); CheckEqual(3, planner.Count); } } public class DescribeUnlistedTests : TestFixture { public void EverythingListed_IsEmpty() { LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(SaveSessionTests.FromNodeTests.BeadChain(2)), typeof(Bead)); CheckEqual("", planner.DescribeUnlisted()); } public void ConverterThatDoesntSay_IsNamed() { LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(SaveConvertersTests.Chain(2)), typeof(Link)); CheckTrue(planner.DescribeUnlisted().Contains(typeof(LinkConverter).FullName)); } public void SeveralConverters_AreNamedInOrder() { var pairs = new Dictionary { { new Link(), new Point(1, 2) } }; LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(pairs), typeof(Dictionary)); string text = planner.DescribeUnlisted(); int link = text.IndexOf(typeof(LinkConverter).FullName); int point = text.IndexOf(typeof(PointConverter).FullName); CheckTrue(link >= 0 && link < point); } public void ConverterThatDoesntSayTwice_IsNamedOnce() { var links = new List { new Link(), new Link() }; LoadPlanner planner = NewPlanner(); planner.Plan(NodeOf(links), typeof(List)); string text = planner.DescribeUnlisted(); string name = typeof(LinkConverter).FullName; CheckEqual(text.IndexOf(name), text.LastIndexOf(name)); } } } }