// Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System; using System.Collections.Generic; using System.Linq; using Apache.Arrow.Memory; using Xunit; namespace Apache.Arrow.Tests { public class BinaryArrayBuilderTests { private static readonly MemoryAllocator _allocator = new NativeMemoryAllocator(); // Various example byte arrays for use in testing. private static readonly byte[] _exampleNull = null; private static readonly byte[] _exampleEmpty = { }; private static readonly byte[] _exampleNonEmpty1 = { 10, 20, 30, 40 }; private static readonly byte[] _exampleNonEmpty2 = { 50, 60, 70, 80 }; private static readonly byte[] _exampleNonEmpty3 = { 90 }; // Base set of single bytes that may be used to append to a builder in testing. private static readonly byte[] _singleBytesToAppend = { 0, 123, 127, 255 }; // Base set of byte arrays that may be used to append to a builder in testing. private static readonly byte[][] _byteArraysToAppend = { _exampleNull, _exampleEmpty, _exampleNonEmpty2, _exampleNonEmpty3, }; // Base set of multiple byte arrays that may be used to append to a builder in testing. private static readonly byte[][][] _byteArrayArraysToAppend = { new byte[][] { }, new[] { _exampleNull }, new[] { _exampleEmpty }, new[] { _exampleNonEmpty2 }, new[] { _exampleNonEmpty2, _exampleNonEmpty3 }, new[] { _exampleNonEmpty2, _exampleEmpty, _exampleNull }, }; // Base set of byte arrays that can be used as "initial contents" of any builder under test. private static readonly byte[][][] _initialContentsSet = { new byte[][] { }, new[] { _exampleNull }, new[] { _exampleEmpty }, new[] { _exampleNonEmpty1 }, new[] { _exampleNonEmpty1, _exampleNonEmpty3 }, new[] { _exampleNonEmpty1, _exampleEmpty, _exampleNull }, }; public class Append { public static IEnumerable _appendSingleByteTestData = from initialContents in _initialContentsSet from singleByte in _singleBytesToAppend select new object[] { initialContents, singleByte }; [Theory] [MemberData(nameof(_appendSingleByteTestData))] public void AppendSingleByte(byte[][] initialContents, byte singleByte) { // Arrange var builder = new BinaryArray.Builder(); if (initialContents.Length > 0) builder.AppendRange(initialContents); int initialLength = builder.Length; int expectedLength = initialLength + 1; var expectedArrayContents = initialContents.Append(new[] { singleByte }); // Act var actualReturnValue = builder.Append(singleByte); // Assert Assert.Equal(builder, actualReturnValue); Assert.Equal(expectedLength, builder.Length); var actualArray = builder.Build(_allocator); AssertArrayContents(expectedArrayContents, actualArray); } [Theory] [MemberData(nameof(_appendSingleByteTestData))] public void AppendSingleByteAfterClear(byte[][] initialContents, byte singleByte) { // Arrange var builder = new BinaryArray.Builder(); if (initialContents.Length > 0) builder.AppendRange(initialContents); builder.Clear(); var expectedArrayContents = new[] { new[] { singleByte } }; // Act var actualReturnValue = builder.Append(singleByte); // Assert Assert.Equal(builder, actualReturnValue); Assert.Equal(1, builder.Length); var actualArray = builder.Build(_allocator); AssertArrayContents(expectedArrayContents, actualArray); } public static readonly IEnumerable _appendNullTestData = from initialContents in _initialContentsSet select new object[] { initialContents }; [Theory] [MemberData(nameof(_appendNullTestData))] public void AppendNull(byte[][] initialContents) { // Arrange var builder = new BinaryArray.Builder(); if (initialContents.Length > 0) builder.AppendRange(initialContents); int initialLength = builder.Length; int expectedLength = initialLength + 1; var expectedArrayContents = initialContents.Append(null); // Act var actualReturnValue = builder.AppendNull(); // Assert Assert.Equal(builder, actualReturnValue); Assert.Equal(expectedLength, builder.Length); var actualArray = builder.Build(_allocator); AssertArrayContents(expectedArrayContents, actualArray); } [Theory] [MemberData(nameof(_appendNullTestData))] public void AppendNullAfterClear(byte[][] initialContents) { // Arrange var builder = new BinaryArray.Builder(); if (initialContents.Length > 0) builder.AppendRange(initialContents); builder.Clear(); var expectedArrayContents = new byte[][] { null }; // Act var actualReturnValue = builder.AppendNull(); // Assert Assert.Equal(builder, actualReturnValue); Assert.Equal(1, builder.Length); var actualArray = builder.Build(_allocator); AssertArrayContents(expectedArrayContents, actualArray); } public static readonly IEnumerable _appendNonNullByteArrayTestData = from initialContents in _initialContentsSet from bytes in _byteArraysToAppend where bytes != null select new object[] { initialContents, bytes }; [Theory] [MemberData(nameof(_appendNonNullByteArrayTestData))] public void AppendReadOnlySpan(byte[][] initialContents, byte[] bytes) { // Arrange var builder = new BinaryArray.Builder(); if (initialContents.Length > 0) builder.AppendRange(initialContents); int initialLength = builder.Length; var span = (ReadOnlySpan)bytes; int expectedLength = initialLength + 1; var expectedArrayContents = initialContents.Append(bytes); // Act var actualReturnValue = builder.Append(span); // Assert Assert.Equal(builder, actualReturnValue); Assert.Equal(expectedLength, builder.Length); var actualArray = builder.Build(_allocator); AssertArrayContents(expectedArrayContents, actualArray); } [Theory] [MemberData(nameof(_appendNonNullByteArrayTestData))] public void AppendReadOnlySpanAfterClear(byte[][] initialContents, byte[] bytes) { // Arrange var builder = new BinaryArray.Builder(); if (initialContents.Length > 0) builder.AppendRange(initialContents); builder.Clear(); var span = (ReadOnlySpan)bytes; var expectedArrayContents = new[] { bytes }; // Act var actualReturnValue = builder.Append(span); // Assert Assert.Equal(builder, actualReturnValue); Assert.Equal(1, builder.Length); var actualArray = builder.Build(_allocator); AssertArrayContents(expectedArrayContents, actualArray); } public static readonly IEnumerable _appendByteArrayTestData = from initialContents in _initialContentsSet from bytes in _byteArraysToAppend select new object[] { initialContents, bytes }; [Theory] [MemberData(nameof(_appendByteArrayTestData))] public void AppendEnumerable(byte[][] initialContents, byte[] bytes) { // Arrange var builder = new BinaryArray.Builder(); if (initialContents.Length > 0) builder.AppendRange(initialContents); int initialLength = builder.Length; int expectedLength = initialLength + 1; var enumerable = (IEnumerable)bytes; var expectedArrayContents = initialContents.Append(bytes); // Act var actualReturnValue = builder.Append(enumerable); // Assert Assert.Equal(builder, actualReturnValue); Assert.Equal(expectedLength, builder.Length); var actualArray = builder.Build(_allocator); AssertArrayContents(expectedArrayContents, actualArray); } [Theory] [MemberData(nameof(_appendByteArrayTestData))] public void AppendEnumerableAfterClear(byte[][] initialContents, byte[] bytes) { // Arrange var builder = new BinaryArray.Builder(); if (initialContents.Length > 0) builder.AppendRange(initialContents); builder.Clear(); var enumerable = (IEnumerable)bytes; var expectedArrayContents = new[] { bytes }; // Act var actualReturnValue = builder.Append(enumerable); // Assert Assert.Equal(builder, actualReturnValue); Assert.Equal(1, builder.Length); var actualArray = builder.Build(_allocator); AssertArrayContents(expectedArrayContents, actualArray); } } public class AppendRange { public static readonly IEnumerable _appendRangeSingleBytesTestData = from initialContents in _initialContentsSet select new object[] { initialContents, _singleBytesToAppend }; [Theory] [MemberData(nameof(_appendRangeSingleBytesTestData))] public void AppendRangeSingleBytes(byte[][] initialContents, byte[] singleBytes) { // Arrange var builder = new BinaryArray.Builder(); if (initialContents.Length > 0) builder.AppendRange(initialContents); int initialLength = builder.Length; int expectedNewLength = initialLength + singleBytes.Length; var expectedArrayContents = initialContents.Concat(singleBytes.Select(b => new[] { b })); // Act var actualReturnValue = builder.AppendRange(singleBytes); // Assert Assert.Equal(builder, actualReturnValue); Assert.Equal(expectedNewLength, builder.Length); var actualArray = builder.Build(_allocator); AssertArrayContents(expectedArrayContents, actualArray); } [Theory] [MemberData(nameof(_appendRangeSingleBytesTestData))] public void AppendRangeSingleBytesAfterClear(byte[][] initialContents, byte[] singleBytes) { // Arrange var builder = new BinaryArray.Builder(); if (initialContents.Length > 0) builder.AppendRange(initialContents); builder.Clear(); var expectedArrayContents = singleBytes.Select(b => new[] { b }); // Act var actualReturnValue = builder.AppendRange(singleBytes); // Assert Assert.Equal(builder, actualReturnValue); Assert.Equal(singleBytes.Length, builder.Length); var actualArray = builder.Build(_allocator); AssertArrayContents(expectedArrayContents, actualArray); } public static readonly IEnumerable _appendRangeByteArraysTestData = from initialContents in _initialContentsSet from byteArrays in _byteArrayArraysToAppend select new object[] { initialContents, byteArrays }; [Theory] [MemberData(nameof(_appendRangeByteArraysTestData))] public void AppendRangeArrays(byte[][] initialContents, byte[][] byteArrays) { // Arrange var builder = new BinaryArray.Builder(); if (initialContents.Length > 0) builder.AppendRange(initialContents); int initialLength = builder.Length; int expectedNewLength = initialLength + byteArrays.Length; var expectedArrayContents = initialContents.Concat(byteArrays); // Act var actualReturnValue = builder.AppendRange(byteArrays); // Assert Assert.Equal(builder, actualReturnValue); Assert.Equal(expectedNewLength, builder.Length); var actualArray = builder.Build(_allocator); AssertArrayContents(expectedArrayContents, actualArray); } [Theory] [MemberData(nameof(_appendRangeByteArraysTestData))] public void AppendRangeArraysAfterClear(byte[][] initialContents, byte[][] byteArrays) { // Arrange var builder = new BinaryArray.Builder(); if (initialContents.Length > 0) builder.AppendRange(initialContents); builder.Clear(); var expectedArrayContents = byteArrays; // Act var actualReturnValue = builder.AppendRange(byteArrays); // Assert Assert.Equal(builder, actualReturnValue); Assert.Equal(byteArrays.Length, builder.Length); var actualArray = builder.Build(_allocator); AssertArrayContents(expectedArrayContents, actualArray); } } public class Clear { [Fact] public void ClearEmpty() { // Arrange var builder = new BinaryArray.Builder(); // Act var actualReturnValue = builder.Clear(); // Assert Assert.NotNull(actualReturnValue); Assert.Equal(builder, actualReturnValue); Assert.Equal(0, builder.Length); var array = builder.Build(_allocator); Assert.Equal(0, array.Length); } public static readonly IEnumerable _testData = from byteArrays in _byteArrayArraysToAppend select new object[] { byteArrays }; [Theory] [MemberData(nameof(_testData))] public void ClearNonEmpty(byte[][] byteArrays) { // Arrange var builder = new BinaryArray.Builder(); builder.AppendRange(byteArrays); // Act var actualReturnValue = builder.Clear(); // Assert Assert.NotNull(actualReturnValue); Assert.Equal(builder, actualReturnValue); Assert.Equal(0, builder.Length); var array = builder.Build(_allocator); Assert.Equal(0, array.Length); } } public class Build { [Fact] public void BuildImmediately() { // Arrange var builder = new BinaryArray.Builder(); // Act var array = builder.Build(_allocator); // Assert Assert.Equal(0, array.Length); } public static readonly IEnumerable _testData = from ba1 in _initialContentsSet from ba2 in _byteArrayArraysToAppend select new object[] { ba1.Concat(ba2) }; [Theory] [MemberData(nameof(_testData))] public void AppendThenBuild(byte[][] byteArrays) { // Arrange var builder = new BinaryArray.Builder(); foreach (var byteArray in byteArrays) { // Test the type of byte array to ensure each Append() overload is exercised. if (byteArray == null) { builder.AppendNull(); } else if (byteArray.Length == 1) { builder.Append(byteArray[0]); } else { builder.Append((ReadOnlySpan)byteArray); } } // Act var array = builder.Build(_allocator); // Assert AssertArrayContents(byteArrays, array); } [Theory] [MemberData(nameof(_testData))] public void BuildMultipleTimes(byte[][] byteArrays) { // Arrange var builder = new BinaryArray.Builder(); builder.AppendRange(byteArrays); builder.Build(_allocator); // Act var array = builder.Build(_allocator); // Assert AssertArrayContents(byteArrays, array); } } private static void AssertArrayContents(IEnumerable expectedContents, BinaryArray array) { var expectedContentsArr = expectedContents.ToArray(); Assert.Equal(expectedContentsArr.Length, array.Length); for (int i = 0; i < array.Length; i++) { var expectedArray = expectedContentsArr[i]; var actualArray = array.IsNull(i) ? null : array.GetBytes(i).ToArray(); Assert.Equal(expectedArray, actualArray); } } } }