private NumberListColumn <int> BuildSampleColumn() { List <ArraySlice <int> > expected = new List <ArraySlice <int> >(); expected.Add(new ArraySlice <int>(new int[] { 5, 6, 7 })); expected.Add(new ArraySlice <int>(new int[] { 2, 3, 4, 5 })); expected.Add(new ArraySlice <int>(Enumerable.Range(0, 8192).ToArray())); NumberListColumn <int> column = new NumberListColumn <int>(); NumberListColumn <int> roundTripped; // Set each list, and verify they are correct when read back for (int i = 0; i < expected.Count; ++i) { column[i].SetTo(expected[i]); } for (int i = 0; i < expected.Count; ++i) { CollectionReadVerifier.VerifySame(expected[i], column[i]); } // Round trip and verify they deserialize correctly (note, these will be in a shared array now) roundTripped = TreeSerializer.RoundTrip(column, TreeFormat.Binary); for (int i = 0; i < expected.Count; ++i) { CollectionReadVerifier.VerifySame(expected[i], column[i]); } return(roundTripped); }
public void NumberListColumn_NumberList_Basics() { // Set up column with sample values, roundtrip, re-verify NumberListColumn <int> column = BuildSampleColumn(); // Verify second value is in a shared array, not at index zero, not expandable (yet), not ReadOnly NumberList <int> slice = column[1]; Assert.Equal(4, slice.Count); Assert.True(slice.Slice.Index > 0); Assert.False(slice.Slice.IsExpandable); // Test second sample row slice IList members CollectionChangeVerifier.VerifyList(slice, (index) => index % 20); // Verify expandable after test Assert.Equal(0, slice.Slice.Index); Assert.True(slice.Slice.IsExpandable); // Verify values are re-merged and re-loaded properly string values = string.Join(", ", slice); column = TreeSerializer.RoundTrip(column, TreeFormat.Binary); Assert.Equal(values, string.Join(", ", column[1])); // Column range check Assert.Throws <IndexOutOfRangeException>(() => column[-1]); }
public void NumberListColumn_NullableCases() { // StringColumns are extremely common in object models, // so having very compact representations for common cases // is really important to file size for small databases. GenericNumberListColumn <int> column = new GenericNumberListColumn <int>(Nullability.DefaultToNull); TreeDiagnostics diagnostics; // Empty: { } diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(diagnostics.Length <= 2); // All null: { IsNull: { Count: 100, Capacity: 100 } } for (int i = 0; i < 100; ++i) { column[i] = null; } CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false)); diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(1 == diagnostics.Children.Count); Assert.True(diagnostics.Length <= 13); // All empty: Only nulls false written List <int> empty = new List <int>(); for (int i = 0; i < 100; ++i) { column[i] = empty; } CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false)); diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(1 == diagnostics.Children.Count); Assert.True(diagnostics.Length <= 13); // No nulls, No Empty: 4b + 2.125b / value (613b) + 4 pages x 4b (16b) + overhead (~10b) List <int> single = new List <int>(); single.Add(1); for (int i = 0; i < 100; ++i) { column[i] = single; } CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false)); diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(1 == diagnostics.Children.Count); Assert.True(diagnostics.Length <= 640); // Nulls and Non-Nulls; both parts must be written column[50] = null; CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false)); diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(2 == diagnostics.Children.Count); Assert.True(diagnostics.Length <= 670); }
internal static void VerifyRoundTrip <T>(ArraySlice <T> slice, T[] copyToTargetArray) where T : unmanaged, IEquatable <T> { ArraySlice <T> roundTripped = TreeSerializer.RoundTrip(slice, TreeFormat.Binary); CollectionReadVerifier.VerifySame <T>(slice, roundTripped); VerifyCopyTo <T>(roundTripped, copyToTargetArray); }
public void ArraySliceTests_Basics() { int[] sample = Enumerable.Range(100, 50).ToArray(); int[] copyToTarget = new int[100]; ArraySlice <int> slice; // Empty ArraySlice slice = ArraySlice <int> .Empty; slice.Trim(); Assert.Empty(slice); Assert.True(slice == ArraySlice <int> .Empty); Assert.False(slice != ArraySlice <int> .Empty); VerifyCopyTo <int>(slice, copyToTarget); VerifyRoundTrip <int>(slice, copyToTarget); CollectionReadVerifier.VerifySame(slice, TreeSerializer.RoundTrip(slice, TreeFormat.Binary)); CollectionReadVerifier.VerifySame(slice, TreeSerializer.RoundTrip(slice, TreeFormat.Json)); // Whole Array slice = new ArraySlice <int>(sample); Assert.Equal(sample.Length, slice.Count); Assert.Equal(sample, slice); Assert.Equal(sample[10], slice[10]); VerifyCopyTo <int>(slice, copyToTarget); VerifyRoundTrip <int>(slice, copyToTarget); CollectionReadVerifier.VerifySame(slice, TreeSerializer.RoundTrip(slice, TreeFormat.Binary)); CollectionReadVerifier.VerifySame(slice, TreeSerializer.RoundTrip(slice, TreeFormat.Json)); // Array slice-to-end slice = new ArraySlice <int>(sample, index: 10); Assert.Equal(sample.Length - 10, slice.Count); Assert.Equal(sample[20], slice[10]); Assert.False(slice.Equals(sample)); VerifyCopyTo <int>(slice, copyToTarget); VerifyRoundTrip <int>(slice, copyToTarget); CollectionReadVerifier.VerifySame(slice, TreeSerializer.RoundTrip(slice, TreeFormat.Binary)); CollectionReadVerifier.VerifySame(slice, TreeSerializer.RoundTrip(slice, TreeFormat.Json)); // Array slice slice = new ArraySlice <int>(sample, index: 10, length: 20); Assert.Equal(20, slice.Count); Assert.Equal(sample[10], slice[0]); VerifyCopyTo <int>(slice, copyToTarget); VerifyRoundTrip <int>(slice, copyToTarget); CollectionReadVerifier.VerifySame(slice, TreeSerializer.RoundTrip(slice, TreeFormat.Binary)); CollectionReadVerifier.VerifySame(slice, TreeSerializer.RoundTrip(slice, TreeFormat.Json)); // Bounds checks Assert.Throws <ArgumentNullException>(() => new ArraySlice <int>(null, 0, 0)); // Array null Assert.Throws <ArgumentOutOfRangeException>(() => new ArraySlice <int>(sample, -1, 0)); // index < 0 Assert.Throws <ArgumentOutOfRangeException>(() => new ArraySlice <int>(sample, sample.Length + 1, 0)); // index > array.Length Assert.Throws <ArgumentOutOfRangeException>(() => new ArraySlice <int>(sample, 0, sample.Length + 1)); // length too long Assert.Throws <ArgumentOutOfRangeException>(() => new ArraySlice <int>(sample, 2, sample.Length + 3)); // Clear slice.Clear(); Assert.Empty(slice); }
public void RefListColumn_Basics() { string referencedTable = "ReferencedTable"; RefListColumn column = new RefListColumn(referencedTable); RefListColumn roundTripped = TreeSerializer.RoundTrip(column, () => new RefListColumn(referencedTable), TreeFormat.Binary); Assert.Equal(referencedTable, roundTripped.ReferencedTableName); }
public void RefColumn_Basics() { string referencedTable = "ReferencedTable"; Column.Basics <int>(() => new RefColumn(referencedTable), -1, 10, (index) => 2 * index); // Verify ReferencedTableName stored and correctly kept after deserialize Assert.Equal(referencedTable, TreeSerializer.RoundTrip(new RefColumn(referencedTable), () => new RefColumn(referencedTable), TreeFormat.Binary).ReferencedTableName); }
public void StringColumn_LongValuesAndMerging() { StringColumn column = new StringColumn(); List <string> expected = new List <string>(); StringColumn roundTripped; // Test values just at and above LargeValue limit expected.Add(new string(' ', 2047)); expected.Add(null); expected.Add(string.Empty); expected.Add("Normal"); expected.Add(new string(' ', 2048)); for (int i = 0; i < expected.Count; ++i) { column[i] = expected[i]; } // Verify values properly captured CollectionReadVerifier.VerifySame(expected, column); // Proactively Trim (before serialization) and verify values not corrupted column.Trim(); CollectionReadVerifier.VerifySame(expected, column); // Verify roundtripped column and column not corrupted by serialization roundTripped = TreeSerializer.RoundTrip(column, TreeFormat.Binary); CollectionReadVerifier.VerifySame(expected, roundTripped); CollectionReadVerifier.VerifySame(expected, column); // Set a short value to long and a long value to short, and add another value expected[0] = new string(':', 2400); expected[2] = "MuchShorter"; expected.Add("Simple"); for (int i = 0; i < expected.Count; ++i) { column[i] = expected[i]; } // Verify values read back correctly immediately CollectionReadVerifier.VerifySame(expected, column); // Verify values re-roundtrip again properly (merging old and new immutable values) roundTripped = TreeSerializer.RoundTrip(column, TreeFormat.Binary); CollectionReadVerifier.VerifySame(expected, roundTripped); CollectionReadVerifier.VerifySame(expected, column); // Add a value causing a gap; verify count, new value returned, values in gap defaulted properly column[100] = "Centennial"; Assert.Equal(101, column.Count); Assert.Equal("Centennial", column[100]); Assert.Null(column[99]); }
public void DistinctColumn_Conversion() { int defaultValue = -1; Func <DistinctColumn <int> > ctor = () => new DistinctColumn <int>(new NumberColumn <int>(defaultValue), defaultValue); DistinctColumn <int> column = ctor(); List <int> expected = new List <int>(); // Verify empty and set up to map values by default Assert.Empty(column); Assert.True(column.IsMappingValues); // Round trip and verify it resets back to mapping correctly column = TreeSerializer.RoundTrip(column, ctor, TreeFormat.Binary); Assert.Empty(column); Assert.True(column.IsMappingValues); // Add 1,000 values with 10 distinct values for (int i = 0; i < 1000; ++i) { int value = i % 10; column[i] = value; expected.Add(value); } // Verify column is mapping, has 11 unique values (default + 10), and matches expected array Assert.True(column.IsMappingValues); Assert.Equal(11, column.DistinctCount); CollectionReadVerifier.VerifySame(expected, column); // Round trip; verify mapped column rehydrates properly column = TreeSerializer.RoundTrip(column, ctor, TreeFormat.Binary); CollectionReadVerifier.VerifySame(expected, column); // Add enough values to force the column to convert for (int i = 1000; i < 1300; ++i) { column[i] = i; expected.Add(i); } Assert.False(column.IsMappingValues); Assert.Equal(-1, column.DistinctCount); CollectionReadVerifier.VerifySame(expected, column); // Round-trip; verify individual values column rehydrates properly column = TreeSerializer.RoundTrip(column, ctor, TreeFormat.Binary); CollectionReadVerifier.VerifySame(expected, column); // Test RemoveFromEnd on unmapped form of column column.RemoveFromEnd(100); expected.RemoveRange(expected.Count - 100, 100); CollectionReadVerifier.VerifySame(expected, column); }
public void StringColumn_EmptyCases() { // StringColumns are extremely common in object models, // so having very compact representations for common cases // is really important to file size for small databases. StringColumn column = new StringColumn(); TreeDiagnostics diagnostics; // Empty: { } diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(diagnostics.Length <= 2); // All null: { IsNull: { Count: 100, Capacity: 100 } } for (int i = 0; i < 100; ++i) { column[i] = null; } CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false)); diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(1 == diagnostics.Children.Count); Assert.True(diagnostics.Length <= 13); // All empty: Only nulls false written for (int i = 0; i < 100; ++i) { column[i] = ""; } CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false)); diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(1 == diagnostics.Children.Count); Assert.True(diagnostics.Length <= 13); // No nulls, No Empty: 3b / value (2b end + 1b text) + 4 pages x 4b + 20b overhead for (int i = 0; i < 100; ++i) { column[i] = "-"; } CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false)); diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(1 == diagnostics.Children.Count); Assert.True(diagnostics.Length <= 336); // Nulls and Non-Nulls; both parts must be written column[50] = null; CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false)); diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(2 == diagnostics.Children.Count); Assert.True(diagnostics.Length <= 336 + 40); }
public void NumberListColumn_TypeList_Basics() { // Set up column with sample values, roundtrip, re-verify NumberListColumn <int> column = BuildSampleColumn(); // Verify second value is in a shared array, not at index zero, not expandable (yet), not ReadOnly NumberList <int> row1List = column[1]; TypedList <int> row1Typed = new TypedList <int>(row1List, (index) => index, (index) => index); // Test second sample row slice IList members on NumberListConverter CollectionChangeVerifier.VerifyList(row1Typed, (index) => index % 20); // Verify values are re-merged and re-loaded properly string values = string.Join(", ", row1List); column = TreeSerializer.RoundTrip(column, TreeFormat.Binary); Assert.Equal(values, string.Join(", ", column[1])); // TypedList Equality TypedList <int> row1 = new TypedList <int>(column[1], (index) => index, (index) => index); TypedList <int> row0 = new TypedList <int>(column[0], (index) => index, (index) => index); Assert.True(row1.Equals(row1)); Assert.False(row1.Equals(row0)); Assert.False(row1 == row0); Assert.True(row1 != row0); Assert.False(null == row0); Assert.True(null != row0); Assert.Equal(row1.GetHashCode(), row1.GetHashCode()); // TypedList.Indices Assert.Equal(row1.Indices, column[1]); // SetTo(other) TypedList <int> firstRow = new TypedList <int>(column[0], (index) => index, (index) => index); row1Typed.SetTo(firstRow); Assert.Equal(string.Join(", ", firstRow), string.Join(", ", row1Typed)); // SetTo(null) row1Typed.SetTo(null); Assert.Empty(row1Typed); // SetTo(IList) row1Typed.SetTo(new int[] { 2, 3, 4, 5 }); Assert.Equal("2, 3, 4, 5", string.Join(", ", row1Typed)); // SetTo(empty) row1Typed.SetTo(Array.Empty <int>()); Assert.Empty(row1Typed); }
public void TreeReaderWriter_ExtensionMethods_Tests() { // DateTime and Guid serialization covered in TreeSerializable.Basics Random r = new Random(); Sample sample = new Sample(r); Sample sample2 = new Sample(r); // List and Dictionary serialization built-ins CollectionContainer <Sample> samples = new CollectionContainer <Sample>(); samples.Add(sample); samples.Add(sample2); samples.AssertEqual(TreeSerializer.RoundTrip(samples, TreeFormat.Json)); samples.AssertEqual(TreeSerializer.RoundTrip(samples, TreeFormat.Binary)); // Null List / Dictionary handling samples.SetCollectionsNull(); samples.AssertEqual(TreeSerializer.RoundTrip(samples, TreeFormat.Json)); samples.AssertEqual(TreeSerializer.RoundTrip(samples, TreeFormat.Binary)); }
public static void Basics <T>(Func <IColumn <T> > builder, T defaultValue, T otherValue, Func <int, T> valueProvider) { IColumn <T> column = builder(); List <T> expected = new List <T>(); // ICollection basics Assert.False(column.IsReadOnly); Assert.False(column.IsSynchronized); Assert.Null(column.SyncRoot); Assert.Equal(typeof(T), column.Type); // Empty behavior Assert.Equal(0, column.Count); Assert.Equal(defaultValue, column[0]); Assert.Equal(defaultValue, column[10]); Assert.Equal(0, column.Count); // Empty roundtrip works CollectionReadVerifier.VerifySame(expected, TreeSerializer.RoundTrip(column, builder, TreeFormat.Binary)); CollectionReadVerifier.VerifySame(expected, TreeSerializer.RoundTrip(column, builder, TreeFormat.Json)); // Empty trim works column.Trim(); // Append values for (int i = 0; i < 50; ++i) { T value = valueProvider(i); column.Add(value); expected.Add(value); } // Verify count, values, indexer, enumerators Assert.Equal(expected.Count, column.Count); CollectionReadVerifier.VerifySame <T>(expected, column); // Verify item type supports equatability (needed for IndexOf, Contains to work) Assert.True(otherValue.Equals(otherValue)); Assert.Equal(otherValue.GetHashCode(), otherValue.GetHashCode()); Assert.False(otherValue.Equals(defaultValue)); Assert.NotEqual(otherValue.GetHashCode(), defaultValue?.GetHashCode() ?? 0); Assert.False(otherValue.Equals(1.45d)); // Contains / IndexOf T notInList = valueProvider(50); if (!expected.Contains(notInList)) { Assert.DoesNotContain(notInList, column); Assert.Equal(-1, column.IndexOf(notInList)); } Assert.Contains(valueProvider(1), column); Assert.Equal(1, column.IndexOf(valueProvider(1))); // CopyTo T[] other = new T[column.Count + 1]; column.CopyTo(other, 1); Assert.Equal(column[0], other[1]); // CopyTo preconditions if (!Debugger.IsAttached) { Assert.Throws <ArgumentNullException>(() => column.CopyTo(null, 0)); Assert.Throws <ArgumentException>(() => column.CopyTo(other, 2)); Assert.Throws <ArgumentOutOfRangeException>(() => column.CopyTo(other, -1)); Assert.Throws <ArgumentException>(() => column.CopyTo((Array)(new decimal[column.Count]), 0)); } // CopyTo (untyped) other = new T[column.Count]; column.CopyTo((Array)other, 0); CollectionReadVerifier.VerifySame(other, column, quick: true); // Change existing value column[1] = otherValue; Assert.Equal(otherValue, column[1]); // Set value back to default, back to non-default column[1] = defaultValue; Assert.Equal(defaultValue, column[1]); column[1] = valueProvider(1); Assert.Equal(valueProvider(1), column[1]); // Add() without instance T item = column.Add(); Assert.Equal(defaultValue, item); // Copy values via CopyItem column.CopyItem(1, column, 2); Assert.Equal(column[1], column[2]); column[1] = valueProvider(1); // CopyItem type checking if (!Debugger.IsAttached) { Assert.Throws <ArgumentException>(() => column.CopyItem(1, new NumberColumn <Decimal>(0.0m), 0)); } // Append so resize is required column[100] = valueProvider(100); // Verify old values were kept, middle defaulted, last one set for (int i = 0; i < column.Count; ++i) { T value = (i < 50 || i == 100 ? valueProvider(i) : defaultValue); Assert.Equal(value, column[i]); } // Verify serialization round trip via all current serialization mechanisms CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, builder, TreeFormat.Binary), quick: true); CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, builder, TreeFormat.Json), quick: true); // Verify column is properly skippable (required to allow flexible file format schema) TreeSerializer.VerifySkip(column, TreeFormat.Binary); TreeSerializer.VerifySkip(column, TreeFormat.Json); // Verify original values are still there post-serialization (ensure column not corrupted by serialization) for (int i = 0; i < column.Count; ++i) { T value = (i < 50 || i == 100 ? valueProvider(i) : defaultValue); Assert.Equal(value, column[i]); } // Swap two non-default values, verify swapped, swap back column.Swap(10, 20); Assert.Equal(valueProvider(10), column[20]); Assert.Equal(valueProvider(20), column[10]); column.Swap(10, 20); Assert.Equal(valueProvider(10), column[10]); Assert.Equal(valueProvider(20), column[20]); // Swap a default with a non-default value, verify swapped, swap back column.Swap(30, 60); Assert.Equal(valueProvider(30), column[60]); Assert.Equal(defaultValue, column[30]); column.Swap(30, 60); Assert.Equal(valueProvider(30), column[30]); Assert.Equal(defaultValue, column[60]); // Verify RemoveFromEnd for only default values works column.RemoveFromEnd(column.Count - 60); Assert.Equal(60, column.Count); Assert.Equal(defaultValue, column[60]); // Verify RemoveFromEnd down to non-default values works column.RemoveFromEnd(60 - 10); Assert.Equal(10, column.Count); for (int i = 0; i < 60; ++i) { T value = (i < 10 ? valueProvider(i) : defaultValue); Assert.Equal(value, column[i]); } // Append a default value big enough another resize would be required // Verify the count is tracked correctly, previous items are initialized to default column[201] = defaultValue; Assert.Equal(202, column.Count); Assert.Equal(defaultValue, column[200]); // Verify serialization handles 'many defaults at end' properly CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, builder, TreeFormat.Binary), quick: true); // Verify Trim doesn't throw column.Trim(); // Verify clear resets count and that previously set values are back to default if accessed column.Clear(); Assert.Equal(0, column.Count); Assert.Equal(defaultValue, column[0]); Assert.Equal(defaultValue, column[1]); // Add one default value (inner array may still not be allocated), then try RemoveFromEnd column[0] = defaultValue; column.RemoveFromEnd(1); Assert.Equal(0, column.Count); if (!Debugger.IsAttached) { // Verify indexer range check (< 0 only; columns auto-size for bigger values) Assert.Throws <IndexOutOfRangeException>(() => column[-1]); // Verify Remove throws (not expected to be implemented) Assert.Throws <NotSupportedException>(() => column.Remove(defaultValue)); } }