Пример #1
0
        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);
        }
Пример #2
0
        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]);
        }
Пример #3
0
        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);
        }
Пример #4
0
        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);
        }
Пример #5
0
        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);
        }
Пример #6
0
        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);
        }
Пример #7
0
        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);
        }
Пример #8
0
        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]);
        }
Пример #9
0
        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);
        }
Пример #10
0
        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);
        }
Пример #11
0
        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);
        }
Пример #12
0
        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));
        }
Пример #13
0
        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));
            }
        }