using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnitTestSharp; namespace Vellum.UnitTests { public class LoadSandboxTests : TestFixture { public class Glitch { } public class GlitchConverter : SaveConverter { public override SaveNode Write(Glitch value, SaveSession session) { return SaveNode.OfText("glitch"); } public override Glitch Read(SaveNode node, SaveSession session) { throw new InvalidOperationException("the converter broke"); } } public class Plain { } public static readonly Type PlainList = typeof(List); public class Nest { } public class NestConverter : SaveConverter { public override SaveNode Write(Nest value, SaveSession session) { return SaveNode.NewTable(); } public override Nest Read(SaveNode node, SaveSession session) { SaveNode inner; if (node.TryGetEntry("inner", out inner)) { session.FromNode(inner); } return new Nest(); } } public class Knot { public bool Completed; } public class KnotConverter : SaveConverter { public override SaveNode Write(Knot value, SaveSession session) { return SaveNode.NewTable(); } public override Knot Read(SaveNode node, SaveSession session) { return new Knot(); } public override void Fill(Knot blank, SaveNode node, SaveSession session) { throw new InvalidOperationException("the fill broke"); } public override void Complete(Knot blank, SaveNode node, SaveSession session) { blank.Completed = true; } public override bool TryListChildren(SaveNode node, ChildList children) { SaveNode self; if (node.TryGetEntry("self", out self)) { children.Add(self, typeof(Knot)); } return true; } } public class Snag { public bool Completed; } public class SnagConverter : SaveConverter { public override SaveNode Write(Snag value, SaveSession session) { return SaveNode.NewTable(); } public override Snag Read(SaveNode node, SaveSession session) { return new Snag(); } public override Snag Allocate(SaveNode node, SaveSession session) { throw new InvalidOperationException("the allocation broke"); } public override void Complete(Snag blank, SaveNode node, SaveSession session) { blank.Completed = true; } public override bool TryListChildren(SaveNode node, ChildList children) { SaveNode self; if (node.TryGetEntry("self", out self)) { children.Add(self, typeof(Snag)); } return true; } } public static SaveSession GlitchSession() { return new SaveConverters(new SaveConverter[] { new GlitchConverter(), new NestConverter(), new KnotConverter(), new SnagConverter() }).CreateSession(); } public static LoadIssue Issue(string path, string message = "bad") { return new LoadIssue(path, LoadIssueKind.BadData, new InvalidDataException(message)); } public class RecordTests : TestFixture { public void Issue_IsKept() { var sandbox = new LoadSandbox(); LoadIssue issue = Issue("a"); sandbox.Record(issue); CheckTrue(ReferenceEquals(issue, sandbox.Issues.Single())); } public void SamePathAndMessage_IsKeptOnce() { var sandbox = new LoadSandbox(); sandbox.Record(Issue("a")); sandbox.Record(Issue("a")); CheckEqual(1, sandbox.Issues.Count); CheckEqual(0, sandbox.Omitted); } public void SamePathWithAnotherMessage_IsKept() { var sandbox = new LoadSandbox(); sandbox.Record(Issue("a", "one")); sandbox.Record(Issue("a", "two")); CheckEqual(2, sandbox.Issues.Count); } public void PastTheLimit_IsCountedNotKept() { var sandbox = new LoadSandbox(); for (int i = 0; i < LoadSandbox.MaxIssues + 3; ++i) { sandbox.Record(Issue("path" + i)); } CheckEqual(LoadSandbox.MaxIssues, sandbox.Issues.Count); CheckEqual(3, sandbox.Omitted); } public void Null_Throws() { CheckThrow(typeof(ArgumentNullException)); new LoadSandbox().Record(null); } } public class CanRecoverTests : TestFixture { public void OrdinaryException_IsTrue() { CheckTrue(LoadSandbox.CanRecover(new InvalidDataException())); } public void OutOfMemory_IsFalse() { CheckFalse(LoadSandbox.CanRecover(new OutOfMemoryException())); } } public class CtorTests : TestFixture { public void LoadIssueWithNullPath_Throws() { CheckThrow(typeof(ArgumentNullException)); new LoadIssue(null, LoadIssueKind.BadData, new InvalidDataException()); } public void LoadIssueWithNullException_Throws() { CheckThrow(typeof(ArgumentNullException)); new LoadIssue("a", LoadIssueKind.BadData, (Exception)null); } public void LoadIssueWithNullMessage_Throws() { CheckThrow(typeof(ArgumentNullException)); new LoadIssue("a", LoadIssueKind.Clamped, (string)null); } public void LoadIssueFromAMessage_HasNoException() { var issue = new LoadIssue("a", LoadIssueKind.Clamped, "adjusted"); CheckNull(issue.Exception); CheckEqual("a: adjusted", issue.ToString()); } public void LoadIssue_ToStringNamesPathAndMessage() { CheckEqual("a: bad", Issue("a").ToString()); } public void AlreadyFailedException_KeepsTheOriginal() { var original = new InvalidDataException("bad"); var exception = new AlreadyFailedException(original); CheckTrue(ReferenceEquals(original, exception.InnerException)); } } public class ConverterBugTests : TestFixture { public void ConverterThatThrowsAnythingButBadData_IsRecordedAsAConverterBug() { SaveSession session = GlitchSession(); session.FromNode>(session.ToNode(new List { new Glitch() })); CheckEqual(LoadIssueKind.ConverterBug, session.Sandbox.Issues.Single().Kind); } public void ItemWhoseConverterBroke_IsLeftOutAndTheOthersLoad() { SaveSession session = GlitchSession(); SaveNode node = SaveNode.NewList(); node.Items.Add(SaveNode.OfText("glitch")); var loaded = session.FromNode>(node); CheckEqual(0, loaded.Count); CheckEqual(new[] { "[0]" }, SaveConvertersTests.IssuePaths(session)); } public void BadData_IsRecordedAsBadData() { SaveNode node = SaveNode.NewList(); node.Items.Add(SaveNode.OfText("x")); SaveSession session = SaveConvertersTests.Session(); session.FromNode(node); CheckEqual(LoadIssueKind.BadData, session.Sandbox.Issues.Single().Kind); } public void RootWhoseConverterBroke_Throws() { CheckThrow(typeof(InvalidOperationException)); GlitchSession().FromNode(SaveNode.OfText("glitch")); } } public class KindTests : TestFixture { public void TypeNothingConverts_IsRecordedAsNoConverter() { SaveNode node = SaveNode.NewList(); node.Items.Add(SaveNode.NewTable()); SaveSession session = GlitchSession(); session.FromNode(node, PlainList); CheckEqual(LoadIssueKind.NoConverter, session.Sandbox.Issues.Single().Kind); } public void ConvertersNestedTooDeep_AreRecordedAsTooDeep() { SaveNode nest = SaveNode.NewTable(); for (int i = 0; i < SaveNode.MaxDepth + 8; ++i) { SaveNode outer = SaveNode.NewTable(); outer.Entries["inner"] = nest; nest = outer; } SaveNode node = SaveNode.NewList(); node.Items.Add(nest); SaveSession session = GlitchSession(); session.FromNode>(node); CheckEqual(LoadIssueKind.TooDeep, session.Sandbox.Issues.Single().Kind); } } public class LoopTests : TestFixture { public static SaveNode ListOfOneKnotHoldingItself() { SaveNode knot = SaveNode.NewTable(); knot.Entries["self"] = knot; SaveNode list = SaveNode.NewList(); list.Items.Add(knot); return list; } public void AllocateThatFails_LeavesTheObjectOutAndSkipsItsOtherSteps() { SaveNode snag = SaveNode.NewTable(); snag.Entries["self"] = snag; SaveNode list = SaveNode.NewList(); list.Items.Add(snag); SaveSession session = GlitchSession(); var loaded = session.FromNode>(list); CheckEqual(0, loaded.Count); CheckEqual(new[] { "[0]" }, SaveConvertersTests.IssuePaths(session)); CheckEqual(LoadIssueKind.ConverterBug, session.Sandbox.Issues.Single().Kind); } public void FillThatFails_IsRecordedAndTheObjectIsStillCompleted() { SaveSession session = GlitchSession(); var loaded = session.FromNode>(ListOfOneKnotHoldingItself()); CheckEqual(LoadIssueKind.ConverterBug, session.Sandbox.Issues.Single().Kind); CheckEqual(new[] { "[0]" }, SaveConvertersTests.IssuePaths(session)); CheckEqual(1, loaded.Count); CheckTrue(loaded[0].Completed); } } public class SubtypePathTests : TestFixture { public void BadValueInASubtypeInAList_IsRecordedBeneathTheListAndItemKeys() { var kennel = new Kennel { Pack = new List { new Hound() } }; SaveSession session = SaveConvertersTests.Session(); SaveNode node = session.ToNode(kennel, typeof(Kennel)); node.Entries["Pack"].Items[0].Entries["Barks"] = SaveNode.OfText("maybe"); session.FromNode(node, typeof(Kennel)); CheckEqual(new[] { "Pack: [0]: Barks" }, SaveConvertersTests.IssuePaths(session)); } public void BadValueInASubtypeHeldByAField_IsRecordedBeneathTheFieldsKey() { var kennel = new Kennel { Resident = new Hound() }; SaveSession session = SaveConvertersTests.Session(); SaveNode node = session.ToNode(kennel, typeof(Kennel)); node.Entries["Resident"].Entries["Barks"] = SaveNode.OfText("maybe"); session.FromNode(node, typeof(Kennel)); CheckEqual(new[] { "Resident: Barks" }, SaveConvertersTests.IssuePaths(session)); } } public class SessionTests : TestFixture { public void RootThatFails_StillThrows() { CheckThrow(typeof(InvalidDataException)); SaveConvertersTests.Session().FromNode(SaveNode.OfText("x"), typeof(int)); } } } }