Unit tests extended. Various fixes. README.md updated.

This commit is contained in:
mlemanczyk committed 2023-09-09 18:19:28 +02:00
1 parent 98347f0727
commit e941fdf8db
8 files changed
+779 -202

No files matched your search

+12 -3
View File
@@ -120,6 +120,12 @@
1. ✅ `InsertAt` to allow inserting items anywhere
1. ✅ `RemoveAt` to allow removing item at any index
1. ✅ Release 0.0.4
1. ✅ Extend unit tests
1. ✅ `.Add` / `.AddRange` must accept `null` values
1. ✅ `.AddRange` must accept empty collections
1. ✅ `.Remove` / `.RemoveAt` / `.Clear` / `.Dispose` must clear reference when reference type
1. ✅ `.Clear` / `.Dispose` must return arrays to pools
1. ✅ `int` / `long` variants of `.Count` / `.LongCount`, `.IndexOf` / `.LongIndexOf`, `.Insert`, `.RemoveAt`, `this[]`
1. 👉 Add `.ToRecyclableList` / `.ToRecyclableLongList` variants for all supported collection types
1. 🅿️ `RecyclableList`
1. 🅿️ `RecyclableLongList`
@@ -187,9 +193,9 @@
1. 🅿️ Multi-threading benchmarks
1. 🅿️ Release 0.0.13
1. 🅿️ Extend unit tests
1. 🅿️ `.Add` / `.AddRange` must allow `null` values
1. 🅿️ `.Remove` / `.RemoveAt` / `.Clear` must clear reference when reference type
1. 🅿️ `RecyclableLongListExtensions.CopyTo`
1. 🅿️ Pools
1. 🅿️ Multi-threading
1. 🅿️ Cleanup
1. 🅿️ Replace `LastBlockWithData` property with `_lastBlockWithData` field
1. 🅿️ Replace properties with field refs, where possible
@@ -198,13 +204,16 @@
1. 🅿️ `MathUtils`
1. 🅿️ Resolve TODOs
1. 🅿️ Remove obsolete methods
1. 🅿️ `RecyclableLongListHelpers.Enumerate` to benchmarks
1. 🅿️ Add`RecyclableLongListHelpers.Enumerate` to benchmarks
1. 🅿️ Review all classes & add `volatile`, where needed
1. 🅿️ Remove `expectedItemsCount` parameter from `RecyclableLongList`'s constructor
1. 🅿️ Release 0.0.14
1. 🅿️ Optimize
1. 🅿️ `ListExtensions`
1. 🅿️ `MathUtils`
1. 🅿️ `SystemRandomNumberGenerator`
1. 🅿️ `RecyclableLongListExtensions`
1. 🅿️ Memory release in pools to utilize GC events in case of memory pressure
1. 🅿️ Release 0.0.15
1. ✅ Review and remove warnings & hints
1. ✅ Warnings
+3 -8
View File
@@ -272,11 +272,6 @@ namespace Recyclable.Collections
int oldCount = _count;
long sourceItemsCount = items._longCount;
if (sourceItemsCount > int.MaxValue)
{
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(items), $"The number of items exceeds the maximum capacity of {nameof(RecyclableList<T>)}, equal {int.MaxValue}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList<T>)}, instead");
}
if (oldCount + sourceItemsCount > int.MaxValue)
{
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(items), $"The total number of items in source and target table exceeds the maximum capacity of {nameof(RecyclableList<T>)}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList<T>)}, instead");
@@ -337,7 +332,7 @@ namespace Recyclable.Collections
{
AddRange(sourceIList);
}
else if (items.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity))
else if (items.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity) && requiredAdditionalCapacity != 0)
{
AddRangeWithKnownCount(items, _count, requiredAdditionalCapacity);
_version++;
@@ -388,7 +383,7 @@ namespace Recyclable.Collections
if (index < oldCount)
{
new Span<T>(_memoryBlock, index, oldCount)
new Span<T>(_memoryBlock, index, oldCount -= index)
.CopyTo(new Span<T>(_memoryBlock, index + 1, oldCount));
}
@@ -468,11 +463,11 @@ namespace Recyclable.Collections
if (_capacity >= RecyclableDefaults.MinPooledArrayLength)
{
_capacity = 0;
// If anything, it has been already cleared by .Clear(), Remove() or RemoveAt() methods, as the list was modified / disposed.
RecyclableArrayPool<T>.ReturnShared(_memoryBlock, false);
}
_capacity = 0;
GC.SuppressFinalize(this);
}
}
@@ -26,7 +26,7 @@ namespace Recyclable.Collections
{
var list = _list;
if (_currentBlockIndex < list._lastBlockWithData)
if (_currentBlockIndex <= list._lastBlockWithData && _currentItemIndex <= (_currentBlockIndex == list._lastBlockWithData && list._nextItemIndex > 0 ? list._nextItemIndex - 1: list._blockSizeMinus1))
{
_current = list._memoryBlocks[_currentBlockIndex][_currentItemIndex];
if (_currentItemIndex + 1 < list._blockSize)
@@ -42,12 +42,6 @@ namespace Recyclable.Collections
return true;
}
// Move to the next item if we're within block boundaries, otherwise report end of collection.
else if (list._longCount > 0 && _currentItemIndex < (list._nextItemIndex > 0 ? list._nextItemIndex : list._blockSize))
{
_current = list._memoryBlocks[_currentBlockIndex][_currentItemIndex];
_currentItemIndex++;
return true;
}
return false;
}
@@ -11,24 +11,21 @@ namespace Recyclable.Collections
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static void CopyFollowingItems(RecyclableLongList<T> list, long destinationItemIndex)
{
T[] currentBlock;
T[][] memoryBlocks = list._memoryBlocks;
int blockSizeMinus1 = list._blockSizeMinus1,
targetItemIndex = (int)(destinationItemIndex & blockSizeMinus1),
targetBlockIndex = (int)(destinationItemIndex >> list._blockSizePow2BitShift) + (targetItemIndex != 0 ? 1 : 0),
targetBlockIndex = (int)(destinationItemIndex >> list._blockSizePow2BitShift) + (targetItemIndex + 1 == 0 ? 1 : 0),
lastTakenBlockIndex = list._lastBlockWithData - 1;
// Each iteration should
// 1. copy the remaining items in the current block (those after the deleted item)
// 2. copy the 1st item from the next block. We deleted 1 item which leaves 1 item gap in the block.
// We need to fill it.
currentBlock = memoryBlocks[targetBlockIndex];
if (targetItemIndex < blockSizeMinus1)
{
new Span<T>(currentBlock, targetItemIndex + 1, blockSizeMinus1 - targetItemIndex)
.CopyTo(new Span<T>(currentBlock, targetItemIndex, blockSizeMinus1 - targetItemIndex));
}
T[][] memoryBlocks = list._memoryBlocks;
T[] currentBlock = memoryBlocks[targetBlockIndex];
new Span<T>(currentBlock, targetItemIndex + 1, blockSizeMinus1 - targetItemIndex)
.CopyTo(new Span<T>(currentBlock, targetItemIndex, blockSizeMinus1 - targetItemIndex));
while (targetBlockIndex < lastTakenBlockIndex)
{
new Span<T>(currentBlock)[blockSizeMinus1] = (currentBlock = memoryBlocks[++targetBlockIndex])[0];
@@ -36,7 +33,7 @@ namespace Recyclable.Collections
.CopyTo(new Span<T>(currentBlock));
}
if (lastTakenBlockIndex >= 0)
if (targetBlockIndex == lastTakenBlockIndex)
{
new Span<T>(currentBlock)[blockSizeMinus1] = (currentBlock = memoryBlocks[targetBlockIndex + 1])[0];
new Span<T>(currentBlock, 1, list._nextItemIndex != 0 ? list._nextItemIndex - 1 : blockSizeMinus1)
@@ -51,7 +48,7 @@ namespace Recyclable.Collections
T[][] memoryBlocks = list._memoryBlocks;
int blockSizeMinus1 = list._blockSizeMinus1,
targetItemIndex = (int)(startingItemIndex & blockSizeMinus1),
targetBlockIndex = (int)(startingItemIndex >> list._blockSizePow2BitShift) + (targetItemIndex != 0 ? 1 : 0),
targetBlockIndex = (int)(startingItemIndex >> list._blockSizePow2BitShift) + (targetItemIndex + 1 == 0 ? 1 : 0),
lastTakenBlockIndex = list._lastBlockWithData;
if (list._nextItemIndex == 0)
@@ -67,11 +64,8 @@ namespace Recyclable.Collections
new Span<T>(currentBlock)[0] = memoryBlocks[currentBlockIndex][blockSizeMinus1];
}
if (lastTakenBlockIndex >= 0)
{
new Span<T>(currentBlock = memoryBlocks[targetBlockIndex], targetItemIndex, blockSizeMinus1 - targetItemIndex)
.CopyTo(new Span<T>(currentBlock, targetItemIndex + 1, blockSizeMinus1 - targetItemIndex));
}
new Span<T>(currentBlock = memoryBlocks[targetBlockIndex], targetItemIndex, blockSizeMinus1 - targetItemIndex)
.CopyTo(new Span<T>(currentBlock, targetItemIndex + 1, blockSizeMinus1 - targetItemIndex));
new Span<T>(memoryBlocks[targetBlockIndex])[targetItemIndex] = item;
}
+95 -40
View File
@@ -341,17 +341,14 @@ namespace Recyclable.Collections
public RecyclableLongList(IEnumerable<T> source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default)
{
minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize));
Helpers.SetupBlockArrayPooling(this, minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)));
if (expectedItemsCount > 0)
{
Helpers.SetupBlockArrayPooling(this, minBlockSize);
_capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((int)BitOperations.RoundUpToPowerOf2((uint)expectedItemsCount.Value)));
_blockSizeMinus1 = minBlockSize - 1;
}
else
{
Helpers.SetupBlockArrayPooling(this, minBlockSize);
_blockSizeMinus1 = _blockSize - 1;
_memoryBlocks = _emptyMemoryBlocksArray;
}
@@ -486,14 +483,22 @@ namespace Recyclable.Collections
if (itemsSpan.Length > 0)
{
itemsSpan.CopyTo(targetBlockArraySpan);
_nextItemBlockIndex = targetBlockIndex;
if (_nextItemBlockIndex != targetBlockIndex)
{
_nextItemIndex = itemsSpan.Length;
_nextItemBlockIndex = targetBlockIndex;
}
else
{
_nextItemIndex += itemsSpan.Length;
}
}
else
{
_nextItemBlockIndex = targetBlockIndex + 1;
_nextItemIndex = 0;
}
_nextItemIndex = itemsSpan.Length;
_longCount = targetCapacity;
_lastBlockWithData = targetBlockIndex;
_version++;
@@ -532,22 +537,38 @@ namespace Recyclable.Collections
targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize);
}
_nextItemIndex = itemsSpan.Length;
itemsSpan.CopyTo(targetBlockArraySpan);
if (itemsSpan.Length > 0)
{
itemsSpan.CopyTo(targetBlockArraySpan);
if (_nextItemBlockIndex != targetBlockIndex)
{
_nextItemIndex = itemsSpan.Length;
_nextItemBlockIndex = targetBlockIndex;
}
else
{
_nextItemIndex += itemsSpan.Length;
}
}
else
{
_nextItemBlockIndex = targetBlockIndex;
_nextItemIndex = 0;
}
_longCount = targetCapacity;
_nextItemBlockIndex = targetBlockIndex;
_lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1);
_version++;
}
public void AddRange(ReadOnlySpan<T> itemsSpan)
public void AddRange(ReadOnlySpan<T> items)
{
if (itemsSpan.Length == 0)
if (items.Length == 0)
{
return;
}
long targetCapacity = _longCount + itemsSpan.Length;
long targetCapacity = _longCount + items.Length;
if (_capacity < targetCapacity)
{
_capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity)));
@@ -559,11 +580,11 @@ namespace Recyclable.Collections
Span<T[]> memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount);
Span<T> targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex);
while (itemsSpan.Length >= targetBlockArraySpan.Length)
while (items.Length >= targetBlockArraySpan.Length)
{
itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan);
itemsSpan = itemsSpan[targetBlockArraySpan.Length..];
if (itemsSpan.Length == 0)
items[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan);
items = items[targetBlockArraySpan.Length..];
if (items.Length == 0)
{
break;
}
@@ -572,17 +593,25 @@ namespace Recyclable.Collections
targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize);
}
if (itemsSpan.Length > 0)
if (items.Length > 0)
{
itemsSpan.CopyTo(targetBlockArraySpan);
_nextItemBlockIndex = targetBlockIndex;
items.CopyTo(targetBlockArraySpan);
if (_nextItemBlockIndex != targetBlockIndex)
{
_nextItemIndex = items.Length;
_nextItemBlockIndex = targetBlockIndex;
}
else
{
_nextItemIndex += items.Length;
}
}
else
{
_nextItemBlockIndex = targetBlockIndex + 1;
_nextItemIndex = 0;
}
_nextItemIndex = itemsSpan.Length;
_longCount = targetCapacity;
_lastBlockWithData = targetBlockIndex;
_version++;
@@ -749,10 +778,26 @@ namespace Recyclable.Collections
targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize);
}
itemsSpan.CopyTo(targetBlockArraySpan);
if (itemsSpan.Length > 0)
{
itemsSpan.CopyTo(targetBlockArraySpan);
if (_nextItemBlockIndex != targetBlockIndex)
{
_nextItemIndex = itemsSpan.Length;
_nextItemBlockIndex = targetBlockIndex;
}
else
{
_nextItemIndex += itemsSpan.Length;
}
}
else
{
_nextItemIndex = 0;
}
_longCount = targetCapacity;
_nextItemBlockIndex = targetBlockIndex;
_nextItemIndex = itemsSpan.Length;
_lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1);
}
finally
@@ -766,36 +811,36 @@ namespace Recyclable.Collections
}
}
public void AddRange(IEnumerable<T> source)
public void AddRange(IEnumerable<T> items)
{
if (source is RecyclableLongList<T> sourceRecyclableLongList)
if (items is RecyclableLongList<T> sourceRecyclableLongList)
{
AddRange(sourceRecyclableLongList);
}
else if (source is RecyclableList<T> sourceRecyclableList)
else if (items is RecyclableList<T> sourceRecyclableList)
{
AddRange(sourceRecyclableList);
}
else if (source is T[] sourceArray)
else if (items is T[] sourceArray)
{
AddRange(sourceArray);
}
else if (source is List<T> sourceList)
else if (items is List<T> sourceList)
{
AddRange(sourceList);
}
else if (source is IList<T> sourceIList)
else if (items is IList<T> sourceIList)
{
AddRange(sourceIList);
}
else if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity))
else if (items.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity))
{
AddRangeWithKnownCount(source, requiredAdditionalCapacity);
AddRangeWithKnownCount(items, requiredAdditionalCapacity);
_version++;
}
else
{
AddRangeEnumerated(source);
AddRangeEnumerated(items);
_version++;
}
}
@@ -807,16 +852,24 @@ namespace Recyclable.Collections
return;
}
T[][] memoryBlocks = _memoryBlocks;
int memoryBlocksCount = _reservedBlockCount;
int toRemoveIdx = 0;
if (_blockSize >= RecyclableDefaults.MinPooledArrayLength)
{
T[][] memoryBlocks = _memoryBlocks;
int memoryBlocksCount = _reservedBlockCount;
int toRemoveIdx = 0;
while (toRemoveIdx < memoryBlocksCount)
{
RecyclableArrayPool<T>.ReturnShared(memoryBlocks[toRemoveIdx++], _needsClearing);
}
}
else if (_needsClearing)
{
while (toRemoveIdx < memoryBlocksCount)
{
Array.Clear(memoryBlocks[toRemoveIdx++]);
}
}
// TODO: Do we want to initialize with empty arrays or null refs? Null refs may be faster.
Array.Fill(_memoryBlocks, _emptyBlockArray, 0, _reservedBlockCount);
@@ -954,17 +1007,19 @@ namespace Recyclable.Collections
new Span<T>(_memoryBlocks[_nextItemBlockIndex])[_nextItemIndex] = item;
}
if (_nextItemIndex < _blockSizeMinus1)
if (++_nextItemIndex == _blockSize)
{
if (++_nextItemIndex == 1)
if (_nextItemBlockIndex > _lastBlockWithData)
{
_lastBlockWithData++;
}
}
else
{
_nextItemIndex = 0;
_nextItemBlockIndex++;
_nextItemIndex = 0;
}
else if (_nextItemIndex == 1 && _nextItemBlockIndex > _lastBlockWithData)
{
_lastBlockWithData++;
}
_version++;
@@ -8,15 +8,32 @@ namespace Recyclable.CollectionsTests
{
public class RecyclableListTests
{
[Fact]
public void AddRangeShouldAcceptNulls()
{
// Prepare
using var list = new RecyclableList<long?>();
// Act
list.AddRange(new long?[] { null, default });
// Validate
_ = list.Should().HaveCount(2).And.AllSatisfy(x => x.Should().BeNull());
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable<long> testData, long itemsCount)
public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Prepare
using var list = new RecyclableList<long>();
// Act
if (testData is long[] testDataArray)
if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase))
{
list.AddRange(new ReadOnlySpan<long>((long[])testData));
}
else if (testData is long[] testDataArray)
{
list.AddRange(testDataArray);
}
@@ -46,20 +63,24 @@ namespace Recyclable.CollectionsTests
}
// Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount, "when capacity == 0, then we should allocate as much memory as needed, only");
_ = list.Count.Should().Be((int)itemsCount);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.Count.Should().Be(itemsCount);
_ = list.Should().Equal(testData);
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.EmptySourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void AddRangeShouldDoNothingWhenSourceIsEmpty(string testCase, IEnumerable<long> testData, long itemsCount)
public void AddRangeShouldDoNothingWhenSourceIsEmpty(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Prepare
using var list = new RecyclableList<long>();
// Act
if (testData is long[] testDataArray)
if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase))
{
list.AddRange(new ReadOnlySpan<long>((long[])testData));
}
else if (testData is long[] testDataArray)
{
list.AddRange(testDataArray);
}
@@ -90,19 +111,25 @@ namespace Recyclable.CollectionsTests
// Validate
_ = list.Capacity.Should().Be(RecyclableDefaults.InitialCapacity);
_ = list.Count.Should().Be(0);
_ = list.Count.Should().Be(itemsCount);
_ = list.Should().BeEmpty();
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void AddRangeShouldNotOverrideItems(string testCase, IEnumerable<long> testData, long itemsCount)
public void AddRangeShouldNotOverrideItems(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Prepare
using var list = new RecyclableList<long>();
// Act
if (testData is long[] testDataArray)
if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase))
{
var readOnlySpan = new ReadOnlySpan<long>((long[])testData);
list.AddRange(readOnlySpan);
list.AddRange(readOnlySpan);
}
else if (testData is long[] testDataArray)
{
list.AddRange(testDataArray);
list.AddRange(testDataArray);
@@ -138,23 +165,18 @@ namespace Recyclable.CollectionsTests
}
// Validate
var expectedData = testData.Concat(testData).ToArray();
if (itemsCount > 0)
{
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount * 2);
}
_ = list.Count.Should().Be(expectedData.Length);
var expectedData = testData.Concat(testData);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount * 2);
_ = list.Count.Should().Be(itemsCount * 2);
_ = list.Should().Equal(expectedData);
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void AddShouldAcceptDuplicates(string testCase, IEnumerable<long> testData, long itemsCount)
public void AddShouldAcceptDuplicates(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Prepare
testData = testData.Concat(testData).ToArray();
testData = testData.Concat(testData);
// Act
using var list = new RecyclableList<long>();
@@ -164,13 +186,31 @@ namespace Recyclable.CollectionsTests
}
// Validate
_ = list.Count.Should().Be((int)(itemsCount * 2));
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount * 2);
_ = list.Count.Should().Be(itemsCount * 2);
_ = list.Should().Equal(testData);
}
[Fact]
public void AddShouldAcceptNulls()
{
// Prepare
#pragma warning disable IDE0028
using var list = new RecyclableList<long?>();
// Act
list.Add(null);
list.Add(default);
#pragma warning restore IDE0028
// Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(RecyclableDefaults.InitialCapacity);
_ = list.Should().AllSatisfy(x => x.Should().BeNull());
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void AddShouldAddItems(string testCase, IEnumerable<long> testData, long itemsCount)
public void AddShouldAddItems(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Prepare
using var list = new RecyclableList<long>();
@@ -183,14 +223,14 @@ namespace Recyclable.CollectionsTests
}
// Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount);
_ = list.Count.Should().Be((int)itemsCount);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.Count.Should().Be(itemsCount);
_ = list.Should().Equal(testData);
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void AddShouldAddItemWhenAfterAddRange(string testCase, IEnumerable<long> testData, long itemsCount)
public void AddShouldAddItemWhenAfterAddRange(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Act
using var list = new RecyclableList<long>();
@@ -201,34 +241,56 @@ namespace Recyclable.CollectionsTests
}
// Validate
_ = list.Count.Should().Be((int)(itemsCount * 2));
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount * 2);
_ = list.Count.Should().Be(itemsCount * 2);
_ = list.Should().Equal(testData.Concat(testData));
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceRefDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void ClearShouldClearItemsWhenReferenceType(string testCase, IEnumerable<object> testData, int itemsCount)
{
if (itemsCount == 0)
{
/// Clearing empty list is tested in <see cref="ClearShouldSucceed"/>.
return;
}
// Arrange
using var list = new RecyclableList<object>(testData, itemsCount);
_ = list.AsArray[..itemsCount].Should().AllSatisfy(x => x.Should().NotBeNull());
// Act
list.Clear();
// Assert
_ = list.AsArray[..itemsCount].Should().AllSatisfy(x => x.Should().BeNull());
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void ClearShouldRemoveAllItems(string testCase, IEnumerable<long> testData, long itemsCount)
public void ClearShouldRemoveAllItems(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount);
_ = list.Count.Should().Be((int)itemsCount);
using var list = new RecyclableList<long>(testData, itemsCount);
_ = list.Count.Should().Be(itemsCount);
// Act
list.Clear();
list.Clear();
// Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.Count.Should().Be(0);
_ = list.Should().BeEmpty();
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void ConsecutiveDisposeShouldSucceed(string testCase, IEnumerable<long> testData, long itemsCount)
public void ConsecutiveDisposeShouldSucceed(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount);
using var list = new RecyclableList<long>(testData, itemsCount);
list.Dispose();
// Act
@@ -237,41 +299,45 @@ namespace Recyclable.CollectionsTests
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void ConstructorShouldAcceptDuplicates(string testCase, IEnumerable<long> testData, long itemsCount)
public void ConstructorShouldAcceptDuplicates(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Prepare
testData = testData.Concat(testData);
// Act
using var list = new RecyclableList<long>(testData.Concat(testData), (int)itemsCount * 2);
using var list = new RecyclableList<long>(testData, itemsCount * 2);
// Validate
_ = list.Count.Should().Be((int)itemsCount * 2);
_ = list.Should().Equal(testData.Concat(testData));
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount * 2);
_ = list.Count.Should().Be(itemsCount * 2);
_ = list.Should().Equal(testData);
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void ConstructorSourceShouldInitializeList(string testCase, IEnumerable<long> testData, long itemsCount)
public void ConstructorSourceShouldInitializeList(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Act
using var list = new RecyclableList<long>(testData, (int)itemsCount);
using var list = new RecyclableList<long>(testData, itemsCount);
// Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount, "when we succeed in taking count without enumerating, then we allocate as much memory as needed, only");
_ = list.Count.Should().Be((int)itemsCount);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.Count.Should().Be(itemsCount);
_ = list.Should().Equal(testData);
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void ContainsShouldFindAllItems(string testCase, IEnumerable<long> testData, long itemsCount, in long[] itemIndexes)
public void ContainsShouldFindAllItems(string testCase, IEnumerable<long> testData, int itemsCount, in long[] itemIndexes)
{
// Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount);
using var list = new RecyclableList<long>(testData, itemsCount);
_ = testData.Any().Should().BeTrue("we need items on the list that we can look for");
// Act & Validate
foreach (var itemIndex in itemIndexes)
{
_ = list.Contains(list[(int)itemIndex]).Should().BeTrue();
_ = list.Contains(list[(int)itemIndex]).Should().BeTrue($"we searched for {list[(int)itemIndex]}");
}
}
@@ -288,10 +354,10 @@ namespace Recyclable.CollectionsTests
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void ContainsShouldNotFindNonExistingItems(string testCase, IEnumerable<long> testData, long itemsCount, in long[] itemIndexes)
public void ContainsShouldNotFindNonExistingItems(string testCase, IEnumerable<long> testData, int itemsCount, in long[] itemIndexes)
{
// Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount);
using var list = new RecyclableList<long>(testData, itemsCount);
_ = testData.Any().Should().BeTrue("we need items on the list that we can look for");
// Act & Validate
@@ -303,10 +369,10 @@ namespace Recyclable.CollectionsTests
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void CopyToShouldCopyAllItemsInTheCorrectOrder(string testCase, IEnumerable<long> testData, long itemsCount)
public void CopyToShouldCopyAllItemsInTheCorrectOrder(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount);
using var list = new RecyclableList<long>(testData, itemsCount);
long[] copiedItems = new long[itemsCount];
// Act
@@ -317,14 +383,72 @@ namespace Recyclable.CollectionsTests
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void EnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable<long> testData, long itemsCount)
[MemberData(nameof(RecyclableLongListTestData.SourceRefDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void DisposeShouldClearItemsWhenReferenceType(string testCase, IEnumerable<object> testData, int itemsCount)
{
// Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount);
if (itemsCount == 0)
{
/// Disposing empty list is tested in <see cref="DisposeShouldSucceed"/>.
return;
}
// Arrange
using var list = new RecyclableList<object>(testData, itemsCount);
var listArray = list.AsArray;
_ = list.AsArray[..itemsCount].Should().NotBeNull();
_ = listArray[..itemsCount].Should().AllSatisfy(x => x.Should().NotBeNull());
// Act
var yieldedItems = new List<long>((int)itemsCount);
list.Dispose();
// Assert
_ = listArray[..itemsCount].Should().AllSatisfy(x => x.Should().BeNull());
}
[Fact]
public void DisposeShouldReturnPooledArrayOnlyOnce()
{
// Arrange
var testData = RecyclableLongListTestData.CreateTestData(RecyclableDefaults.MinPooledArrayLength);
RecyclableList<long> firstInstance = new(testData);
var firstArray = firstInstance.AsArray;
// Act
firstInstance.Dispose();
// Assert
using RecyclableList<long> sameInstance = new(testData);
_ = sameInstance.AsArray.Should().BeSameAs(firstArray);
using RecyclableList<long> anotherInstance = new(testData);
_ = anotherInstance.AsArray.Should().NotBeSameAs(firstArray);
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void DisposeShouldSucceed(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Prepare
using var list = new RecyclableList<long>(testData, itemsCount);
// Act
list.Dispose();
// Validate
_ = list.Capacity.Should().Be(0);
_ = list.Count.Should().Be(0);
_ = list.Should().BeEmpty();
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void EnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Prepare
using var list = new RecyclableList<long>(testData, itemsCount);
// Act
var yieldedItems = new List<long>(itemsCount);
using var enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
@@ -332,10 +456,23 @@ namespace Recyclable.CollectionsTests
}
// Validate
_ = yieldedItems.Count.Should().Be((int)itemsCount);
_ = yieldedItems.Should().Equal(testData);
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void GetByIndexShouldReturnCorrectItem(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Prepare
using var list = new RecyclableLongList<long>(testData, itemsCount);
// Act & Validate
foreach (var item in testData)
{
_ = list[item - 1].Should().Be(item);
}
}
[Fact]
public void IndexOfShouldNotFindAnythingWhenListEmpty()
{
@@ -349,10 +486,10 @@ namespace Recyclable.CollectionsTests
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void IndexOfShouldNotFindNonExistingItems(string testCase, IEnumerable<long> testData, long itemsCount, in long[] itemIndexes)
public void IndexOfShouldNotFindNonExistingItems(string testCase, IEnumerable<long> testData, int itemsCount, in long[] itemIndexes)
{
// Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount);
using var list = new RecyclableList<long>(testData, itemsCount);
_ = testData.Any().Should().BeTrue("we need items on the list that we can look for");
// Act & Validate
@@ -364,10 +501,10 @@ namespace Recyclable.CollectionsTests
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void IndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable<long> testData, long itemsCount, in long[] itemIndexes)
public void IndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable<long> testData, int itemsCount, in long[] itemIndexes)
{
// Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount);
using var list = new RecyclableList<long>(testData, itemsCount);
// Act & Validate
foreach (var itemIndex in itemIndexes)
@@ -378,17 +515,21 @@ namespace Recyclable.CollectionsTests
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void InitializeSortClearAddRangeRemoveShouldSucceed(string testCase, IEnumerable<long> testData, long itemsCount, in long[] itemIndexes)
public void InitializeSortClearAddRangeRemoveShouldSucceed(string testCase, IEnumerable<long> testData, int itemsCount, in long[] itemIndexes)
{
foreach (var itemIndex in itemIndexes)
{
// Prepare
var testDataList = testData.ToList();
var item = testDataList[(int)itemIndex];
// Act
using RecyclableList<long> list = new(testData, (int)itemsCount);
using RecyclableList<long> list = new(testData);
QuickSortExtensions<long>.QuickSort(list);
// Validate
_ = list.Count.Should().Be((int)itemsCount);
_ = list.Should().Equal(testData);
_ = list.Count.Should().Be(itemsCount);
_ = list.Should().Equal(testDataList);
// Act
list.Clear();
@@ -401,22 +542,29 @@ namespace Recyclable.CollectionsTests
list.AddRange(testData);
// Validate
_ = list.Count.Should().Be((int)itemsCount);
_ = list.Should().Equal(testData);
_ = list.Count.Should().Be(itemsCount);
_ = list.Should().Equal(testDataList);
// Prepare
testDataList.RemoveAt((int)itemIndex);
// Act
_ = list.Remove(list[(int)itemIndex]).Should().BeTrue();
_ = list.Remove(item).Should().BeTrue();
_ = list.IndexOf(item).Should().Be(RecyclableDefaults.ItemNotFoundIndex);
_ = list.Contains(item).Should().BeFalse();
_ = list.Count.Should().Be(itemsCount - 1);
_ = list.Should().Equal(testDataList);
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void InsertShouldMoveItems(string testCase, IEnumerable<long> testData, long itemsCount, in long[] itemIndexes)
public void InsertShouldMoveItems(string testCase, IEnumerable<long> testData, int itemsCount, in long[] itemIndexes)
{
foreach (var itemIndex in itemIndexes)
{
// Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount);
using var list = new RecyclableList<long>(testData, itemsCount);
var item = list[(int)itemIndex];
var expectedItems = list.ToList();
expectedItems.Insert((int)itemIndex, item);
@@ -425,22 +573,40 @@ namespace Recyclable.CollectionsTests
list.Insert((int)itemIndex, item);
// Validate
_ = list.Count.Should().Be((int)itemsCount + 1);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount);
_ = list[(int)itemIndex].Should().Be(item);
_ = list[(int)itemIndex + 1].Should().Be(item);
_ = list.Count.Should().Be(itemsCount + 1);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.Should().Equal(expectedItems);
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceRefDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void RemoveAtShouldClearItemWhenReferenceType(string testCase, IEnumerable<object> testData, int itemsCount, in long[] indexesToRemove)
{
// Arrange
foreach (int indexToRemove in indexesToRemove)
{
using var list = new RecyclableList<object>(testData, itemsCount);
_ = list[^1].Should().NotBeNull();
// Act
list.RemoveAt(indexToRemove);
// Assert
_ = list[list.Count].Should().BeNull();
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void RemoveAtShouldSucceed(string testCase, IEnumerable<long> testData, long itemsCount, in long[] itemIndexes)
public void RemoveAtShouldRemoveTheCorrectItem(string testCase, IEnumerable<long> testData, int itemsCount, in long[] itemIndexes)
{
foreach (var itemIndex in itemIndexes)
{
// Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount);
using var list = new RecyclableList<long>(testData, itemsCount);
var expectedItems = testData.ToList();
expectedItems.RemoveAt((int)itemIndex);
@@ -448,42 +614,103 @@ namespace Recyclable.CollectionsTests
list.RemoveAt((int)itemIndex);
// Validate
_ = list.Count.Should().Be((int)(itemsCount - 1));
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.Count.Should().Be(itemsCount - 1);
_ = list.Should().Equal(expectedItems);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount);
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceRefDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void RemoveShouldClearItemWhenReferenceType(string testCase, IEnumerable<object> testData, int itemsCount, in long[] indexesToRemove)
{
var expectedItems = testData.ToArray();
foreach (int indexToRemove in indexesToRemove)
{
// Arrange
var itemToRemove = expectedItems[indexToRemove];
using var list = new RecyclableList<object>(testData, itemsCount);
_ = list[^1].Should().NotBeNull();
// Act
list.Remove(itemToRemove);
// Assert
_ = list[list.Count].Should().BeNull();
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void RemoveShouldRemoveTheCorrectItem(string testCase, IEnumerable<long> testData, long itemsCount, in long[] itemIndexes)
public void RemoveShouldRemoveTheCorrectItem(string testCase, IEnumerable<long> testData, int itemsCount, in long[] itemIndexes)
{
foreach (var itemIndex in itemIndexes)
{
// Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount);
var item = list[(int)itemIndex];
using var list = new RecyclableList<long>(testData, itemsCount);
var expectedItems = testData.ToList();
var item = list[(int)itemIndex];
expectedItems.RemoveAt((int)itemIndex);
// Act
_ = list.Remove(item).Should().BeTrue();
// Validate
_ = list.Count.Should().Be((int)(itemsCount - 1));
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.Count.Should().Be(itemsCount - 1);
_ = list.Should().Equal(expectedItems);
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void VersionedEnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable<long> testData, long itemsCount)
public void SetByIndexShouldReturnCorrectItem(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount);
using var list = new RecyclableList<long>(testData, itemsCount);
var expectedData = testData.ToList();
// Act & Validate
foreach (long item in testData)
{
// Act
list[(int)item - 1] = item + 1;
// Validate
expectedData[(int)item - 1] = item + 1;
list[(int)item - 1].Should().Be(item + 1);
list.Should().Equal(expectedData);
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void VersionedEnumerateShouldRaiseExceptionWhenCollectionIsModified(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Prepare
using var list = new RecyclableList<long>(testData, itemsCount);
// Act
var yieldedItems = new List<long>((int)itemsCount);
Assert.Throws<InvalidOperationException>(() =>
{
list.Add(-1);
foreach (var item in (IRecyclableVersionedList<long>)list)
{
list.Add(item);
}
});
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void VersionedEnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable<long> testData, int itemsCount)
{
// Prepare
using var list = new RecyclableList<long>(testData, itemsCount);
// Act
var yieldedItems = new List<long>(itemsCount);
foreach (var item in (IRecyclableVersionedList<long>)list)
{
yieldedItems.Add(item);
@@ -30,7 +30,7 @@ namespace Recyclable.CollectionsTests
public static readonly IEnumerable<long> ItemsCountVariants = new long[]
{
//RecyclableDefaults.MinPooledArrayLength - 5, RecyclableDefaults.MinPooledArrayLength - 1, RecyclableDefaults.MinPooledArrayLength, RecyclableDefaults.MinPooledArrayLength + 1, RecyclableDefaults.MinPooledArrayLength + 5, 127, 128, 129, RecyclableDefaults.MinItemsCountForParallelization
0, 1, 2, 3, 7, 10, 16, RecyclableDefaults.MinPooledArrayLength - 5, RecyclableDefaults.MinPooledArrayLength - 1, RecyclableDefaults.MinPooledArrayLength, RecyclableDefaults.MinPooledArrayLength + 1, RecyclableDefaults.MinPooledArrayLength + 5, RecyclableDefaults.MinPooledArrayLength * 100, RecyclableDefaults.MinItemsCountForParallelization
0, 1, 2, 3, 7, 10, 16, RecyclableDefaults.MinPooledArrayLength - 5, RecyclableDefaults.MinPooledArrayLength - 1, RecyclableDefaults.MinPooledArrayLength, RecyclableDefaults.MinPooledArrayLength + 1, RecyclableDefaults.MinPooledArrayLength + 5, RecyclableDefaults.MinPooledArrayLength * 100
};
public static readonly IEnumerable<int> BlockSizeVariants = new int[]
@@ -83,9 +83,10 @@ namespace Recyclable.CollectionsTests
switch (refType)
{
case false:
IEnumerable<long> testData = CreateTestData(itemsCount).ToArray();
long[] testData = CreateTestData(itemsCount).ToArray();
yield return new object[] { $"int[{itemsCount}]", testData, itemsCount };
yield return new object[] { $"ReadOnlySpan[{itemsCount}]", testData, itemsCount };
//GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true);
yield return new object[] { $"List<int>(itemsCount: {itemsCount}", testData.ToList(), itemsCount };
@@ -12,12 +12,7 @@ namespace Recyclable.CollectionsTests
private static long CalculateExpectedCapacity(string testCase, long itemsCount, int targetBlockSize)
{
var expectedCapacity = (long)BitOperations.RoundUpToPowerOf2((uint)itemsCount + 1);
if (testCase.Contains("non-enumerated"))
{
expectedCapacity = (long)BitOperations.RoundUpToPowerOf2((ulong)itemsCount + RecyclableDefaults.BlockSize);
expectedCapacity = expectedCapacity >= RecyclableDefaults.InitialCapacity ? expectedCapacity : RecyclableDefaults.InitialCapacity;
}
else if (targetBlockSize > itemsCount)
if (targetBlockSize > itemsCount)
{
expectedCapacity = ((itemsCount / targetBlockSize) + (itemsCount % targetBlockSize > 0 ? 1 : 0)) * targetBlockSize;
}
@@ -25,6 +20,19 @@ namespace Recyclable.CollectionsTests
return expectedCapacity;
}
[Fact]
public void AddRangeShouldAcceptNulls()
{
// Prepare
using var list = new RecyclableLongList<long?>();
// Act
list.AddRange(new long?[] { null, default });
// Validate
_ = list.Should().HaveCount(2).And.AllSatisfy(x => x.Should().BeNull());
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
@@ -33,7 +41,11 @@ namespace Recyclable.CollectionsTests
using var list = new RecyclableLongList<long>(minBlockSize: targetBlockSize);
// Act
if (testData is long[] testDataArray)
if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase))
{
list.AddRange(new ReadOnlySpan<long>((long[])testData));
}
else if (testData is long[] testDataArray)
{
list.AddRange(testDataArray);
}
@@ -63,9 +75,9 @@ namespace Recyclable.CollectionsTests
}
// Validate
int expectedItemsCount = testData.Count();
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(expectedItemsCount, "when capacity == 0, then we should allocate as much memory as needed, only");
_ = list.LastBlockWithData.Should().Be((int)(itemsCount / list.BlockSize) - (itemsCount % list.BlockSize != 0 ? 0 : 1));
long expectedItemsCount = itemsCount;
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(expectedItemsCount);
_ = list.LastBlockWithData.Should().Be((int)(itemsCount / targetBlockSize) - (itemsCount % targetBlockSize != 0 ? 0 : 1));
_ = list.LongCount.Should().Be(expectedItemsCount);
_ = list.Should().Equal(testData);
}
@@ -78,7 +90,11 @@ namespace Recyclable.CollectionsTests
using var list = new RecyclableLongList<long>();
// Act
if (testData is long[] testDataArray)
if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase))
{
list.AddRange(new ReadOnlySpan<long>((long[])testData));
}
else if (testData is long[] testDataArray)
{
list.AddRange(testDataArray);
}
@@ -110,7 +126,7 @@ namespace Recyclable.CollectionsTests
// Validate
_ = list.Capacity.Should().Be(0);
_ = list.LastBlockWithData.Should().Be(-1);
_ = list.LongCount.Should().Be(0);
_ = list.LongCount.Should().Be(itemsCount);
_ = list.Should().BeEmpty();
}
@@ -122,7 +138,13 @@ namespace Recyclable.CollectionsTests
using var list = new RecyclableLongList<long>(minBlockSize: targetBlockSize);
// Act
if (testData is long[] testDataArray)
if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase))
{
var readOnlySpan = new ReadOnlySpan<long>((long[])testData);
list.AddRange(readOnlySpan);
list.AddRange(readOnlySpan);
}
else if (testData is long[] testDataArray)
{
list.AddRange(testDataArray);
list.AddRange(testDataArray);
@@ -158,14 +180,9 @@ namespace Recyclable.CollectionsTests
}
// Validate
var expectedData = testData.Concat(testData).ToArray();
if (itemsCount > 0)
{
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(targetBlockSize);
}
_ = list.LongCount.Should().Be(expectedData.Length);
var expectedData = testData.Concat(testData);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount * 2);
_ = list.LongCount.Should().Be(itemsCount * 2);
_ = list.LastBlockWithData.Should().Be((int)(itemsCount * 2 / list.BlockSize) - (itemsCount * 2 % list.BlockSize != 0 ? 0 : 1));
_ = list.Should().Equal(expectedData);
}
@@ -175,7 +192,7 @@ namespace Recyclable.CollectionsTests
public void AddShouldAcceptDuplicates(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
{
// Prepare
testData = testData.Concat(testData).ToArray();
testData = testData.Concat(testData);
// Act
using var list = new RecyclableLongList<long>(minBlockSize: targetBlockSize);
@@ -185,11 +202,29 @@ namespace Recyclable.CollectionsTests
}
// Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount * 2);
_ = list.LongCount.Should().Be(itemsCount * 2);
_ = list.LastBlockWithData.Should().Be((int)(itemsCount * 2 / list.BlockSize) - (itemsCount * 2 % list.BlockSize != 0 ? 0 : 1));
_ = list.Should().Equal(testData);
}
[Fact]
public void AddShouldAcceptNulls()
{
// Prepare
#pragma warning disable IDE0028
using var list = new RecyclableLongList<long?>();
// Act
list.Add(null);
list.Add(default);
#pragma warning restore IDE0028
// Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(RecyclableDefaults.BlockSize);
_ = list.Should().AllSatisfy(x => x.Should().BeNull());
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void AddShouldAddItems(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
@@ -201,9 +236,11 @@ namespace Recyclable.CollectionsTests
foreach (var index in testData)
{
list.Add(index);
_ = list.LongCount.Should().Be(index);
}
// Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.LongCount.Should().Be(itemsCount, "we added so many items");
_ = list.LastBlockWithData.Should().Be((int)(itemsCount / list.BlockSize) - (itemsCount % list.BlockSize == 0 ? 1 : 0));
_ = list.Should().Equal(testData);
@@ -223,11 +260,38 @@ namespace Recyclable.CollectionsTests
}
// Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount * 2);
_ = list.LongCount.Should().Be(itemsCount * 2);
_ = list.LastBlockWithData.Should().Be((int)(itemsCount * 2 / list.BlockSize) - (itemsCount * 2 % list.BlockSize != 0 ? 0 : 1));
_ = list.Should().Equal(testData.Concat(testData));
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceRefDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void ClearShouldClearItemsWhenReferenceType(string testCase, IEnumerable<object> testData, long itemsCount, int targetBlockSize)
{
if (itemsCount == 0)
{
/// Clearing empty list is tested in <see cref="ClearShouldSucceed"/>.
return;
}
// Arrange
using var list = new RecyclableLongList<object>(testData, minBlockSize: targetBlockSize, itemsCount);
var blocks = list.AsArray.ToArray();
_ = blocks.Should().AllBeOfType<object[]>();
var beforeItems = blocks.SelectMany(x => x.Take(targetBlockSize)).Take((int)itemsCount).ToArray();
_ = beforeItems.Should().AllSatisfy(x => x.Should().NotBeNull());
// Act
list.Clear();
// Assert
var afterItems = blocks.SelectMany(x => x.Take(targetBlockSize)).Take((int)itemsCount).ToArray();
_ = afterItems.Should().AllSatisfy(x => x.Should().BeNull());
_ = list.AsArray.Should().AllBeOfType<object[]>().And.AllSatisfy(x => x.Should().BeEmpty());
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void ClearShouldRemoveAllItems(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
@@ -241,10 +305,31 @@ namespace Recyclable.CollectionsTests
list.Clear();
// Validate
_ = list.Capacity.Should().Be(0);
_ = list.LastBlockWithData.Should().Be(-1);
_ = list.LongCount.Should().Be(0);
_ = list.Should().BeEmpty();
}
[Fact]
public void ClearShouldReturnPooledArraysOnlyOnce()
{
// Arrange
var testData = RecyclableLongListTestData.CreateTestData(RecyclableDefaults.MinPooledArrayLength * 10);
using RecyclableLongList<long> firstInstances = new(testData, RecyclableDefaults.MinPooledArrayLength);
var firstArrays = firstInstances.AsArray.ToArray();
// Act
firstInstances.Clear();
// Assert
using RecyclableLongList<long> sameInstances = new(testData, RecyclableDefaults.MinPooledArrayLength);
_ = sameInstances.AsArray.Should().Equal(firstArrays.Reverse());
using RecyclableLongList<long> otherInstances = new(testData, RecyclableDefaults.MinPooledArrayLength);
_ = otherInstances.AsArray.Should().NotIntersectWith(firstArrays);
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void ConsecutiveDisposeShouldSucceed(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
@@ -262,12 +347,14 @@ namespace Recyclable.CollectionsTests
public void ConstructorShouldAcceptDuplicates(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
{
// Prepare
testData = testData.Concat(testData).ToArray();
testData = testData.Concat(testData);
// Act
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount);
// Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount * 2);
_ = list.LastBlockWithData.Should().Be((int)(itemsCount * 2 / targetBlockSize) - (itemsCount * 2 % targetBlockSize > 0 ? 0 : 1));
_ = list.LongCount.Should().Be(itemsCount * 2);
_ = list.Should().Equal(testData);
}
@@ -280,7 +367,8 @@ namespace Recyclable.CollectionsTests
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize);
// Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount, "when we succeed in taking count without enumerating, then we allocate as much memory as needed, only");
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.LastBlockWithData.Should().Be((int)(itemsCount / targetBlockSize) - (itemsCount % targetBlockSize > 0 ? 0 : 1));
_ = list.LongCount.Should().Be(itemsCount);
_ = list.Should().Equal(testData);
}
@@ -334,11 +422,57 @@ namespace Recyclable.CollectionsTests
{
// Prepare
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount);
long[] copiedItems = new long[itemsCount];
// Act
var actual = new long[itemsCount];
list.CopyTo(actual, 0);
_ = actual.Should().Equal(testData);
list.CopyTo(copiedItems, 0);
// Validate
_ = copiedItems.Should().Equal(testData);
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceRefDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void DisposeShouldClearItemsWhenReferenceType(string testCase, IEnumerable<object> testData, long itemsCount, int targetBlockSize)
{
if (itemsCount == 0)
{
/// Disposing empty list is tested in <see cref="DisposeShouldSucceed"/>.
return;
}
// Arrange
using var list = new RecyclableLongList<object>(testData, targetBlockSize, itemsCount);
var blocks = list.AsArray.ToArray();
_ = blocks.Should().AllBeOfType<object[]>();
_ = blocks.SelectMany(x => x.Take(targetBlockSize)).Take((int)itemsCount).Should().AllSatisfy(x => x.Should().NotBeNull());
// Act
list.Dispose();
// Assert
_ = blocks.SelectMany(x => x.Take(targetBlockSize)).Take((int)itemsCount).Should().AllSatisfy(x => x.Should().BeNull());
_ = list.AsArray.Should().AllBeOfType<object[]>().And.AllSatisfy(x => x.Should().BeEmpty());
}
[Fact]
public void DisposeShouldReturnPooledArrayOnlyOnce()
{
// Arrange
var testData = RecyclableLongListTestData.CreateTestData(RecyclableDefaults.MinPooledArrayLength * 10);
using RecyclableLongList<long> firstInstances = new(testData, RecyclableDefaults.MinPooledArrayLength);
var firstArrays = firstInstances.AsArray.ToArray();
// Act
firstInstances.Dispose();
// Assert
using RecyclableLongList<long> sameInstances = new(testData, RecyclableDefaults.MinPooledArrayLength);
_ = sameInstances.AsArray.Should().BeEquivalentTo(firstArrays);
using RecyclableLongList<long> otherInstances = new(testData, RecyclableDefaults.MinPooledArrayLength);
_ = otherInstances.AsArray.Should().NotIntersectWith(firstArrays);
}
[Theory]
@@ -352,27 +486,29 @@ namespace Recyclable.CollectionsTests
list.Dispose();
// Validate
_ = list.LongCount.Should().Be(0L);
_ = list.Capacity.Should().Be(0);
_ = list.LastBlockWithData.Should().Be(-1);
_ = list.LongCount.Should().Be(0);
_ = list.Should().BeEmpty();
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void EnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
public void EnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable<long> testData, int itemsCount, int targetBlockSize)
{
// Prepare
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount);
// Act
var actual = new List<long>((int)itemsCount);
var yieldedItems = new List<long>(itemsCount);
using var enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
actual.Add(enumerator.Current);
yieldedItems.Add(enumerator.Current);
}
// Validate
_ = actual.Should().Equal(testData);
_ = yieldedItems.Should().Equal(testData);
}
[Theory]
@@ -383,7 +519,21 @@ namespace Recyclable.CollectionsTests
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount);
// Act & Validate
foreach (var item in testData)
foreach (long item in testData)
{
_ = list[(int)item - 1].Should().Be(item);
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void GetByIndexShouldReturnCorrectItemWhenLong(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
{
// Prepare
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount);
// Act & Validate
foreach (long item in testData)
{
_ = list[item - 1].Should().Be(item);
}
@@ -436,7 +586,7 @@ namespace Recyclable.CollectionsTests
foreach (var itemIndex in itemIndexes)
{
// Prepare
using var testDataList = testData.ToRecyclableList();
var testDataList = testData.ToList();
var item = testDataList[(int)itemIndex];
// Act
@@ -477,23 +627,52 @@ namespace Recyclable.CollectionsTests
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void InsertShouldMoveItems(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize, in long[] itemIndexes)
{
foreach (var itemIndex in itemIndexes)
{
// Prepare
using var list = new RecyclableLongList<long>(testData, targetBlockSize, itemsCount);
long expectedCapacity = CalculateExpectedCapacity(testCase, itemsCount, targetBlockSize);
var expectedItems = testData.ToList();
var item = list[itemIndex];
var expectedItems = list.ToList();
expectedItems.Insert((int)itemIndex, item);
var existingItem = expectedItems[(int)itemIndex];
var newItem = existingItem + 1;
expectedItems.Insert((int)itemIndex, newItem);
// Act
list.Insert(itemIndex, item);
list.Insert((int)itemIndex, newItem);
// Validate
_ = list[itemIndex].Should().Be(newItem);
_ = list[itemIndex + 1].Should().Be(existingItem);
_ = list.Capacity.Should().Be(expectedCapacity);
_ = list.Count.Should().Be((int)itemsCount + 1);
_ = list.Should().Equal(expectedItems);
}
}
_ = list[itemIndex].Should().Be(item);
_ = list[itemIndex + 1].Should().Be(item);
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void InsertShouldMoveItemsWhenLong(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize, in long[] itemIndexes)
{
foreach (var itemIndex in itemIndexes)
{
// Prepare
using var list = new RecyclableLongList<long>(testData, targetBlockSize, itemsCount);
long expectedCapacity = CalculateExpectedCapacity(testCase, itemsCount, targetBlockSize);
var expectedItems = testData.ToList();
var existingItem = expectedItems[(int)itemIndex];
var newItem = existingItem + 1;
expectedItems.Insert((int)itemIndex, newItem);
// Act
list.Insert(itemIndex, newItem);
// Validate
_ = list[itemIndex].Should().Be(newItem);
_ = list[itemIndex + 1].Should().Be(existingItem);
_ = list.Capacity.Should().Be(expectedCapacity);
_ = list.Count.Should().Be((int)itemsCount + 1);
_ = list.Should().Equal(expectedItems);
@@ -540,9 +719,49 @@ namespace Recyclable.CollectionsTests
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceRefDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void RemoveAtShouldClearItemWhenReferenceType(string testCase, IEnumerable<object> testData, long itemsCount, int targetBlockSize, in long[] indexesToRemove)
{
foreach (var indexToRemove in indexesToRemove)
{
// Arrange
using var list = new RecyclableLongList<object>(testData, targetBlockSize, itemsCount);
_ = list[^1].Should().NotBeNull();
// Act
list.RemoveAt(indexToRemove);
// Assert
_ = list[list.LongCount].Should().BeNull();
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void RemoveAtShouldSucceed(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize, in long[] itemIndexes)
public void RemoveAtShouldRemoveTheCorrectItem(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize, in long[] itemIndexes)
{
foreach (var itemIndex in itemIndexes)
{
// Prepare
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize);
var expectedItems = list.ToList();
expectedItems.RemoveAt((int)itemIndex);
// Act
list.RemoveAt((int)itemIndex);
// Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.LastBlockWithData.Should().Be((int)(list.LongCount >> list.BlockSizePow2BitShift) - ((list.LongCount & list.BlockSizeMinus1) != 0 ? 0 : 1));
_ = list.LongCount.Should().Be(itemsCount - 1);
_ = list.Should().Equal(expectedItems);
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void RemoveAtShouldRemoveTheCorrectItemWhenLong(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize, in long[] itemIndexes)
{
foreach (var itemIndex in itemIndexes)
{
@@ -555,13 +774,34 @@ namespace Recyclable.CollectionsTests
list.RemoveAt(itemIndex);
// Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.LastBlockWithData.Should().Be((int)(list.LongCount >> list.BlockSizePow2BitShift) - ((list.LongCount & list.BlockSizeMinus1) != 0 ? 0 : 1));
_ = list.LongCount.Should().Be(itemsCount - 1);
_ = list.Should().Equal(expectedItems);
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceRefDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void RemoveShouldClearItemWhenReferenceType(string testCase, IEnumerable<object> testData, long itemsCount, int targetBlockSize, in long[] indexesToRemove)
{
var expectedItems = testData.ToArray();
foreach (var indexToRemove in indexesToRemove)
{
// Arrange
var itemToRemove = expectedItems[(int)indexToRemove];
using var list = new RecyclableLongList<object>(testData, targetBlockSize, itemsCount);
_ = list[^1].Should().NotBeNull();
// Act
list.Remove(itemToRemove);
// Assert
_ = list[list.LongCount].Should().BeNull();
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void RemoveShouldRemoveTheCorrectItem(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize, in long[] itemIndexes)
@@ -578,29 +818,91 @@ namespace Recyclable.CollectionsTests
_ = list.Remove(item).Should().BeTrue();
// Validate
_ = list.LongCount.Should().Be(itemsCount - 1);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.LastBlockWithData.Should().Be((int)(list.LongCount >> list.BlockSizePow2BitShift) - ((list.LongCount & list.BlockSizeMinus1) != 0 ? 0 : 1));
_ = list.LongCount.Should().Be(itemsCount - 1);
_ = list.Should().Equal(expectedItems);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount);
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void VersionedEnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
public void SetByIndexShouldReturnCorrectItem(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
{
// Prepare
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount);
var expectedData = testData.ToList();
// Act & Validate
foreach (long item in testData)
{
// Act
list[(int)item - 1] = item + 1;
// Validate
expectedData[(int)item - 1] = item + 1;
list[item - 1].Should().Be(item + 1);
list.Should().Equal(expectedData);
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void SetByIndexShouldReturnCorrectItemWhenLong(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
{
// Prepare
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount);
var expectedData = testData.ToList();
// Act & Validate
foreach (long item in testData)
{
// Act
list[item - 1] = item + 1;
// Validate
expectedData[(int)item - 1] = item + 1;
list[item - 1].Should().Be(item + 1);
list.Should().Equal(expectedData);
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void VersionedEnumerateShouldRaiseExceptionWhenCollectionIsModified(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
{
// Prepare
using var list = new RecyclableLongList<long>(testData, targetBlockSize, itemsCount)
{
-1
};
// Act
_ = Assert.Throws<InvalidOperationException>(() =>
{
foreach (var item in (IRecyclableVersionedLongList<long>)list)
{
list.Add(item);
}
});
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void VersionedEnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable<long> testData, int itemsCount, int targetBlockSize)
{
// Prepare
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount);
// Act
using var actual = new RecyclableList<long>((int)itemsCount);
var yieldedItems = new List<long>(itemsCount);
foreach (var item in (IRecyclableVersionedLongList<long>)list)
{
actual.Add(item);
yieldedItems.Add(item);
}
// Validate
_ = actual.Should().Equal(testData);
_ = yieldedItems.Should().Equal(testData);
}
}
}