예제 #1
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));
            }
        }