using System; using System.IO; using System.Linq; using UnitTestSharp; namespace Vellum.UnitTests { public class ArrayConverterTests : TestFixture { public const int ManyItems = 1000; public static readonly SaveConverters SharedConverters = SaveConvertersTests.TestConverters(); public static readonly SaveNode ManyIntegers = IntegerList(ManyItems); public static readonly int[] ManyIntegerArray = Enumerable.Range(0, ManyItems).ToArray(); public static readonly int[,] ManyIntegerGrid = IntegerGrid(ManyItems / 10, 10); public static readonly SaveNode ManyIntegerGridNode = SharedConverters.CreateSession().ToNode(ManyIntegerGrid, typeof(int[,])); public static SaveSession Session() { return SaveConvertersTests.TestConverters().CreateSession(); } public static SaveNode IntegerList(int count) { SaveNode returnMe = SaveNode.NewList(); for (int i = 0; i < count; ++i) { returnMe.Items.Add(SaveNode.OfText(i.ToString())); } return returnMe; } public static int[,] IntegerGrid(int rows, int columns) { var returnMe = new int[rows, columns]; for (int row = 0; row < rows; ++row) { for (int column = 0; column < columns; ++column) { returnMe[row, column] = row * columns + column; } } return returnMe; } public static int[,,] Counted() { var returnMe = new int[2, 3, 4]; int next = 0; for (int i = 0; i < 2; ++i) { for (int j = 0; j < 3; ++j) { for (int k = 0; k < 4; ++k) { returnMe[i, j, k] = next++; } } } return returnMe; } public class CtorTests : TestFixture { public void KeepsTheArrayTypeItConverts() { CheckTrue(new ArrayConverter(typeof(int[])).ValueType == typeof(int[])); CheckTrue(new ArrayConverter(typeof(int[,])).ValueType == typeof(int[,])); } public void NoArguments_ConvertsOneDimension() { CheckTrue(new ArrayConverter().ValueType == typeof(string[])); } public void TypeThatIsNotAnArray_Throws() { CheckThrow(typeof(ArgumentException)); new ArrayConverter(typeof(int)); } public void ArrayOfAnotherElementType_Throws() { CheckThrow(typeof(ArgumentException)); new ArrayConverter(typeof(string[])); } public void ArrayWithABound_Throws() { CheckThrow(typeof(ArgumentException)); new ArrayConverter(typeof(int).MakeArrayType(1)); } } public class WriteTests : TestFixture { public void OneDimension_IsAListOfItsItems() { SaveNode node = Session().ToNode(new[] { 3, 1, 2 }, typeof(int[])); CheckEqual(SaveNode.Kinds.List, node.Kind); CheckEqual(new[] { "3", "1", "2" }, node.Items.Select(item => item.Text).ToArray()); } public void ArrayOfArrays_IsAListOfLists() { SaveNode node = Session().ToNode(new[] { new[] { "a" }, new[] { "b", "c" } }, typeof(string[][])); CheckEqual("c", node.Items[1].Items[1].Text); } public void ArrayOfConvertedValues_IsAListOfTheirNodes() { SaveNode node = Session().ToNode(new[] { new Point(1.5, -2) }, typeof(Point[])); CheckEqual("-2", node.Items[0].RequireEntry("y").Comment); } public void ArrayOfNullables_IsAListWithNullsInPlace() { SaveNode node = Session().ToNode(new int?[] { 1, null }, typeof(int?[])); CheckEqual("1", node.Items[0].Text); CheckEqual(SaveNode.Kinds.Null, node.Items[1].Kind); } public void TwoDimensions_AreItsLengthsAndItsItemsRowByRow() { var value = new[,] { { 1, 2, 3 }, { 4, 5, 6 } }; SaveNode node = Session().ToNode(value, typeof(int[,])); CheckEqual(new[] { "2", "3" }, node.RequireEntry("lengths").Items.Select(item => item.Text).ToArray()); CheckEqual(new[] { "1", "2", "3", "4", "5", "6" }, node.RequireEntry("items").Items.Select(item => item.Text).ToArray()); } public void ThreeDimensions_AreSavedInRowMajorOrder() { SaveNode node = Session().ToNode(Counted(), typeof(int[,,])); CheckEqual(new[] { "2", "3", "4" }, node.RequireEntry("lengths").Items.Select(item => item.Text).ToArray()); CheckEqual(Enumerable.Range(0, 24).Select(number => number.ToString()).ToArray(), node.RequireEntry("items").Items.Select(item => item.Text).ToArray()); } public void NullArray_IsNull() { CheckEqual(SaveNode.Kinds.Null, Session().ToNode(null, typeof(int[])).Kind); CheckEqual(SaveNode.Kinds.Null, Session().ToNode(null, typeof(int[,])).Kind); } [AllocationTest] public void ManyIntegers_AllocateOnlyTheirNodes() { SaveNode node = SharedConverters.CreateSession().ToNode(ManyIntegerArray, typeof(int[])); CheckAllocationsAtMost(maxBytesPerRun: ManyItems * (IntPtr.Size == 4 ? 48 : 88) + 8192); CheckEqual(ManyItems, node.Items.Count); } [AllocationTest] public void ManyIntegersInAGrid_AllocateOnlyTheirNodes() { SaveNode node = SharedConverters.CreateSession().ToNode(ManyIntegerGrid, typeof(int[,])); CheckAllocationsAtMost(maxBytesPerRun: ManyItems * (IntPtr.Size == 4 ? 48 : 88) + 8192); CheckEqual(ManyItems, node.RequireEntry("items").Items.Count); } } public class ReadTests : TestFixture { public void OneDimensionHoldingItself_ReadsAsAnArrayHoldingItself() { SaveNode node = SaveNode.NewList(); node.Items.Add(node); var loaded = (object[])new SaveConverters(new SaveConverter[0]).CreateSession() .FromNode(node, typeof(object[])); CheckTrue(ReferenceEquals(loaded, loaded[0])); } public void OneDimension_RoundTrips() { SaveSession session = Session(); SaveNode node = session.ToNode(new[] { 3, 1, 2 }, typeof(int[])); CheckEqual(new[] { 3, 1, 2 }, (int[])session.FromNode(node, typeof(int[]))); } public void OneDimensionOfConvertedValues_RoundTrips() { SaveSession session = Session(); SaveNode node = session.ToNode(new[] { new Point(1.5, -2) }, typeof(Point[])); var loaded = (Point[])session.FromNode(node, typeof(Point[])); CheckEqual(-2.0, loaded[0].Y); } public void ArrayOfNullables_RoundTrips() { SaveSession session = Session(); SaveNode node = session.ToNode(new int?[] { 1, null }, typeof(int?[])); var loaded = (int?[])session.FromNode(node, typeof(int?[])); CheckEqual(1, loaded[0].Value); CheckFalse(loaded[1].HasValue); } public void EmptyOneDimensionThroughLuaText_RoundTrips() { SaveSession session = Session(); SaveNode node = SaveConvertersTests.ThroughLuaText(session.ToNode(new int[0], typeof(int[]))); CheckEqual(0, ((int[])session.FromNode(node, typeof(int[]))).Length); } public void ArrayOfArraysThroughLuaText_RoundTrips() { SaveSession session = Session(); var value = new[] { new[] { "a" }, new string[0], new[] { "b", null } }; SaveNode node = SaveConvertersTests.ThroughLuaText(session.ToNode(value, typeof(string[][]))); var loaded = (string[][])session.FromNode(node, typeof(string[][])); CheckEqual(3, loaded.Length); CheckEqual(0, loaded[1].Length); CheckNull(loaded[2][1]); } public void OneDimensionNodeThatIsNotAList_Throws() { CheckThrow(typeof(InvalidDataException)); Session().FromNode(SaveNode.OfText("3"), typeof(int[])); } public void ItemThatIsNotValid_IsLeftTheDefaultAndRecordedAtItsIndex() { SaveNode list = SaveNode.NewList(); list.Items.Add(SaveNode.OfText("7")); list.Items.Add(SaveNode.OfText("x")); list.Items.Add(SaveNode.OfText("9")); SaveSession session = Session(); CheckEqual(new[] { 7, 0, 9 }, (int[])session.FromNode(list, typeof(int[]))); CheckEqual(new[] { "[1]" }, SaveConvertersTests.IssuePaths(session)); } public void GridItemThatIsNotValid_IsLeftTheDefaultAndRecordedAtItsIndex() { SaveSession session = Session(); SaveNode node = session.ToNode(new[,] { { 1, 2 }, { 3, 4 } }, typeof(int[,])); node.Entries[SavedGrid.ItemsKey].Items[2] = SaveNode.OfText("x"); var loaded = (int[,])session.FromNode(node, typeof(int[,])); CheckEqual(new[] { 1, 2, 0, 4 }, loaded.Cast().ToArray()); CheckEqual(new[] { "items: [2]" }, SaveConvertersTests.IssuePaths(session)); } public void GridHoldingItself_ReadsAsAGridHoldingItself() { SaveNode node = SaveNode.NewTable(); SaveNode lengths = SaveNode.NewList(); lengths.Items.Add(SaveNode.OfText("1")); lengths.Items.Add(SaveNode.OfText("1")); SaveNode items = SaveNode.NewList(); items.Items.Add(node); node.Entries[SavedGrid.LengthsKey] = lengths; node.Entries[SavedGrid.ItemsKey] = items; var loaded = (object[,])new SaveConverters(new SaveConverter[0]).CreateSession() .FromNode(node, typeof(object[,])); CheckTrue(ReferenceEquals(loaded, loaded[0, 0])); } public void TwoDimensions_RoundTrip() { SaveSession session = Session(); var value = new[,] { { 1, 2, 3 }, { 4, 5, 6 } }; SaveNode node = session.ToNode(value, typeof(int[,])); var loaded = (int[,])session.FromNode(node, typeof(int[,])); CheckEqual(2, loaded.GetLength(0)); CheckEqual(3, loaded.GetLength(1)); CheckEqual(6, loaded[1, 2]); CheckEqual(2, loaded[0, 1]); CheckEqual(4, loaded[1, 0]); } public void ThreeDimensionsThroughLuaText_RoundTrip() { SaveSession session = Session(); SaveNode node = SaveConvertersTests.ThroughLuaText(session.ToNode(Counted(), typeof(int[,,]))); var loaded = (int[,,])session.FromNode(node, typeof(int[,,])); CheckEqual(Counted().Cast().ToArray(), loaded.Cast().ToArray()); CheckEqual(23, loaded[1, 2, 3]); CheckEqual(5, loaded[0, 1, 1]); } public void FiveDimensionsThroughLuaText_RoundTrip() { SaveSession session = Session(); var value = new string[2, 1, 2, 1, 2]; value[1, 0, 1, 0, 1] = "last"; SaveNode node = SaveConvertersTests.ThroughLuaText(session.ToNode(value, typeof(string[,,,,]))); var loaded = (string[,,,,])session.FromNode(node, typeof(string[,,,,])); CheckEqual("last", loaded[1, 0, 1, 0, 1]); CheckNull(loaded[0, 0, 0, 0, 0]); } public void EveryRankFromTwoToThirtyTwo_RoundTrips() { SaveSession session = Session(); for (int rank = 2; rank <= 32; ++rank) { foreach (int axis in new[] { 0, rank - 1 }) { var lengths = new int[rank]; for (int dimension = 0; dimension < rank; ++dimension) { lengths[dimension] = 1; } lengths[axis] = 3; Array grid = Array.CreateInstance(typeof(int), lengths); var position = new int[rank]; position[axis] = 2; grid.SetValue(rank, position); var loaded = (Array)session.FromNode(session.ToNode(grid, grid.GetType()), grid.GetType()); CheckEqual(rank, loaded.GetValue(position)); CheckEqual(3, loaded.GetLength(axis)); } } } public void ConvertedItemsThroughLuaText_RoundTrip() { SaveSession session = Session(); var value = new Point[2, 1]; value[1, 0] = new Point(1.5, -2); SaveNode node = SaveConvertersTests.ThroughLuaText(session.ToNode(value, typeof(Point[,]))); var loaded = (Point[,])session.FromNode(node, typeof(Point[,])); CheckEqual(-2.0, loaded[1, 0].Y); CheckEqual(0.0, loaded[0, 0].X); } public void ArrayWithNoItemsThroughLuaText_KeepsItsLengths() { SaveSession session = Session(); SaveNode node = SaveConvertersTests.ThroughLuaText(session.ToNode(new int[0, 5], typeof(int[,]))); var loaded = (int[,])session.FromNode(node, typeof(int[,])); CheckEqual(0, loaded.GetLength(0)); CheckEqual(5, loaded.GetLength(1)); } public void ArrayWithAnEmptyMiddleDimension_KeepsItsLengths() { SaveSession session = Session(); SaveNode node = SaveConvertersTests.ThroughLuaText( session.ToNode(new string[2, 0, 3], typeof(string[,,]))); var loaded = (string[,,])session.FromNode(node, typeof(string[,,])); CheckEqual(new[] { 2, 0, 3 }, new[] { loaded.GetLength(0), loaded.GetLength(1), loaded.GetLength(2) }); } public void NullForAnArray_IsNull() { CheckNull(Session().FromNode(SaveNode.Null, typeof(int[]))); CheckNull(Session().FromNode(SaveNode.Null, typeof(int[,]))); } public void GridNodeThatIsNotATable_Throws() { CheckThrow(typeof(InvalidDataException)); Session().FromNode(SaveNode.OfText("3"), typeof(int[,])); } public void OneConverter_IsMadeForEachArrayType() { SaveSession session = Session(); SaveNode ints = session.ToNode(new int[1, 1], typeof(int[,])); SaveNode strings = session.ToNode(new string[1, 1], typeof(string[,])); CheckEqual(SaveNode.Kinds.Table, ints.Kind); CheckEqual(SaveNode.Kinds.Null, strings.RequireEntry("items").Items[0].Kind); } public static readonly SaveSession Shared = SharedConverters.CreateSession(); public static readonly ArrayConverter ListConverter = new ArrayConverter(); public static readonly ArrayConverter GridConverter = new ArrayConverter(typeof(int[,])); public readonly int[] ListTarget = new int[ManyItems]; public readonly int[,] GridTarget = new int[ManyItems / 10, 10]; [AllocationTest] public void ManyIntegersFilledIntoAnArray_AllocateNothing() { ListConverter.FillBlank(ListTarget, ManyIntegers, Shared); CheckNoAllocations(); CheckEqual(ManyIntegerArray, ListTarget); } [AllocationTest] public void ManyIntegersInAGridFilledIntoAnArray_AllocateOnlyTheirPosition() { GridConverter.FillBlank(GridTarget, ManyIntegerGridNode, Shared); CheckAllocationsAtMost(maxBytesPerRun: IntPtr.Size == 4 ? 48 : 64); CheckEqual(ManyIntegerGrid[0, 0], GridTarget[0, 0]); CheckEqual(ManyIntegerGrid[ManyItems / 10 - 1, 9], GridTarget[ManyItems / 10 - 1, 9]); } } public class TryListChildrenTests : TestFixture { public void EachItemOfOneDimension_IsListedAsTheItemType() { var value = new[] { new Waypoint { Name = "a" }, null }; SaveNode node = Session().ToNode(value, typeof(Waypoint[])); var children = new ChildList(); CheckTrue(new ArrayConverter().TryListChildren(node, children)); CheckEqual(2, children.Count); CheckTrue(ReferenceEquals(node.Items[0], children.NodeAt(0))); CheckTrue(ReferenceEquals(node.Items[1], children.NodeAt(1))); CheckEqual(typeof(Waypoint), children.TypeAt(1)); } public void ArrayOfArrays_ListsEachInnerArrayAsAnArray() { SaveNode node = Session().ToNode(new[] { new[] { 1 }, new[] { 2, 3 } }, typeof(int[][])); var children = new ChildList(); CheckTrue(new ArrayConverter().TryListChildren(node, children)); CheckEqual(typeof(int[]), children.TypeAt(0)); CheckTrue(ReferenceEquals(node.Items[1], children.NodeAt(1))); } public void EmptyOneDimension_ListsNothingAndSucceeds() { var children = new ChildList(); CheckTrue(new ArrayConverter().TryListChildren(SaveNode.NewList(), children)); CheckEqual(0, children.Count); } public void OneDimensionNodeThatIsNotAList_ReturnsFalse() { CheckFalse(new ArrayConverter().TryListChildren(SaveNode.OfText("3"), new ChildList())); } public void EachItemOfAGrid_IsListedAsTheElementType() { var grid = new Waypoint[1, 2]; grid[0, 1] = new Waypoint { Name = "a" }; SaveNode node = Session().ToNode(grid, typeof(Waypoint[,])); var children = new ChildList(); CheckTrue(new ArrayConverter(typeof(Waypoint[,])).TryListChildren(node, children)); CheckEqual(2, children.Count); CheckTrue(ReferenceEquals(node.RequireEntry("items").Items[0], children.NodeAt(0))); CheckTrue(ReferenceEquals(node.RequireEntry("items").Items[1], children.NodeAt(1))); CheckEqual(typeof(Waypoint), children.TypeAt(1)); } public void LengthsAreBuiltInAndSoNotListed() { SaveNode node = Session().ToNode(new Waypoint[2, 2], typeof(Waypoint[,])); var children = new ChildList(); new ArrayConverter(typeof(Waypoint[,])).TryListChildren(node, children); CheckEqual(4, children.Count); } public void GridMissingItems_ReturnsFalse() { CheckFalse(new ArrayConverter(typeof(int[,])).TryListChildren(SaveNode.NewTable(), new ChildList())); } public void GridNodeThatIsNotATable_ReturnsFalse() { CheckFalse(new ArrayConverter(typeof(int[,])).TryListChildren(SaveNode.NewList(), new ChildList())); } } public class CanAllocateTests : TestFixture { public void Always_IsTrue() { CheckTrue(new ArrayConverter().CanAllocate); CheckTrue(new ArrayConverter(typeof(int[,])).CanAllocate); } } public class HasFillTests : TestFixture { public void Always_IsTrue() { CheckTrue(new ArrayConverter().HasFill); } } public class FillNeedsFilledPartTests : TestFixture { public void Always_IsFalse() { CheckFalse(new ArrayConverter().FillNeedsFilledPart(typeof(int))); } } public class AllocateBlankTests : TestFixture { public void OneDimension_GivesAnArrayOfTheSavedLength() { SaveNode node = Session().ToNode(new[] { 4, 5, 6 }, typeof(int[])); var made = (int[])new ArrayConverter().AllocateBlank(node, Session()); CheckEqual(3, made.Length); CheckEqual(0, made[2]); } public void TwoDimensions_GiveAGridOfTheSavedLengths() { SaveNode node = Session().ToNode(new int[2, 3], typeof(int[,])); var made = (int[,])new ArrayConverter(typeof(int[,])).AllocateBlank(node, Session()); CheckEqual(2, made.GetLength(0)); CheckEqual(3, made.GetLength(1)); } public void NodeThatIsNotAList_Throws() { CheckThrow(typeof(InvalidDataException)); new ArrayConverter().AllocateBlank(SaveNode.OfText("3"), Session()); } } public class FillBlankTests : TestFixture { public void OneDimension_FillsTheItemsIn() { SaveNode node = Session().ToNode(new[] { 4, 5, 6 }, typeof(int[])); var converter = new ArrayConverter(); object blank = converter.AllocateBlank(node, Session()); converter.FillBlank(blank, node, Session()); CheckEqual(new[] { 4, 5, 6 }, (int[])blank); } public void TwoDimensions_FillTheItemsIn() { SaveNode node = Session().ToNode(new[,] { { 1, 2 }, { 3, 4 } }, typeof(int[,])); var converter = new ArrayConverter(typeof(int[,])); object blank = converter.AllocateBlank(node, Session()); converter.FillBlank(blank, node, Session()); CheckEqual(3, ((int[,])blank)[1, 0]); } } public class CompleteBlankTests : TestFixture { public void Always_DoesNothing() { var made = new[] { 1 }; new ArrayConverter().CompleteBlank(made, SaveNode.NewList(), Session()); CheckEqual(1, made[0]); } } } }