using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnitTestSharp;
namespace Vellum.UnitTests
{
public class SaveSessionTests : TestFixture
{
///
/// Reads each primitive back through into an array that already exists,
/// so that only what reading allocates is counted. A value is read from the node made for it and from the
/// node parsed from the text of a save file.
///
public sealed class RepeatedReads
{
public const int Count = 100;
public readonly T[] Into = new T[Count];
private readonly SaveNode[] made = new SaveNode[Count];
private readonly SaveNode[] parsed = new SaveNode[Count];
public RepeatedReads(T sample)
: this(NodeOf(sample))
{
}
private static SaveNode NodeOf(T sample)
{
SaveNode returnMe;
SavedValue.TryToNode(sample, out returnMe);
return returnMe;
}
public RepeatedReads(SaveNode node)
{
SaveNode save = SaveNode.NewTable();
save.Entries["value"] = node;
SaveNode fromText = LuaSaveFormat.Parse(LuaSaveFormat.Format(save)).RequireEntry("value");
for (int i = 0; i < Count; ++i)
{
made[i] = node;
parsed[i] = fromText;
}
}
public void Read(SaveSession session)
{
for (int i = 0; i < Count; ++i)
{
Into[i] = session.FromNode(made[i]);
}
for (int i = 0; i < Count; ++i)
{
Into[i] = session.FromNode(parsed[i]);
}
}
}
public class TypeNameTests : TestFixture
{
public static readonly SaveConverters SharedConverters = SaveConvertersTests.TestConverters();
public void TryNameOf_NamesAKeywordType()
{
string name;
CheckTrue(SharedConverters.CreateSession().TryNameOf(typeof(int), out name));
CheckEqual("int", name);
}
public void TryNameOf_RefusesATypeNothingRegistered()
{
string name;
CheckFalse(SharedConverters.CreateSession().TryNameOf(typeof(System.Diagnostics.Process), out name));
CheckNull(name);
}
public void ResolveName_FindsTheTypeANameSpells()
{
CheckEqual(typeof(int), SharedConverters.CreateSession().ResolveName("int"));
}
public void ResolveName_ThrowsForATypeNothingRegistered()
{
CheckThrow(typeof(InvalidDataException));
SharedConverters.CreateSession().ResolveName("System.Diagnostics.Process");
}
}
public class ToNodeTests : TestFixture
{
public void TypedCall_SavesWhatTheTypeofCallSaves()
{
var waypoint = new Waypoint { Name = "home", At = new Point(1.5, -2) };
SaveNode typed = SaveConvertersTests.Session().ToNode(waypoint);
SaveNode untyped = SaveConvertersTests.Session().ToNode(waypoint, typeof(Waypoint));
CheckEqual(untyped.RequireEntry("name").Text, typed.RequireEntry("name").Text);
CheckEqual(untyped.RequireEntry("at").RequireEntry("x").Text,
typed.RequireEntry("at").RequireEntry("x").Text);
}
public const int ManyItems = 1000;
public static readonly SaveSession Shared = SaveConvertersTests.Session();
public static readonly SaveConverters SharedConverters = SaveConvertersTests.TestConverters();
public static readonly Point[] ManyPoints = Points(ManyItems);
public static Point[] Points(int count)
{
var returnMe = new Point[count];
for (int i = 0; i < count; ++i)
{
returnMe[i] = new Point(i, -i);
}
return returnMe;
}
public void BuiltInValue_IsSavedAsItsText()
{
CheckEqual("5", SaveConvertersTests.Session().ToNode(5).Text);
}
public void BuiltInValueTypes_SaveAsTheObjectCallSavesThem()
{
SaveSession session = SaveConvertersTests.Session();
CheckTrue(SavedValueTests.SameNode(session.ToNode((object)-7, typeof(int)), session.ToNode(-7)));
CheckTrue(SavedValueTests.SameNode(session.ToNode((object)0.1, typeof(double)), session.ToNode(0.1)));
CheckTrue(SavedValueTests.SameNode(session.ToNode((object)true, typeof(bool)), session.ToNode(true)));
CheckTrue(SavedValueTests.SameNode(session.ToNode((object)new DateTime(2020, 1, 2), typeof(DateTime)),
session.ToNode(new DateTime(2020, 1, 2))));
}
public void EnumAndNullable_SaveAsTheObjectCallSavesThem()
{
SaveSession session = SaveConvertersTests.Session();
CheckEqual("Running", session.ToNode(SavedValueTests.Phase.Running).Text);
CheckEqual("5", session.ToNode(5).Text);
CheckEqual(SaveNode.Kinds.Null, session.ToNode(null).Kind);
}
[AllocationTest]
public void Integer_AllocatesOnlyItsNode()
{
SaveNode node = Shared.ToNode(12345);
CheckAllocationsAtMost(maxBytesPerRun: IntPtr.Size == 4 ? 44 : 80);
CheckEqual("12345", node.Text);
}
public void StructsInsideAConverter_SaveAsTheObjectCallSavesThem()
{
SaveSession session = SaveConvertersTests.Session();
var points = new[] { new Point(1.5, -2), new Point(0, double.NaN) };
SaveNode list = session.ToNode(points);
for (int i = 0; i < points.Length; ++i)
{
SaveNode expected = SaveConvertersTests.Session().ToNode((object)points[i], typeof(Point));
CheckTrue(SavedValueTests.SameNode(expected, list.Items[i].Resolved));
}
}
public static LinkHolder Holding(int length)
{
return new LinkHolder { Head = SaveConvertersTests.Chain(length) };
}
public static int LinksIn(SaveNode holder)
{
return SaveConvertersTests.DepthOf(holder.RequireEntry("head"), "next");
}
public static readonly Point SomePoint = new Point(1.5, -2);
public static readonly Vector SomeVector = new Vector { X = 1.5, Y = -2 };
[AllocationTest]
public void StructAtTheTopLevel_AllocatesOnlyItsNodes()
{
SaveNode node = Shared.ToNode(SomePoint);
CheckAllocationsAtMost(maxBytesPerRun: IntPtr.Size == 4 ? 256 : 512);
CheckEqual(2, node.Entries.Count);
}
[AllocationTest]
public void GeneratedStructAtTheTopLevel_AllocatesOnlyItsNodes()
{
SaveNode node = Shared.ToNode(SomeVector);
CheckAllocationsAtMost(maxBytesPerRun: IntPtr.Size == 4 ? 256 : 512);
CheckEqual(2, node.Entries.Count);
}
public void StructAtTheTopLevel_SavesTheSameNodesAsTheObjectCall()
{
SaveSession session = SaveConvertersTests.Session();
CheckTrue(SavedValueTests.SameNode(session.ToNode((object)SomePoint, typeof(Point)),
session.ToNode(SomePoint)));
CheckTrue(SavedValueTests.SameNode(session.ToNode((object)SomeVector, typeof(Vector)),
session.ToNode(SomeVector)));
}
public void StructHoldingADeepChain_SavesEveryLink()
{
SaveNode node = SaveConvertersTests.Session().ToNode(Holding(10000));
CheckEqual(10000, LinksIn(node));
}
public void StructHoldingARing_ClosesTheRingOnTheFirstNode()
{
Link head = SaveConvertersTests.Chain(40);
Link tail = head;
while (tail.Next != null)
{
tail = tail.Next;
}
tail.Next = head;
SaveNode node = SaveConvertersTests.Session().ToNode(new LinkHolder { Head = head });
SaveNode first = node.RequireEntry("head").Resolved;
CheckTrue(ReferenceEquals(first, SaveConvertersTests.Descend(first, "next", 40)));
}
public void StructHoldingADeepChain_SavesTheSameAsTheObjectCall()
{
var holder = Holding(500);
SaveNode typed = SaveConvertersTests.Session().ToNode(holder);
SaveNode untyped = SaveConvertersTests.Session().ToNode((object)holder, typeof(LinkHolder));
CheckEqual(SaveConvertersTests.DepthOf(untyped.RequireEntry("head"), "next"),
SaveConvertersTests.DepthOf(typed.RequireEntry("head"), "next"));
}
public void TwoStructSavesInARow_EachSavesWhatItIsGiven()
{
SaveSession session = SaveConvertersTests.Session();
CheckEqual(300, SaveConvertersTests.DepthOf(
session.ToNode(Holding(300)).RequireEntry("head"), "next"));
CheckEqual(5, SaveConvertersTests.DepthOf(
session.ToNode(Holding(5)).RequireEntry("head"), "next"));
}
public void StructThatFailsToSave_Throws()
{
CheckThrow(typeof(InvalidOperationException));
SaveConvertersTests.Session().ToNode(new FaultyStruct());
}
public void FailedStructSaveAtTheTopLevel_MakesTheSessionForgetWhatItSaved()
{
SaveSession session = SaveConvertersTests.Session();
var waypoint = new Waypoint { Name = "home", At = new Point(1, 2) };
SaveNode first = session.ToNode(waypoint);
CheckTrue(ReferenceEquals(first, session.ToNode(waypoint)));
try
{
session.ToNode(new FaultyStruct());
}
catch (InvalidOperationException)
{
}
CheckFalse(ReferenceEquals(first, session.ToNode(waypoint)));
}
public void FailedStructSaveAtTheTopLevel_LeavesTheSessionAbleToSave()
{
SaveSession session = SaveConvertersTests.Session();
try
{
session.ToNode(new FaultyStruct());
}
catch (InvalidOperationException)
{
}
CheckEqual(10, SaveConvertersTests.DepthOf(
session.ToNode(Holding(10)).RequireEntry("head"), "next"));
}
public void StructSavedAtTheTopLevel_KeepsWhatTheSessionSaved()
{
SaveSession session = SaveConvertersTests.Session();
var waypoint = new Waypoint { Name = "home", At = new Point(1, 2) };
SaveNode first = session.ToNode(waypoint);
session.ToNode(SomePoint);
CheckTrue(ReferenceEquals(first, session.ToNode(waypoint)));
}
[AllocationTest]
public void ManyStructs_AllocateOnlyTheirNodes()
{
SaveNode node = SharedConverters.CreateSession().ToNode(ManyPoints);
CheckAllocationsAtMost(maxBytesPerRun: ManyItems * (IntPtr.Size == 4 ? 392 : 660) + 2048);
CheckEqual(ManyItems, node.Items.Count);
}
public void NullReference_IsANullNode()
{
CheckEqual(SaveNode.Kinds.Null, SaveConvertersTests.Session().ToNode(null).Kind);
}
public void SubtypeValueTypedAsItself_NamesNoSubtype()
{
SaveNode node = SaveConvertersTests.Session().ToNode(new Dog { Name = "Rex" });
CheckFalse(node.Entries.ContainsKey(SaveConverters.TypeKey));
}
public void SubtypeValueTypedAsItsBase_NamesItsSubtype()
{
SaveNode node = SaveConvertersTests.Session().ToNode(new Dog { Name = "Rex" });
CheckEqual("Vellum.UnitTests.Dog", node.RequireEntry(SaveConverters.TypeKey).Text);
}
}
public class FromNodeTests : TestFixture
{
public void SavedValue_RoundTrips()
{
SaveSession session = SaveConvertersTests.Session();
var waypoint = new Waypoint { Name = "home", At = new Point(1.5, -2) };
Waypoint loaded = session.FromNode(session.ToNode(waypoint));
CheckEqual("home", loaded.Name);
CheckEqual(-2.0, loaded.At.Y);
}
public static readonly SaveSession Shared = SaveConvertersTests.Session();
public static readonly SaveNode IntegerNode = SaveNode.OfText("12345");
public static readonly SaveConverters SharedConverters = SaveConvertersTests.TestConverters();
public static readonly SaveNode ManyPointsNode =
SharedConverters.CreateSession().ToNode(ToNodeTests.ManyPoints);
public const int ManyItems = ToNodeTests.ManyItems;
public void BuiltInValue_IsReadAsItsType()
{
CheckEqual(5, SaveConvertersTests.Session().FromNode(SaveNode.OfText("5")));
}
public void BuiltInValueTypes_RoundTripThroughTheTypedCalls()
{
SaveSession session = SaveConvertersTests.Session();
CheckEqual(-7, session.FromNode(session.ToNode(-7)));
CheckEqual(0.1, session.FromNode(session.ToNode(0.1)));
CheckEqual(true, session.FromNode(session.ToNode(true)));
var date = new DateTime(2020, 1, 2);
CheckEqual(date, session.FromNode(session.ToNode(date)));
}
public void EnumAndNullable_ReadAsTheObjectCallReadsThem()
{
SaveSession session = SaveConvertersTests.Session();
var phase = session.FromNode(SaveNode.OfText("Running"));
CheckEqual(SavedValueTests.Phase.Running, phase);
CheckEqual(5, session.FromNode(SaveNode.OfText("5")).Value);
}
public void ReferenceToText_ReadsWhatItStandsFor()
{
SaveNode reference = SaveNode.NewRef();
reference.Resolve(SaveNode.OfText("7"));
CheckEqual(7, SaveConvertersTests.Session().FromNode(reference));
}
public void TextThatIsNotAnInteger_ThrowsWhatTheObjectCallThrows()
{
SaveSession session = SaveConvertersTests.Session();
SaveNode text = SaveNode.OfText("seven");
CheckEqual(SavedValueTests.MessageOf(() => session.FromNode(text, typeof(int))),
SavedValueTests.MessageOf(() => session.FromNode(text)));
}
[AllocationTest]
public void Integer_AllocatesNothing()
{
int value = Shared.FromNode(IntegerNode);
CheckNoAllocations();
CheckEqual(12345, value);
}
public static readonly SaveNode NumberNode = SaveNode.OfDouble(1.5);
public void NumberNodeLoadedAsADouble_IsTheNumber()
{
CheckEqual(1.5, SaveConvertersTests.Session().FromNode(NumberNode));
}
public void NumberNodeLoadedAsAString_IsItsHexTextEachTime()
{
SaveSession session = SaveConvertersTests.Session();
CheckEqual("0x3FF8000000000000", session.FromNode(NumberNode));
CheckEqual("0x3FF8000000000000", session.FromNode(NumberNode));
}
[AllocationTest]
public void NumberNodeLoadedAsADouble_AllocatesNothing()
{
double value = Shared.FromNode(NumberNode);
CheckNoAllocations();
CheckEqual(1.5, value);
}
public static SaveNode PointList(params SaveNode[] items)
{
SaveNode returnMe = SaveNode.NewList();
returnMe.Items.AddRange(items);
return returnMe;
}
public void StructsInsideAConverter_RoundTrip()
{
SaveSession session = SaveConvertersTests.Session();
var points = new[] { new Point(1.5, -2), new Point(0, double.PositiveInfinity) };
var loaded = session.FromNode(session.ToNode(points));
CheckEqual(points[0], loaded[0]);
CheckEqual(points[1], loaded[1]);
}
public void StructNodeNamingItsOwnType_LoadsAsTheStruct()
{
SaveSession session = SaveConvertersTests.Session();
SaveNode named = session.ToNode(new Point(1.5, -2));
named.Entries[SaveConverters.TypeKey] = SaveNode.OfText("Vellum.UnitTests.Point");
var loaded = session.FromNode(PointList(named));
CheckEqual(new Point(1.5, -2), loaded[0]);
}
public void NullInPlaceOfAStruct_IsLeftTheDefaultAndRecorded()
{
SaveSession session = SaveConvertersTests.Session();
Point[] loaded = session.FromNode(PointList(SaveNode.Null));
CheckEqual(default(Point), loaded[0]);
CheckEqual(new[] { "[0]" }, SaveConvertersTests.IssuePaths(session));
}
public void StructMissingAPart_IsLeftTheDefaultAndRecorded()
{
SaveNode incomplete = SaveNode.NewTable();
incomplete.Entries["y"] = SaveNode.OfText("1");
SaveSession session = SaveConvertersTests.Session();
Point[] loaded = session.FromNode(PointList(incomplete));
CheckEqual(default(Point), loaded[0]);
CheckEqual(new[] { "[0]" }, SaveConvertersTests.IssuePaths(session));
}
public void AfterAStructFailedToLoad_TheSessionStillLoads()
{
SaveSession session = SaveConvertersTests.Session();
SaveNode good = session.ToNode(new Point(1.5, -2));
try
{
session.FromNode(PointList(good, SaveNode.Null));
}
catch (InvalidDataException)
{
}
CheckEqual(new Point(1.5, -2), session.FromNode(PointList(good))[0]);
}
public void StructNodeHeldTwice_LoadsACopyEachTime()
{
SaveSession session = SaveConvertersTests.Session();
SaveNode shared = session.ToNode(new Point(1.5, -2));
var loaded = session.FromNode(PointList(shared, shared));
CheckEqual(new Point(1.5, -2), loaded[0]);
CheckEqual(new Point(1.5, -2), loaded[1]);
}
public static readonly SaveNode ManyVectorsNode =
SharedConverters.CreateSession().ToNode(new Vector[ToNodeTests.ManyItems]);
public static readonly ArrayConverter VectorArrayConverter = new ArrayConverter();
public static readonly ArrayConverter PointArrayConverter = new ArrayConverter();
public readonly Vector[] VectorTarget =
Enumerable.Repeat(new Vector { X = 7 }, ToNodeTests.ManyItems).ToArray();
public readonly Point[] PointTarget = new Point[ToNodeTests.ManyItems];
[AllocationTest]
public void ManyGeneratedStructsFilledIntoAnArray_AllocateNothing()
{
VectorArrayConverter.FillBlank(VectorTarget, ManyVectorsNode, Shared);
CheckNoAllocations();
CheckEqual(0.0, VectorTarget[ToNodeTests.ManyItems - 1].X);
}
[AllocationTest]
public void ManyStructsFilledIntoAnArray_AllocateNothing()
{
PointArrayConverter.FillBlank(PointTarget, ManyPointsNode, Shared);
CheckNoAllocations();
CheckEqual(ToNodeTests.ManyPoints[ToNodeTests.ManyItems - 1], PointTarget[ToNodeTests.ManyItems - 1]);
}
public static Bead BeadChain(int count)
{
Bead returnMe = null;
for (int i = 0; i < count; ++i)
{
returnMe = new Bead { Next = returnMe };
}
return returnMe;
}
public static int BeadsIn(Bead head)
{
int returnMe = 0;
for (Bead next = head; next != null; next = next.Next)
{
++returnMe;
}
return returnMe;
}
public static IPouch SatchelChain(int count)
{
IPouch returnMe = null;
for (int i = 0; i < count; ++i)
{
returnMe = new Satchel { Inner = returnMe };
}
return returnMe;
}
public static int SatchelsIn(IPouch head)
{
int returnMe = 0;
for (IPouch next = head; next != null; next = ((Satchel)next).Inner)
{
++returnMe;
}
return returnMe;
}
public void ChainFarDeeperThanTheStackAllows_Loads()
{
SaveSession session = SaveConvertersTests.Session();
CheckEqual(10000, BeadsIn(session.FromNode(session.ToNode(BeadChain(10000)))));
}
public void ChainFarDeeperThanTheStackAllows_LoadsFromLuaText()
{
SaveSession session = SaveConvertersTests.Session();
SaveNode node = SaveConvertersTests.ThroughLuaText(session.ToNode(BeadChain(10000)));
CheckEqual(10000, BeadsIn(session.FromNode(node)));
}
public void ChainOfOneBead_Loads()
{
SaveSession session = SaveConvertersTests.Session();
CheckEqual(1, BeadsIn(session.FromNode(session.ToNode(BeadChain(1)))));
}
public void ChainHeldByAStructAtTheTopLevel_Loads()
{
SaveSession session = SaveConvertersTests.Session();
SaveNode node = session.ToNode(new BeadHolder { Head = BeadChain(10000) });
CheckEqual(10000, BeadsIn(session.FromNode(node).Head));
}
public void ChainsHeldByStructsInAList_Load()
{
SaveSession session = SaveConvertersTests.Session();
var holders = new List
{
new BeadHolder { Head = BeadChain(5000) }, new BeadHolder { Head = BeadChain(3000) },
};
var loaded = session.FromNode>(session.ToNode(holders));
CheckEqual(5000, BeadsIn(loaded[0].Head));
CheckEqual(3000, BeadsIn(loaded[1].Head));
}
public void ChainOfBoxedStructs_Loads()
{
SaveSession session = SaveConvertersTests.Session();
SaveNode node = session.ToNode(SatchelChain(5000), typeof(IPouch));
CheckEqual(5000, SatchelsIn((IPouch)session.FromNode(node, typeof(IPouch))));
}
public void ChainsInAList_Load()
{
SaveSession session = SaveConvertersTests.Session();
var beads = new List { BeadChain(4000), BeadChain(6000) };
var loaded = session.FromNode>(session.ToNode(beads));
CheckEqual(4000, BeadsIn(loaded[0]));
CheckEqual(6000, BeadsIn(loaded[1]));
}
public void LinkHeldByTwoChainsAndListedBetweenThem_LoadsAsOneObject()
{
SaveSession session = SaveConvertersTests.Session();
Bead shared = BeadChain(3000);
var beads = new List { new Bead { Next = shared }, shared, new Bead { Next = shared } };
var loaded = session.FromNode>(session.ToNode(beads));
CheckTrue(ReferenceEquals(loaded[1], loaded[0].Next));
CheckTrue(ReferenceEquals(loaded[1], loaded[2].Next));
CheckEqual(3000, BeadsIn(loaded[1]));
}
public void LinkHeldByTwoChainsAndListedAfterThem_LoadsAsOneObject()
{
SaveSession session = SaveConvertersTests.Session();
Bead shared = BeadChain(3000);
var beads = new List { new Bead { Next = shared }, new Bead { Next = shared }, shared };
var loaded = session.FromNode>(session.ToNode(beads));
CheckTrue(ReferenceEquals(loaded[2], loaded[0].Next));
CheckTrue(ReferenceEquals(loaded[2], loaded[1].Next));
}
public void ConverterThatDoesntList_StillThrowsPastTheLimitAndNamesItself()
{
SaveSession session = SaveConvertersTests.Session();
SaveNode node = session.ToNode(SaveConvertersTests.Chain(SaveNode.MaxDepth + 8), typeof(Link));
string message = "";
try
{
session.FromNode(node, typeof(Link));
}
catch (InvalidOperationException caught)
{
message = caught.Message;
}
CheckTrue(message.Contains(typeof(LinkConverter).FullName));
}
public void ConverterThatDoesntListWithinTheLimit_Loads()
{
SaveSession session = SaveConvertersTests.Session();
SaveNode node = session.ToNode(SaveConvertersTests.Chain(SaveNode.MaxDepth - 4), typeof(Link));
CheckNotNull(session.FromNode(node, typeof(Link)));
}
public void NodeNoBeadCouldHaveMade_ThrowsAndTheSessionLoadsAfterwards()
{
SaveSession session = SaveConvertersTests.Session();
SaveNode broken = session.ToNode(BeadChain(100));
SaveNode last = broken;
while (last.Entries["next"].Kind == SaveNode.Kinds.Table)
{
last = last.Entries["next"];
}
last.Entries["next"] = SaveNode.NewTable();
bool loadedBrokenChain = false;
try
{
session.FromNode(broken);
loadedBrokenChain = true;
}
catch (InvalidDataException)
{
}
CheckFalse(loadedBrokenChain);
CheckEqual(50, BeadsIn(session.FromNode(session.ToNode(BeadChain(50)))));
}
public void SubtypeHeldAsItsBase_LoadsAsTheSubtype()
{
SaveSession session = SaveConvertersTests.Session();
Animal loaded = session.FromNode(session.ToNode(new Dog { Name = "Rex" }));
CheckEqual(typeof(Dog), loaded.GetType());
}
public void NullForAReferenceType_IsNull()
{
CheckNull(SaveConvertersTests.Session().FromNode(SaveNode.Null));
}
public void NullForANullableValueType_IsNull()
{
CheckFalse(SaveConvertersTests.Session().FromNode(SaveNode.Null).HasValue);
}
public void NullForAValueType_Throws()
{
CheckThrow(typeof(InvalidDataException));
SaveConvertersTests.Session().FromNode(SaveNode.Null);
}
public void NodeOfTheWrongShape_Throws()
{
CheckThrow(typeof(InvalidDataException));
SaveConvertersTests.Session().FromNode(SaveNode.OfText("home"));
}
}
public class StructReadTests : TestFixture
{
public const int Last = RepeatedReads.Count - 1;
public readonly SaveSession Session = SaveConvertersTests.Session();
public readonly RepeatedReads VectorReads =
new RepeatedReads(SaveConvertersTests.Session().ToNode(new Vector { X = 1.5, Y = -2 }));
public readonly RepeatedReads PointReads =
new RepeatedReads(SaveConvertersTests.Session().ToNode(new Point(1.5, -2)));
public readonly RepeatedReads FixedReads =
new RepeatedReads(SaveConvertersTests.Session().ToNode(new Fixed(3, "label")));
[AllocationTest]
public void GeneratedStruct_AllocatesNothing()
{
VectorReads.Read(Session);
CheckNoAllocations();
CheckEqual(1.5, VectorReads.Into[Last].X);
CheckEqual(-2.0, VectorReads.Into[Last].Y);
}
[AllocationTest]
public void StructWithAHandWrittenConverter_AllocatesNothing()
{
PointReads.Read(Session);
CheckNoAllocations();
CheckEqual(new Point(1.5, -2), PointReads.Into[Last]);
}
[AllocationTest]
public void GeneratedStructWithAString_AllocatesNothing()
{
FixedReads.Read(Session);
CheckNoAllocations();
CheckEqual(3, FixedReads.Into[Last].Count);
CheckEqual("label", FixedReads.Into[Last].Label);
}
public void StructRead_IsTheSameAsTheObjectCallReads()
{
SaveSession session = SaveConvertersTests.Session();
SaveNode node = session.ToNode(new Vector { X = 1.5, Y = -2 });
var typed = session.FromNode(node);
var untyped = (Vector)session.FromNode(node, typeof(Vector));
CheckEqual(untyped.X, typed.X);
CheckEqual(untyped.Y, typed.Y);
}
public void FailedStructReadAtTheTopLevel_MakesTheSessionForgetWhatItLoaded()
{
SaveSession session = SaveConvertersTests.Session();
SaveNode shared = session.ToNode(new Waypoint { Name = "home", At = new Point(1, 2) });
var first = session.FromNode(shared);
CheckTrue(ReferenceEquals(first, session.FromNode(shared)));
try
{
session.FromNode(SaveNode.NewTable());
}
catch (InvalidDataException)
{
}
CheckFalse(ReferenceEquals(first, session.FromNode(shared)));
}
public void StructReadAtTheTopLevel_KeepsWhatTheSessionLoaded()
{
SaveSession session = SaveConvertersTests.Session();
SaveNode shared = session.ToNode(new Waypoint { Name = "home", At = new Point(1, 2) });
var first = session.FromNode(shared);
session.FromNode(session.ToNode(new Point(3, 4)));
CheckTrue(ReferenceEquals(first, session.FromNode(shared)));
}
}
public class LoadLoopTests : TestFixture
{
public static Ring RingOf(int count, bool closed)
{
Ring head = null;
Ring tail = null;
for (int i = 0; i < count; ++i)
{
var ring = new Ring { Name = "r" + i, Prev = tail };
if (tail == null)
{
head = ring;
}
else
{
tail.Next = ring;
}
tail = ring;
}
if (closed)
{
tail.Next = head;
head.Prev = tail;
}
return head;
}
///
/// Returns how many of the first rings from are named or
/// linked wrongly.
///
public static int WronglyLinked(Ring head, int count, bool closed)
{
int returnMe = 0;
Ring previous = null;
Ring ring = head;
for (int i = 0; i < count; ++i)
{
if (ring == null || ring.Name != "r" + i || (i > 0 && !ReferenceEquals(ring.Prev, previous)))
{
++returnMe;
break;
}
previous = ring;
ring = ring.Next;
}
if (closed && (!ReferenceEquals(ring, head) || !ReferenceEquals(head.Prev, previous)))
{
++returnMe;
}
if (!closed && (ring != null || head.Prev != null))
{
++returnMe;
}
return returnMe;
}
public void DoublyLinkedListFarDeeperThanTheStackAllows_Loads()
{
SaveSession session = SaveConvertersTests.Session();
Ring loaded = session.FromNode(session.ToNode(RingOf(10000, closed: false)));
CheckEqual(0, WronglyLinked(loaded, 10000, closed: false));
}
public void ClosedRing_Loads()
{
SaveSession session = SaveConvertersTests.Session();
Ring loaded = session.FromNode(session.ToNode(RingOf(5000, closed: true)));
CheckEqual(0, WronglyLinked(loaded, 5000, closed: true));
}
public void RingOfTwo_Loads()
{
SaveSession session = SaveConvertersTests.Session();
Ring loaded = session.FromNode(session.ToNode(RingOf(2, closed: true)));
CheckEqual(0, WronglyLinked(loaded, 2, closed: true));
}
public void RingOfOne_PointsAtItself()
{
SaveSession session = SaveConvertersTests.Session();
Ring loaded = session.FromNode(session.ToNode(RingOf(1, closed: true)));
CheckTrue(ReferenceEquals(loaded, loaded.Next));
CheckTrue(ReferenceEquals(loaded, loaded.Prev));
CheckEqual("r0", loaded.Name);
}
public void RingsListedSeparately_LoadAsTheSameObjects()
{
SaveSession session = SaveConvertersTests.Session();
Ring head = RingOf(3, closed: true);
var rings = new List { head, head.Next, head.Next.Next };
var loaded = session.FromNode>(session.ToNode(rings));
CheckTrue(ReferenceEquals(loaded[1], loaded[0].Next));
CheckTrue(ReferenceEquals(loaded[2], loaded[1].Next));
CheckTrue(ReferenceEquals(loaded[0], loaded[2].Next));
}
public void TwoRootsInTheSameLoop_LoadTheLoopOnce()
{
SaveSession session = SaveConvertersTests.Session();
Ring head = RingOf(3, closed: true);
SaveNode headNode = session.ToNode(head);
SaveNode middleNode = session.ToNode(head.Next);
Ring first = session.FromNode(headNode);
Ring second = session.FromNode(middleNode);
CheckTrue(ReferenceEquals(first.Next, second));
CheckTrue(ReferenceEquals(first, second.Next.Next));
CheckEqual(0, WronglyLinked(first, 3, closed: true));
}
public void TwoRootsInTheSameLoop_LoadTheLoopOnceWhicheverRootIsFirst()
{
SaveSession session = SaveConvertersTests.Session();
Ring head = RingOf(3, closed: true);
SaveNode headNode = session.ToNode(head);
SaveNode middleNode = session.ToNode(head.Next);
Ring second = session.FromNode(middleNode);
Ring first = session.FromNode(headNode);
CheckTrue(ReferenceEquals(first.Next, second));
CheckEqual(0, WronglyLinked(first, 3, closed: true));
}
public void RootThatPointsIntoAnEarlierRootsLoop_HoldsTheLoadedObjects()
{
SaveSession session = SaveConvertersTests.Session();
Ring head = RingOf(2, closed: true);
var outsider = new Ring { Name = "outsider", Next = head };
SaveNode headNode = session.ToNode(head);
SaveNode outsiderNode = session.ToNode(outsider);
Ring loadedHead = session.FromNode(headNode);
Ring loadedOutsider = session.FromNode(outsiderNode);
CheckTrue(ReferenceEquals(loadedHead, loadedOutsider.Next));
CheckTrue(ReferenceEquals(loadedHead.Next, loadedHead.Next.Next.Next));
CheckEqual(0, WronglyLinked(loadedHead, 2, closed: true));
}
public void RootLoadedTwice_IsTheSameObject()
{
SaveSession session = SaveConvertersTests.Session();
SaveNode headNode = session.ToNode(RingOf(3, closed: true));
CheckTrue(ReferenceEquals(session.FromNode(headNode), session.FromNode(headNode)));
}
public void PairWhoseConstructorsReadEachOthersNames_Loads()
{
var ann = new Spouse("ann", null);
var bob = new Spouse("bob", ann);
SpouseConverter.PartnerField.Set(ann, bob);
SaveSession session = SaveConvertersTests.Session();
Spouse loaded = session.FromNode(session.ToNode(ann));
CheckEqual("ann", loaded.Name);
CheckEqual("bob", loaded.Partner.Name);
CheckTrue(ReferenceEquals(loaded, loaded.Partner.Partner));
CheckEqual(6, loaded.PairedLength);
CheckEqual(6, loaded.Partner.PairedLength);
}
public void LoopWithAClassBuiltInOneGo_LoadsFromEitherEnd()
{
var plate = new Plate(null, "brass");
var knob = new Knob(plate);
PlateConverter.KnobField.Set(plate, knob);
SaveSession session = SaveConvertersTests.Session();
Knob fromKnob = session.FromNode(session.ToNode(knob));
Plate fromPlate = SaveConvertersTests.Session().FromNode(session.ToNode(plate));
CheckTrue(ReferenceEquals(fromKnob, fromKnob.Plate.Knob));
CheckEqual("brass", fromKnob.Plate.Label);
CheckTrue(ReferenceEquals(fromPlate, fromPlate.Knob.Plate));
CheckEqual("brass", fromPlate.Label);
}
public void LoopNeitherClassCanBeMadeEarlyFor_ThrowsAsItAlwaysDid()
{
var gate = new Gate { Name = "g" };
gate.Latch = new Latch { Name = "l", Gate = gate };
SaveSession session = SaveConvertersTests.Session();
SaveNode node = session.ToNode(gate);
CheckThrow(typeof(InvalidDataException));
session.FromNode(node);
}
public void HashSetInALoop_IsFilledAfterTheDataOfWhatItHashes()
{
var hub = new Hub(new HashSet());
hub.Badges.Add(new Badge("x", hub));
hub.Badges.Add(new Badge("y", hub));
SaveSession session = SaveConvertersTests.Session();
Hub loaded = session.FromNode(session.ToNode(hub));
CheckEqual(2, loaded.Badges.Count);
CheckTrue(loaded.Badges.Contains(new Badge("x", null)));
CheckTrue(loaded.Badges.Contains(new Badge("y", null)));
foreach (Badge badge in loaded.Badges)
{
CheckTrue(ReferenceEquals(loaded, badge.Hub));
}
}
public void LoaderThatReadsThroughItsLoop_SeesTheLoopsDataFromEitherEnd()
{
var clan = new Clan { Name = "wolves" };
clan.Scouts.Add(new Scout { Clan = clan, Rank = 1 });
clan.Scouts.Add(new Scout { Clan = clan, Rank = 2 });
SaveSession session = SaveConvertersTests.Session();
SaveNode node = session.ToNode(clan);
Clan fromClan = session.FromNode(node);
Scout fromScout = SaveConvertersTests.Session().FromNode(
SaveConvertersTests.Session().ToNode(clan.Scouts[1]));
CheckEqual(2, fromClan.Scouts[0].ClanSize);
CheckEqual(2, fromClan.Scouts[1].ClanSize);
CheckEqual(2, fromScout.ClanSize);
CheckEqual(2, fromScout.Rank);
CheckEqual("wolves", fromScout.Clan.Name);
}
public void HashSetOfGeneratedClassesHashedByTheirData_IsFilledAfterTheirData()
{
var earth = new Planet { Name = "earth" };
var mars = new Planet { Name = "mars" };
var venus = new Planet { Name = "venus" };
earth.Neighbors.Add(mars);
earth.Neighbors.Add(venus);
mars.Neighbors.Add(earth);
SaveSession session = SaveConvertersTests.Session();
Planet loaded = session.FromNode(session.ToNode(earth));
CheckEqual(2, loaded.Neighbors.Count);
CheckTrue(loaded.Neighbors.Contains(new Planet { Name = "mars" }));
CheckTrue(loaded.Neighbors.Contains(new Planet { Name = "venus" }));
foreach (Planet neighbor in loaded.Neighbors)
{
if (neighbor.Name == "mars")
{
CheckTrue(ReferenceEquals(loaded, neighbor.Neighbors.First()));
}
}
}
public void HashSetOfClassesThatCompleteButNeverFill_LoadsAsItDidBefore()
{
var first = new Mote("a", new HashSet());
var second = new Mote("b", new HashSet { first });
first.Near.Add(second);
SaveSession session = SaveConvertersTests.Session();
Mote loaded = session.FromNode(session.ToNode(first));
CheckEqual(1, loaded.Near.Count);
CheckEqual("b", loaded.Near.First().Name);
CheckTrue(loaded.Near.First().Near.Contains(loaded));
}
public void HashSetOfGeneratedClasses_Loads()
{
var first = new Vertex { Name = "v1" };
var second = new Vertex { Name = "v2" };
first.Neighbors.Add(second);
second.Neighbors.Add(first);
SaveSession session = SaveConvertersTests.Session();
Vertex loaded = session.FromNode(session.ToNode(first));
CheckEqual(1, loaded.Neighbors.Count);
Vertex neighbor = null;
foreach (Vertex vertex in loaded.Neighbors)
{
neighbor = vertex;
}
CheckEqual("v2", neighbor.Name);
CheckTrue(neighbor.Neighbors.Contains(loaded));
}
public void ArrayInALoop_Loads()
{
var first = new Cell { Name = "c1" };
var second = new Cell { Name = "c2" };
first.Around = new[] { second };
second.Around = new[] { first };
SaveSession session = SaveConvertersTests.Session();
Cell loaded = session.FromNode| (session.ToNode(first));
CheckEqual("c2", loaded.Around[0].Name);
CheckTrue(ReferenceEquals(loaded, loaded.Around[0].Around[0]));
}
public void AfterALoopFailedToLoad_TheSessionStillLoads()
{
var ann = new Spouse("ann", null);
var bob = new Spouse("bob", ann);
SpouseConverter.PartnerField.Set(ann, bob);
SaveSession session = SaveConvertersTests.Session();
SaveNode broken = session.ToNode(ann);
broken.Entries["name"] = SaveNode.NewTable();
bool loadedBroken = false;
try
{
session.FromNode(broken);
loadedBroken = true;
}
catch (InvalidDataException)
{
}
var cat = new Spouse("cat", null);
var dan = new Spouse("dan", cat);
SpouseConverter.PartnerField.Set(cat, dan);
Spouse loaded = session.FromNode(session.ToNode(cat));
CheckFalse(loadedBroken);
CheckEqual(6, loaded.PairedLength);
}
}
public class PrimitiveReadTests : TestFixture
{
public const int Last = RepeatedReads.Count - 1;
public readonly SaveSession Session = SaveConvertersTests.Session();
public readonly RepeatedReads SByteReads = new RepeatedReads(-5);
public readonly RepeatedReads ByteReads = new RepeatedReads(200);
public readonly RepeatedReads ShortReads = new RepeatedReads(-300);
public readonly RepeatedReads UShortReads = new RepeatedReads(60000);
public readonly RepeatedReads IntReads = new RepeatedReads(12345);
public readonly RepeatedReads UIntReads = new RepeatedReads(4000000000u);
public readonly RepeatedReads LongReads = new RepeatedReads(long.MinValue);
public readonly RepeatedReads ULongReads = new RepeatedReads(ulong.MaxValue);
public readonly RepeatedReads FloatReads = new RepeatedReads(0.1f);
public readonly RepeatedReads DoubleReads = new RepeatedReads(0.1 + 0.2);
public readonly RepeatedReads DecimalReads = new RepeatedReads(12.50m);
public readonly RepeatedReads BoolReads = new RepeatedReads(true);
public readonly RepeatedReads CharReads = new RepeatedReads('a');
public readonly RepeatedReads TimeSpanReads =
new RepeatedReads(TimeSpan.FromSeconds(90.5));
public readonly RepeatedReads DateTimeReads =
new RepeatedReads(new DateTime(2020, 1, 2, 3, 4, 5, DateTimeKind.Utc));
public readonly RepeatedReads DateTimeOffsetReads =
new RepeatedReads(new DateTimeOffset(2020, 1, 2, 3, 4, 5, TimeSpan.FromHours(-7)));
public readonly RepeatedReads GuidReads =
new RepeatedReads(new Guid("0f8fad5b-d9cb-469f-a165-70867728950e"));
[AllocationTest]
public void SByte_AllocatesNothing()
{
SByteReads.Read(Session);
CheckNoAllocations();
CheckEqual((sbyte)-5, SByteReads.Into[Last]);
}
[AllocationTest]
public void Byte_AllocatesNothing()
{
ByteReads.Read(Session);
CheckNoAllocations();
CheckEqual((byte)200, ByteReads.Into[Last]);
}
[AllocationTest]
public void Short_AllocatesNothing()
{
ShortReads.Read(Session);
CheckNoAllocations();
CheckEqual((short)-300, ShortReads.Into[Last]);
}
[AllocationTest]
public void UShort_AllocatesNothing()
{
UShortReads.Read(Session);
CheckNoAllocations();
CheckEqual((ushort)60000, UShortReads.Into[Last]);
}
[AllocationTest]
public void Int_AllocatesNothing()
{
IntReads.Read(Session);
CheckNoAllocations();
CheckEqual(12345, IntReads.Into[Last]);
}
[AllocationTest]
public void UInt_AllocatesNothing()
{
UIntReads.Read(Session);
CheckNoAllocations();
CheckEqual(4000000000u, UIntReads.Into[Last]);
}
[AllocationTest]
public void Long_AllocatesNothing()
{
LongReads.Read(Session);
CheckNoAllocations();
CheckEqual(long.MinValue, LongReads.Into[Last]);
}
[AllocationTest]
public void ULong_AllocatesNothing()
{
ULongReads.Read(Session);
CheckNoAllocations();
CheckEqual(ulong.MaxValue, ULongReads.Into[Last]);
}
[AllocationTest]
public void Float_AllocatesNothing()
{
FloatReads.Read(Session);
CheckNoAllocations();
CheckEqual(0.1f, FloatReads.Into[Last]);
}
[AllocationTest]
public void Double_AllocatesNothing()
{
DoubleReads.Read(Session);
CheckNoAllocations();
CheckEqual(0.1 + 0.2, DoubleReads.Into[Last]);
}
[AllocationTest]
public void Decimal_AllocatesNothing()
{
DecimalReads.Read(Session);
CheckNoAllocations();
CheckEqual(12.50m, DecimalReads.Into[Last]);
}
[AllocationTest]
public void Bool_AllocatesNothing()
{
BoolReads.Read(Session);
CheckNoAllocations();
CheckTrue(BoolReads.Into[Last]);
}
[AllocationTest]
public void Char_AllocatesNothing()
{
CharReads.Read(Session);
CheckNoAllocations();
CheckEqual('a', CharReads.Into[Last]);
}
[AllocationTest]
public void TimeSpan_AllocatesNothing()
{
TimeSpanReads.Read(Session);
CheckNoAllocations();
CheckEqual(TimeSpan.FromSeconds(90.5), TimeSpanReads.Into[Last]);
}
[AllocationTest]
public void DateTime_AllocatesNothing()
{
DateTimeReads.Read(Session);
CheckNoAllocations();
CheckEqual(new DateTime(2020, 1, 2, 3, 4, 5, DateTimeKind.Utc), DateTimeReads.Into[Last]);
CheckEqual(DateTimeKind.Utc, DateTimeReads.Into[Last].Kind);
}
[AllocationTest]
public void DateTimeOffset_AllocatesNothing()
{
DateTimeOffsetReads.Read(Session);
CheckNoAllocations();
CheckEqual(new DateTimeOffset(2020, 1, 2, 3, 4, 5, TimeSpan.FromHours(-7)),
DateTimeOffsetReads.Into[Last]);
}
[AllocationTest]
public void Guid_AllocatesNothing()
{
GuidReads.Read(Session);
CheckNoAllocations();
CheckEqual(new Guid("0f8fad5b-d9cb-469f-a165-70867728950e"), GuidReads.Into[Last]);
}
}
}
}
|