diff --git a/Recyclable.Collections.Compatibility.List.Tests/Recyclable.Collections.Compatibility.List.Tests.csproj b/Recyclable.Collections.Compatibility.List.Tests/Recyclable.Collections.Compatibility.List.Tests.csproj index fbc8c9b..a3e8f22 100644 --- a/Recyclable.Collections.Compatibility.List.Tests/Recyclable.Collections.Compatibility.List.Tests.csproj +++ b/Recyclable.Collections.Compatibility.List.Tests/Recyclable.Collections.Compatibility.List.Tests.csproj @@ -13,7 +13,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Recyclable.Collections.Compatibility.List.Tests/RecyclableLongListCompatibilityListTests.cs b/Recyclable.Collections.Compatibility.List.Tests/RecyclableLongListCompatibilityListTests.cs index cb219e0..ee518ea 100644 --- a/Recyclable.Collections.Compatibility.List.Tests/RecyclableLongListCompatibilityListTests.cs +++ b/Recyclable.Collections.Compatibility.List.Tests/RecyclableLongListCompatibilityListTests.cs @@ -35,8 +35,8 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] - public void BinarySearchShouldFindAllItemsWhenConstrainedRange(string testCase, IEnumerable testData, int itemsCount, int minBlockSize, in long[] itemIndexes) + [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexWithRangeVariants), MemberType = typeof(RecyclableLongListTestData))] + public void BinarySearchShouldFindAllItemsWhenConstrainedRange(string testCase, IEnumerable testData, int itemsCount, int minBlockSize, in (long ItemIndex, long RangedItemsCount)[] itemRanges) { _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); @@ -45,15 +45,13 @@ namespace Recyclable.CollectionsTests var comparer = Comparer.Default; var expectedData = testData.ToList(); - foreach (var itemIndex in itemIndexes) + foreach (var (itemIndex, rangedItemsCount) in itemRanges) { - var rangeSize = (int)Math.Min(itemsCount - itemIndex, 4); var expectedItem = expectedData[(int)itemIndex]; - var startItemIndex = (int)(rangeSize > 2 ? Math.Max(0, itemIndex - 2) : itemIndex); - var expected = expectedData.BinarySearch(startItemIndex, rangeSize, expectedItem, comparer); + var expected = expectedData.BinarySearch((int)itemIndex, (int)rangedItemsCount, expectedItem, comparer); // Act - var actual = list.BinarySearch(startItemIndex, rangeSize, expectedItem, comparer); + var actual = list.BinarySearch((int)itemIndex, (int)rangedItemsCount, expectedItem, comparer); // Validate _ = actual.Should().Be(expected); @@ -111,8 +109,8 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] - public void BinarySearchShouldNotFindNonExistingItemsWhenConstrainedRange(string testCase, IEnumerable testData, int itemsCount, int minBlockSize, in long[] itemIndexes) + [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexWithRangeVariants), MemberType = typeof(RecyclableLongListTestData))] + public void BinarySearchShouldNotFindNonExistingItemsWhenConstrainedRange(string testCase, IEnumerable testData, int itemsCount, int minBlockSize, in (long ItemIndex, long RangedItemsCount)[] itemRanges) { _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); @@ -120,20 +118,19 @@ namespace Recyclable.CollectionsTests var comparer = Comparer.Default; var expectedData = testData.ToList(); - foreach (var itemIndex in itemIndexes) + foreach (var (itemIndex, rangedItemsCount) in itemRanges) { using var list = new RecyclableLongList(testData, minBlockSize, initialCapacity: itemsCount); list.RemoveAt(itemIndex); var expectedItem = expectedData[(int)itemIndex]; var expectedRangedData = testData.ToList(); expectedRangedData.RemoveAt((int)itemIndex); + var expectedRangeSize = itemIndex + rangedItemsCount < expectedRangedData.Count ? rangedItemsCount : expectedRangedData.Count - itemIndex; // Now we have 1 element less on the lists - var rangeSize = (int)Math.Max(Math.Min(itemsCount - itemIndex - 2, 4), 0); - int startItemIndex = (int)(rangeSize > 2 ? Math.Max(0, itemIndex - 2 - 1) : Math.Max(0, itemIndex - 1)); - var expected = expectedRangedData.BinarySearch(startItemIndex, rangeSize, expectedItem, comparer); + var expected = expectedRangedData.BinarySearch((int)itemIndex, (int)expectedRangeSize, expectedItem, comparer); // Act - var actual = list.BinarySearch(startItemIndex, rangeSize, expectedItem, comparer); + var actual = list.BinarySearch((int)itemIndex, (int)expectedRangeSize, expectedItem, comparer); // Validate _ = actual.Should().Be(expected); @@ -212,6 +209,9 @@ namespace Recyclable.CollectionsTests Assert.Throws(() => _ = expectedData.BinarySearch((int)itemIndex + 1, itemsCount, expectedItem, comparer)); Assert.Throws(() => _ = list.BinarySearch((int)itemIndex + 1, itemsCount, expectedItem, comparer)); + + Assert.Throws(() => _ = expectedData.BinarySearch((int)itemIndex + 1, -1, expectedItem, comparer)); + Assert.Throws(() => _ = list.BinarySearch((int)itemIndex + 1, -1, expectedItem, comparer)); } } @@ -232,32 +232,41 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] - public void CopyToShouldCopyAllItemsInTheCorrectOrderWhenConstrainedRange(string testCase, IEnumerable testData, int itemsCount, int minBlockSize, in long[] itemIndexes) + [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexWithRangeVariants), MemberType = typeof(RecyclableLongListTestData))] + public void CopyToShouldCopyAllItemsInTheCorrectOrderWhenConstrainedIndex(string testCase, IEnumerable testData, int itemsCount, int minBlockSize, in (long ItemIndex, long RangedItemsCount)[] itemIndexRanges) { // Prepare var expectedData = testData.ToList(); - foreach (var itemIndex in itemIndexes) + foreach (var (itemIndex, _) in itemIndexRanges) { - var rangedItemsCount = (int)Math.Max(itemIndex, 1); - TestCopyTo(0, rangedItemsCount); + using var list = new RecyclableLongList(testData, minBlockSize, initialCapacity: itemsCount); + long[] expectedItems = new long[expectedData.Count]; + expectedData.CopyTo((int)itemIndex, expectedItems, (int)itemIndex, (int)(expectedData.Count - itemIndex)); + long[] actualItems = new long[expectedData.Count]; - rangedItemsCount = (int)(itemsCount - itemIndex); - TestCopyTo((int)itemIndex, rangedItemsCount); + // Act + list.CopyTo((int)itemIndex, actualItems, (int)itemIndex); - rangedItemsCount = 1; - TestCopyTo((int)itemIndex, rangedItemsCount); + // Validate + _ = actualItems.Should().Equal(expectedItems); } + } - void TestCopyTo(int itemIndex, int rangedItemsCount) + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexWithRangeVariants), MemberType = typeof(RecyclableLongListTestData))] + public void CopyToShouldCopyAllItemsInTheCorrectOrderWhenConstrainedRange(string testCase, IEnumerable testData, int itemsCount, int minBlockSize, in (long ItemIndex, long RangedItemsCount)[] itemIndexRanges) + { + // Prepare + var expectedData = testData.ToList(); + foreach (var (itemIndex, rangedItemsCount) in itemIndexRanges) { using var list = new RecyclableLongList(testData, minBlockSize, initialCapacity: itemsCount); long[] expectedItems = new long[rangedItemsCount + itemIndex]; - expectedData.CopyTo(itemIndex, expectedItems, itemIndex, rangedItemsCount); + expectedData.CopyTo((int)itemIndex, expectedItems, (int)itemIndex, (int)rangedItemsCount); long[] actualItems = new long[rangedItemsCount + itemIndex]; // Act - list.CopyTo(itemIndex, actualItems, itemIndex, rangedItemsCount); + list.CopyTo((int)itemIndex, actualItems, (int)itemIndex, (int)rangedItemsCount); // Validate _ = actualItems.Should().Equal(expectedItems); @@ -282,6 +291,36 @@ namespace Recyclable.CollectionsTests _ = copiedItems.Should().Equal(testData); } + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] + public void CopyToShouldThrowArgumentOutOfRangeWhenOutsideRange(string testCase, IEnumerable testData, int itemsCount, int minBlockSize, in long[] itemIndexes) + { + _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); + + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize, initialCapacity: itemsCount); + var comparer = Comparer.Default; + var expectedData = testData.ToList(); + var actual = new long[itemsCount]; + + foreach (var itemIndex in itemIndexes) + { + + // Act && Validate + Assert.Throws(() => expectedData.CopyTo(-1, actual, 0, (int)itemIndex)); + Assert.Throws(() => list.CopyTo(-1, actual, 0, (int)itemIndex)); + + Assert.Throws(() => expectedData.CopyTo(0, actual, -1, (int)itemIndex)); + Assert.Throws(() => list.CopyTo(0, actual, -1, (int)itemIndex)); + + Assert.Throws(() => expectedData.CopyTo(0, actual, (int)itemIndex + 1, itemsCount)); + Assert.Throws(() => list.CopyTo(0, actual, (int)itemIndex + 1, itemsCount)); + + Assert.Throws(() => expectedData.CopyTo(0, actual, (int)itemIndex + 1, -1)); + Assert.Throws(() => list.CopyTo(0, actual, (int)itemIndex + 1, -1)); + } + } + [Theory] [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] public void ExistsShouldFindAllItems(string testCase, IEnumerable testData, int itemsCount, int minBlockSize, in long[] itemIndexes) diff --git a/Recyclable.Collections.Compatibility.List/RecyclableLongList.Compatibility.List.cs b/Recyclable.Collections.Compatibility.List/RecyclableLongList.Compatibility.List.cs index 18d7548..ee8711f 100644 --- a/Recyclable.Collections.Compatibility.List/RecyclableLongList.Compatibility.List.cs +++ b/Recyclable.Collections.Compatibility.List/RecyclableLongList.Compatibility.List.cs @@ -302,8 +302,36 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] //public static void CopyTo(this RecyclableLongList list, int index, T[] array, int arrayIndex, int count) => RecyclableLongList.Helpers.CopyTo(list._memoryBlocks, index, list._blockSize, count, array, arrayIndex); - public static void CopyTo(this RecyclableLongList list, int index, T[] array, int arrayIndex, int count) => DoCopyTo(list._memoryBlocks, index, list._blockSize, count, array, arrayIndex); + public static void CopyTo(this RecyclableLongList list, int index, T[] array, int arrayIndex) => DoCopyTo(list._memoryBlocks, index, list._blockSize, list._longCount - index, array, arrayIndex); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void CopyTo(this RecyclableLongList list, int index, T[] array, int arrayIndex, int count) + { + + if (index < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException_Index(); + } + + if (count < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException_Count(); + } + + if (arrayIndex < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(arrayIndex), "Parameter `arrayIndex` has invalid value. It must be within the no. of elements in the collection"); + } + + if (index + count > list._longCount || arrayIndex + count > list._longCount) + { + Compatibility.List.ThrowHelper.ThrowArgumentException_Count(); + } + + DoCopyTo(list._memoryBlocks, index, list._blockSize, count, array, arrayIndex); + + } + public static void ForEach(this RecyclableLongList list, Action action) { //int sourceItemsCount = list._count; diff --git a/Recyclable.Collections.Compatibility.List/ThrowHelper.cs b/Recyclable.Collections.Compatibility.List/ThrowHelper.cs new file mode 100644 index 0000000..a8d70fc --- /dev/null +++ b/Recyclable.Collections.Compatibility.List/ThrowHelper.cs @@ -0,0 +1,25 @@ +#pragma warning disable IDE0022, CA2208 + +namespace Recyclable.Collections.Compatibility.List +{ + [Obsolete("This class is temporary until new Recyclable.Collections is released")] + internal static class ThrowHelper + { + public static void ThrowArgumentException(string argumentName) => throw new ArgumentException("Parameter has invalid value. ", argumentName); + public static void ThrowArgumentException(string argumentName, Exception? innerException) => throw new ArgumentException("Parameter has invalid value. ", argumentName, innerException); + public static void ThrowArgumentException(string argumentName, string message) => throw new ArgumentException(message, argumentName); + public static void ThrowArgumentException(string argumentName, string message, Exception? innerException) => throw new ArgumentException(message, argumentName, innerException); + public static void ThrowArgumentException_Index() => throw new ArgumentException("Parameter has invalid value", "index"); + public static void ThrowArgumentException_Index(Exception? innerException) => throw new ArgumentException("Parameter has invalid value", "index", innerException); + public static void ThrowArgumentException_Count() => throw new ArgumentException("Parameter has invalid value", "count"); + public static void ThrowArgumentException_Count(Exception? innerException) => throw new ArgumentException("Parameter has invalid value", "count", innerException); + public static void ThrowArgumentOutOfRangeException(string argumentName) => throw new ArgumentOutOfRangeException(argumentName); + public static void ThrowArgumentOutOfRangeException(string argumentName, Exception? innerException) => throw new ArgumentOutOfRangeException($"Argument {argumentName} is out of range", innerException); + public static void ThrowArgumentOutOfRangeException(string argumentName, string message) => throw new ArgumentOutOfRangeException(argumentName, message); + public static void ThrowWrongReturnedArraySizeException(int returnedLength, int blockSize) => throw new InvalidOperationException($"The array size {returnedLength} doesn't match the size {blockSize} of the pool buffer and cannot be returned to this pool."); + public static void ThrowArgumentOutOfRangeException_Index() => throw new ArgumentOutOfRangeException("index", "Parameter `index` has invalid value. It must be within the no. of elements in the collection"); + public static void ThrowArgumentOutOfRangeException_Index(Exception? innerException) => throw new ArgumentOutOfRangeException("Parameter `index` has invalid value. It must be within the no. of elements in the collection", innerException); + public static void ThrowArgumentOutOfRangeException_Count() => throw new ArgumentOutOfRangeException("count", "Parameter `count` has invalid value. Combined with index, it must be within the no. of elements in the collection"); + public static void ThrowArgumentOutOfRangeException_Count(Exception? innerException) => throw new ArgumentOutOfRangeException("Parameter `count` has invalid value. Combined with index, it must be within the no. of elements in the collection", innerException); + } +}