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. ✅ `InsertAt` to allow inserting items anywhere
1. ✅ `RemoveAt` to allow removing item at any index 1. ✅ `RemoveAt` to allow removing item at any index
1. ✅ Release 0.0.4 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. 👉 Add `.ToRecyclableList` / `.ToRecyclableLongList` variants for all supported collection types
1. 🅿️ `RecyclableList` 1. 🅿️ `RecyclableList`
1. 🅿️ `RecyclableLongList` 1. 🅿️ `RecyclableLongList`
@@ -187,9 +193,9 @@
1. 🅿️ Multi-threading benchmarks 1. 🅿️ Multi-threading benchmarks
1. 🅿️ Release 0.0.13 1. 🅿️ Release 0.0.13
1. 🅿️ Extend unit tests 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. 🅿️ `RecyclableLongListExtensions.CopyTo`
1. 🅿️ Pools
1. 🅿️ Multi-threading
1. 🅿️ Cleanup 1. 🅿️ Cleanup
1. 🅿️ Replace `LastBlockWithData` property with `_lastBlockWithData` field 1. 🅿️ Replace `LastBlockWithData` property with `_lastBlockWithData` field
1. 🅿️ Replace properties with field refs, where possible 1. 🅿️ Replace properties with field refs, where possible
@@ -198,13 +204,16 @@
1. 🅿️ `MathUtils` 1. 🅿️ `MathUtils`
1. 🅿️ Resolve TODOs 1. 🅿️ Resolve TODOs
1. 🅿️ Remove obsolete methods 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. 🅿️ Release 0.0.14
1. 🅿️ Optimize 1. 🅿️ Optimize
1. 🅿️ `ListExtensions` 1. 🅿️ `ListExtensions`
1. 🅿️ `MathUtils` 1. 🅿️ `MathUtils`
1. 🅿️ `SystemRandomNumberGenerator` 1. 🅿️ `SystemRandomNumberGenerator`
1. 🅿️ `RecyclableLongListExtensions` 1. 🅿️ `RecyclableLongListExtensions`
1. 🅿️ Memory release in pools to utilize GC events in case of memory pressure
1. 🅿️ Release 0.0.15 1. 🅿️ Release 0.0.15
1. ✅ Review and remove warnings & hints 1. ✅ Review and remove warnings & hints
1. ✅ Warnings 1. ✅ Warnings
+3 -8
View File
@@ -272,11 +272,6 @@ namespace Recyclable.Collections
int oldCount = _count; int oldCount = _count;
long sourceItemsCount = items._longCount; 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) 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"); 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); AddRange(sourceIList);
} }
else if (items.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) else if (items.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity) && requiredAdditionalCapacity != 0)
{ {
AddRangeWithKnownCount(items, _count, requiredAdditionalCapacity); AddRangeWithKnownCount(items, _count, requiredAdditionalCapacity);
_version++; _version++;
@@ -388,7 +383,7 @@ namespace Recyclable.Collections
if (index < oldCount) if (index < oldCount)
{ {
new Span<T>(_memoryBlock, index, oldCount) new Span<T>(_memoryBlock, index, oldCount -= index)
.CopyTo(new Span<T>(_memoryBlock, index + 1, oldCount)); .CopyTo(new Span<T>(_memoryBlock, index + 1, oldCount));
} }
@@ -468,11 +463,11 @@ namespace Recyclable.Collections
if (_capacity >= RecyclableDefaults.MinPooledArrayLength) 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. // If anything, it has been already cleared by .Clear(), Remove() or RemoveAt() methods, as the list was modified / disposed.
RecyclableArrayPool<T>.ReturnShared(_memoryBlock, false); RecyclableArrayPool<T>.ReturnShared(_memoryBlock, false);
} }
_capacity = 0;
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
} }
@@ -26,7 +26,7 @@ namespace Recyclable.Collections
{ {
var list = _list; 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]; _current = list._memoryBlocks[_currentBlockIndex][_currentItemIndex];
if (_currentItemIndex + 1 < list._blockSize) if (_currentItemIndex + 1 < list._blockSize)
@@ -42,12 +42,6 @@ namespace Recyclable.Collections
return true; return true;
} }
// Move to the next item if we're within block boundaries, otherwise report end of collection. // 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; return false;
} }
@@ -11,23 +11,20 @@ namespace Recyclable.Collections
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static void CopyFollowingItems(RecyclableLongList<T> list, long destinationItemIndex) public static void CopyFollowingItems(RecyclableLongList<T> list, long destinationItemIndex)
{ {
T[] currentBlock;
T[][] memoryBlocks = list._memoryBlocks;
int blockSizeMinus1 = list._blockSizeMinus1, int blockSizeMinus1 = list._blockSizeMinus1,
targetItemIndex = (int)(destinationItemIndex & 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; lastTakenBlockIndex = list._lastBlockWithData - 1;
// Each iteration should // Each iteration should
// 1. copy the remaining items in the current block (those after the deleted item) // 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. // 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. // We need to fill it.
currentBlock = memoryBlocks[targetBlockIndex];
if (targetItemIndex < blockSizeMinus1) T[][] memoryBlocks = list._memoryBlocks;
{ T[] currentBlock = memoryBlocks[targetBlockIndex];
new Span<T>(currentBlock, targetItemIndex + 1, blockSizeMinus1 - targetItemIndex) new Span<T>(currentBlock, targetItemIndex + 1, blockSizeMinus1 - targetItemIndex)
.CopyTo(new Span<T>(currentBlock, targetItemIndex, blockSizeMinus1 - targetItemIndex)); .CopyTo(new Span<T>(currentBlock, targetItemIndex, blockSizeMinus1 - targetItemIndex));
}
while (targetBlockIndex < lastTakenBlockIndex) while (targetBlockIndex < lastTakenBlockIndex)
{ {
@@ -36,7 +33,7 @@ namespace Recyclable.Collections
.CopyTo(new Span<T>(currentBlock)); .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)[blockSizeMinus1] = (currentBlock = memoryBlocks[targetBlockIndex + 1])[0];
new Span<T>(currentBlock, 1, list._nextItemIndex != 0 ? list._nextItemIndex - 1 : blockSizeMinus1) new Span<T>(currentBlock, 1, list._nextItemIndex != 0 ? list._nextItemIndex - 1 : blockSizeMinus1)
@@ -51,7 +48,7 @@ namespace Recyclable.Collections
T[][] memoryBlocks = list._memoryBlocks; T[][] memoryBlocks = list._memoryBlocks;
int blockSizeMinus1 = list._blockSizeMinus1, int blockSizeMinus1 = list._blockSizeMinus1,
targetItemIndex = (int)(startingItemIndex & 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; lastTakenBlockIndex = list._lastBlockWithData;
if (list._nextItemIndex == 0) if (list._nextItemIndex == 0)
@@ -67,11 +64,8 @@ namespace Recyclable.Collections
new Span<T>(currentBlock)[0] = memoryBlocks[currentBlockIndex][blockSizeMinus1]; 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; 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) 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) if (expectedItemsCount > 0)
{ {
Helpers.SetupBlockArrayPooling(this, minBlockSize);
_capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((int)BitOperations.RoundUpToPowerOf2((uint)expectedItemsCount.Value))); _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((int)BitOperations.RoundUpToPowerOf2((uint)expectedItemsCount.Value)));
_blockSizeMinus1 = minBlockSize - 1; _blockSizeMinus1 = minBlockSize - 1;
} }
else else
{ {
Helpers.SetupBlockArrayPooling(this, minBlockSize);
_blockSizeMinus1 = _blockSize - 1; _blockSizeMinus1 = _blockSize - 1;
_memoryBlocks = _emptyMemoryBlocksArray; _memoryBlocks = _emptyMemoryBlocksArray;
} }
@@ -486,14 +483,22 @@ namespace Recyclable.Collections
if (itemsSpan.Length > 0) if (itemsSpan.Length > 0)
{ {
itemsSpan.CopyTo(targetBlockArraySpan); itemsSpan.CopyTo(targetBlockArraySpan);
_nextItemBlockIndex = targetBlockIndex; if (_nextItemBlockIndex != targetBlockIndex)
{
_nextItemIndex = itemsSpan.Length;
_nextItemBlockIndex = targetBlockIndex;
}
else
{
_nextItemIndex += itemsSpan.Length;
}
} }
else else
{ {
_nextItemBlockIndex = targetBlockIndex + 1; _nextItemBlockIndex = targetBlockIndex + 1;
_nextItemIndex = 0;
} }
_nextItemIndex = itemsSpan.Length;
_longCount = targetCapacity; _longCount = targetCapacity;
_lastBlockWithData = targetBlockIndex; _lastBlockWithData = targetBlockIndex;
_version++; _version++;
@@ -532,22 +537,38 @@ namespace Recyclable.Collections
targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize);
} }
_nextItemIndex = itemsSpan.Length; if (itemsSpan.Length > 0)
itemsSpan.CopyTo(targetBlockArraySpan); {
itemsSpan.CopyTo(targetBlockArraySpan);
if (_nextItemBlockIndex != targetBlockIndex)
{
_nextItemIndex = itemsSpan.Length;
_nextItemBlockIndex = targetBlockIndex;
}
else
{
_nextItemIndex += itemsSpan.Length;
}
}
else
{
_nextItemBlockIndex = targetBlockIndex;
_nextItemIndex = 0;
}
_longCount = targetCapacity; _longCount = targetCapacity;
_nextItemBlockIndex = targetBlockIndex;
_lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1);
_version++; _version++;
} }
public void AddRange(ReadOnlySpan<T> itemsSpan) public void AddRange(ReadOnlySpan<T> items)
{ {
if (itemsSpan.Length == 0) if (items.Length == 0)
{ {
return; return;
} }
long targetCapacity = _longCount + itemsSpan.Length; long targetCapacity = _longCount + items.Length;
if (_capacity < targetCapacity) if (_capacity < targetCapacity)
{ {
_capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)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[]> memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount);
Span<T> targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); Span<T> targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex);
while (itemsSpan.Length >= targetBlockArraySpan.Length) while (items.Length >= targetBlockArraySpan.Length)
{ {
itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); items[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan);
itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; items = items[targetBlockArraySpan.Length..];
if (itemsSpan.Length == 0) if (items.Length == 0)
{ {
break; break;
} }
@@ -572,17 +593,25 @@ namespace Recyclable.Collections
targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize);
} }
if (itemsSpan.Length > 0) if (items.Length > 0)
{ {
itemsSpan.CopyTo(targetBlockArraySpan); items.CopyTo(targetBlockArraySpan);
_nextItemBlockIndex = targetBlockIndex; if (_nextItemBlockIndex != targetBlockIndex)
{
_nextItemIndex = items.Length;
_nextItemBlockIndex = targetBlockIndex;
}
else
{
_nextItemIndex += items.Length;
}
} }
else else
{ {
_nextItemBlockIndex = targetBlockIndex + 1; _nextItemBlockIndex = targetBlockIndex + 1;
_nextItemIndex = 0;
} }
_nextItemIndex = itemsSpan.Length;
_longCount = targetCapacity; _longCount = targetCapacity;
_lastBlockWithData = targetBlockIndex; _lastBlockWithData = targetBlockIndex;
_version++; _version++;
@@ -749,10 +778,26 @@ namespace Recyclable.Collections
targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); 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; _longCount = targetCapacity;
_nextItemBlockIndex = targetBlockIndex; _nextItemBlockIndex = targetBlockIndex;
_nextItemIndex = itemsSpan.Length;
_lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1);
} }
finally 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); AddRange(sourceRecyclableLongList);
} }
else if (source is RecyclableList<T> sourceRecyclableList) else if (items is RecyclableList<T> sourceRecyclableList)
{ {
AddRange(sourceRecyclableList); AddRange(sourceRecyclableList);
} }
else if (source is T[] sourceArray) else if (items is T[] sourceArray)
{ {
AddRange(sourceArray); AddRange(sourceArray);
} }
else if (source is List<T> sourceList) else if (items is List<T> sourceList)
{ {
AddRange(sourceList); AddRange(sourceList);
} }
else if (source is IList<T> sourceIList) else if (items is IList<T> sourceIList)
{ {
AddRange(sourceIList); AddRange(sourceIList);
} }
else if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) else if (items.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity))
{ {
AddRangeWithKnownCount(source, requiredAdditionalCapacity); AddRangeWithKnownCount(items, requiredAdditionalCapacity);
_version++; _version++;
} }
else else
{ {
AddRangeEnumerated(source); AddRangeEnumerated(items);
_version++; _version++;
} }
} }
@@ -807,16 +852,24 @@ namespace Recyclable.Collections
return; return;
} }
T[][] memoryBlocks = _memoryBlocks;
int memoryBlocksCount = _reservedBlockCount;
int toRemoveIdx = 0;
if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) if (_blockSize >= RecyclableDefaults.MinPooledArrayLength)
{ {
T[][] memoryBlocks = _memoryBlocks;
int memoryBlocksCount = _reservedBlockCount;
int toRemoveIdx = 0;
while (toRemoveIdx < memoryBlocksCount) while (toRemoveIdx < memoryBlocksCount)
{ {
RecyclableArrayPool<T>.ReturnShared(memoryBlocks[toRemoveIdx++], _needsClearing); 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. // TODO: Do we want to initialize with empty arrays or null refs? Null refs may be faster.
Array.Fill(_memoryBlocks, _emptyBlockArray, 0, _reservedBlockCount); Array.Fill(_memoryBlocks, _emptyBlockArray, 0, _reservedBlockCount);
@@ -954,17 +1007,19 @@ namespace Recyclable.Collections
new Span<T>(_memoryBlocks[_nextItemBlockIndex])[_nextItemIndex] = item; new Span<T>(_memoryBlocks[_nextItemBlockIndex])[_nextItemIndex] = item;
} }
if (_nextItemIndex < _blockSizeMinus1) if (++_nextItemIndex == _blockSize)
{ {
if (++_nextItemIndex == 1) if (_nextItemBlockIndex > _lastBlockWithData)
{ {
_lastBlockWithData++; _lastBlockWithData++;
} }
}
else
{
_nextItemIndex = 0;
_nextItemBlockIndex++; _nextItemBlockIndex++;
_nextItemIndex = 0;
}
else if (_nextItemIndex == 1 && _nextItemBlockIndex > _lastBlockWithData)
{
_lastBlockWithData++;
} }
_version++; _version++;
@@ -8,15 +8,32 @@ namespace Recyclable.CollectionsTests
{ {
public class RecyclableListTests 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] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // Prepare
using var list = new RecyclableList<long>(); using var list = new RecyclableList<long>();
// Act // 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); list.AddRange(testDataArray);
} }
@@ -46,20 +63,24 @@ namespace Recyclable.CollectionsTests
} }
// Validate // Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount, "when capacity == 0, then we should allocate as much memory as needed, only"); _ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.Count.Should().Be((int)itemsCount); _ = list.Count.Should().Be(itemsCount);
_ = list.Should().Equal(testData); _ = list.Should().Equal(testData);
} }
[Theory] [Theory]
[MemberData(nameof(RecyclableLongListTestData.EmptySourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // Prepare
using var list = new RecyclableList<long>(); using var list = new RecyclableList<long>();
// Act // 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); list.AddRange(testDataArray);
} }
@@ -90,19 +111,25 @@ namespace Recyclable.CollectionsTests
// Validate // Validate
_ = list.Capacity.Should().Be(RecyclableDefaults.InitialCapacity); _ = list.Capacity.Should().Be(RecyclableDefaults.InitialCapacity);
_ = list.Count.Should().Be(0); _ = list.Count.Should().Be(itemsCount);
_ = list.Should().BeEmpty(); _ = list.Should().BeEmpty();
} }
[Theory] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // Prepare
using var list = new RecyclableList<long>(); using var list = new RecyclableList<long>();
// Act // 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);
list.AddRange(testDataArray); list.AddRange(testDataArray);
@@ -138,23 +165,18 @@ namespace Recyclable.CollectionsTests
} }
// Validate // Validate
var expectedData = testData.Concat(testData).ToArray(); var expectedData = testData.Concat(testData);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount * 2);
if (itemsCount > 0) _ = list.Count.Should().Be(itemsCount * 2);
{
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount * 2);
}
_ = list.Count.Should().Be(expectedData.Length);
_ = list.Should().Equal(expectedData); _ = list.Should().Equal(expectedData);
} }
[Theory] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // Prepare
testData = testData.Concat(testData).ToArray(); testData = testData.Concat(testData);
// Act // Act
using var list = new RecyclableList<long>(); using var list = new RecyclableList<long>();
@@ -164,13 +186,31 @@ namespace Recyclable.CollectionsTests
} }
// Validate // Validate
_ = list.Count.Should().Be((int)(itemsCount * 2)); _ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount * 2);
_ = list.Count.Should().Be(itemsCount * 2);
_ = list.Should().Equal(testData); _ = 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] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // Prepare
using var list = new RecyclableList<long>(); using var list = new RecyclableList<long>();
@@ -183,14 +223,14 @@ namespace Recyclable.CollectionsTests
} }
// Validate // Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); _ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.Count.Should().Be((int)itemsCount); _ = list.Count.Should().Be(itemsCount);
_ = list.Should().Equal(testData); _ = list.Should().Equal(testData);
} }
[Theory] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // Act
using var list = new RecyclableList<long>(); using var list = new RecyclableList<long>();
@@ -201,34 +241,56 @@ namespace Recyclable.CollectionsTests
} }
// Validate // 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)); _ = 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] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount); using var list = new RecyclableList<long>(testData, itemsCount);
_ = list.Count.Should().Be((int)itemsCount); _ = list.Count.Should().Be(itemsCount);
// Act // Act
list.Clear(); list.Clear();
list.Clear(); list.Clear();
// Validate // Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); _ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.Count.Should().Be(0); _ = list.Count.Should().Be(0);
_ = list.Should().BeEmpty(); _ = list.Should().BeEmpty();
} }
[Theory] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount); using var list = new RecyclableList<long>(testData, itemsCount);
list.Dispose(); list.Dispose();
// Act // Act
@@ -237,41 +299,45 @@ namespace Recyclable.CollectionsTests
[Theory] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // Act
using var list = new RecyclableList<long>(testData.Concat(testData), (int)itemsCount * 2); using var list = new RecyclableList<long>(testData, itemsCount * 2);
// Validate // Validate
_ = list.Count.Should().Be((int)itemsCount * 2); _ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount * 2);
_ = list.Should().Equal(testData.Concat(testData)); _ = list.Count.Should().Be(itemsCount * 2);
_ = list.Should().Equal(testData);
} }
[Theory] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // Act
using var list = new RecyclableList<long>(testData, (int)itemsCount); using var list = new RecyclableList<long>(testData, itemsCount);
// Validate // 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.Count.Should().Be((int)itemsCount); _ = list.Count.Should().Be(itemsCount);
_ = list.Should().Equal(testData); _ = list.Should().Equal(testData);
} }
[Theory] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // 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"); _ = testData.Any().Should().BeTrue("we need items on the list that we can look for");
// Act & Validate // Act & Validate
foreach (var itemIndex in itemIndexes) 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] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // 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"); _ = testData.Any().Should().BeTrue("we need items on the list that we can look for");
// Act & Validate // Act & Validate
@@ -303,10 +369,10 @@ namespace Recyclable.CollectionsTests
[Theory] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount); using var list = new RecyclableList<long>(testData, itemsCount);
long[] copiedItems = new long[itemsCount]; long[] copiedItems = new long[itemsCount];
// Act // Act
@@ -317,14 +383,72 @@ namespace Recyclable.CollectionsTests
} }
[Theory] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] [MemberData(nameof(RecyclableLongListTestData.SourceRefDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void EnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable<long> testData, long itemsCount) public void DisposeShouldClearItemsWhenReferenceType(string testCase, IEnumerable<object> testData, int itemsCount)
{ {
// Prepare if (itemsCount == 0)
using var list = new RecyclableList<long>(testData, (int)itemsCount); {
/// 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 // 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(); using var enumerator = list.GetEnumerator();
while (enumerator.MoveNext()) while (enumerator.MoveNext())
{ {
@@ -332,10 +456,23 @@ namespace Recyclable.CollectionsTests
} }
// Validate // Validate
_ = yieldedItems.Count.Should().Be((int)itemsCount);
_ = yieldedItems.Should().Equal(testData); _ = 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] [Fact]
public void IndexOfShouldNotFindAnythingWhenListEmpty() public void IndexOfShouldNotFindAnythingWhenListEmpty()
{ {
@@ -349,10 +486,10 @@ namespace Recyclable.CollectionsTests
[Theory] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // 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"); _ = testData.Any().Should().BeTrue("we need items on the list that we can look for");
// Act & Validate // Act & Validate
@@ -364,10 +501,10 @@ namespace Recyclable.CollectionsTests
[Theory] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount); using var list = new RecyclableList<long>(testData, itemsCount);
// Act & Validate // Act & Validate
foreach (var itemIndex in itemIndexes) foreach (var itemIndex in itemIndexes)
@@ -378,17 +515,21 @@ namespace Recyclable.CollectionsTests
[Theory] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] [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) foreach (var itemIndex in itemIndexes)
{ {
// Prepare
var testDataList = testData.ToList();
var item = testDataList[(int)itemIndex];
// Act // Act
using RecyclableList<long> list = new(testData, (int)itemsCount); using RecyclableList<long> list = new(testData);
QuickSortExtensions<long>.QuickSort(list); QuickSortExtensions<long>.QuickSort(list);
// Validate // Validate
_ = list.Count.Should().Be((int)itemsCount); _ = list.Count.Should().Be(itemsCount);
_ = list.Should().Equal(testData); _ = list.Should().Equal(testDataList);
// Act // Act
list.Clear(); list.Clear();
@@ -401,22 +542,29 @@ namespace Recyclable.CollectionsTests
list.AddRange(testData); list.AddRange(testData);
// Validate // Validate
_ = list.Count.Should().Be((int)itemsCount); _ = list.Count.Should().Be(itemsCount);
_ = list.Should().Equal(testData); _ = list.Should().Equal(testDataList);
// Prepare
testDataList.RemoveAt((int)itemIndex);
// Act // 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] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] [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) foreach (var itemIndex in itemIndexes)
{ {
// Prepare // Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount); using var list = new RecyclableList<long>(testData, itemsCount);
var item = list[(int)itemIndex]; var item = list[(int)itemIndex];
var expectedItems = list.ToList(); var expectedItems = list.ToList();
expectedItems.Insert((int)itemIndex, item); expectedItems.Insert((int)itemIndex, item);
@@ -425,22 +573,40 @@ namespace Recyclable.CollectionsTests
list.Insert((int)itemIndex, item); list.Insert((int)itemIndex, item);
// Validate // Validate
_ = list.Count.Should().Be((int)itemsCount + 1);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount);
_ = list[(int)itemIndex].Should().Be(item); _ = list[(int)itemIndex].Should().Be(item);
_ = list[(int)itemIndex + 1].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); _ = 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] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] [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) foreach (var itemIndex in itemIndexes)
{ {
// Prepare // Prepare
using var list = new RecyclableList<long>(testData, (int)itemsCount); using var list = new RecyclableList<long>(testData, itemsCount);
var expectedItems = testData.ToList(); var expectedItems = testData.ToList();
expectedItems.RemoveAt((int)itemIndex); expectedItems.RemoveAt((int)itemIndex);
@@ -448,42 +614,103 @@ namespace Recyclable.CollectionsTests
list.RemoveAt((int)itemIndex); list.RemoveAt((int)itemIndex);
// Validate // Validate
_ = list.Count.Should().Be((int)(itemsCount - 1)); _ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.Count.Should().Be(itemsCount - 1);
_ = list.Should().Equal(expectedItems); _ = 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] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] [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) foreach (var itemIndex in itemIndexes)
{ {
// Prepare // 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 = testData.ToList(); var expectedItems = testData.ToList();
var item = list[(int)itemIndex];
expectedItems.RemoveAt((int)itemIndex); expectedItems.RemoveAt((int)itemIndex);
// Act // Act
_ = list.Remove(item).Should().BeTrue(); _ = list.Remove(item).Should().BeTrue();
// Validate // Validate
_ = list.Count.Should().Be((int)(itemsCount - 1)); _ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.Count.Should().Be(itemsCount - 1);
_ = list.Should().Equal(expectedItems); _ = list.Should().Equal(expectedItems);
} }
} }
[Theory] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // 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 // 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) foreach (var item in (IRecyclableVersionedList<long>)list)
{ {
yieldedItems.Add(item); yieldedItems.Add(item);
@@ -30,7 +30,7 @@ namespace Recyclable.CollectionsTests
public static readonly IEnumerable<long> ItemsCountVariants = new long[] 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 //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[] public static readonly IEnumerable<int> BlockSizeVariants = new int[]
@@ -83,9 +83,10 @@ namespace Recyclable.CollectionsTests
switch (refType) switch (refType)
{ {
case false: 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[] { $"int[{itemsCount}]", testData, itemsCount };
yield return new object[] { $"ReadOnlySpan[{itemsCount}]", testData, itemsCount };
//GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true);
yield return new object[] { $"List<int>(itemsCount: {itemsCount}", testData.ToList(), itemsCount }; 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) private static long CalculateExpectedCapacity(string testCase, long itemsCount, int targetBlockSize)
{ {
var expectedCapacity = (long)BitOperations.RoundUpToPowerOf2((uint)itemsCount + 1); var expectedCapacity = (long)BitOperations.RoundUpToPowerOf2((uint)itemsCount + 1);
if (testCase.Contains("non-enumerated")) if (targetBlockSize > itemsCount)
{
expectedCapacity = (long)BitOperations.RoundUpToPowerOf2((ulong)itemsCount + RecyclableDefaults.BlockSize);
expectedCapacity = expectedCapacity >= RecyclableDefaults.InitialCapacity ? expectedCapacity : RecyclableDefaults.InitialCapacity;
}
else if (targetBlockSize > itemsCount)
{ {
expectedCapacity = ((itemsCount / targetBlockSize) + (itemsCount % targetBlockSize > 0 ? 1 : 0)) * targetBlockSize; expectedCapacity = ((itemsCount / targetBlockSize) + (itemsCount % targetBlockSize > 0 ? 1 : 0)) * targetBlockSize;
} }
@@ -25,6 +20,19 @@ namespace Recyclable.CollectionsTests
return expectedCapacity; 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] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))] [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize) 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); using var list = new RecyclableLongList<long>(minBlockSize: targetBlockSize);
// Act // 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); list.AddRange(testDataArray);
} }
@@ -63,9 +75,9 @@ namespace Recyclable.CollectionsTests
} }
// Validate // Validate
int expectedItemsCount = testData.Count(); long expectedItemsCount = itemsCount;
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(expectedItemsCount, "when capacity == 0, then we should allocate as much memory as needed, only"); _ = list.Capacity.Should().BeGreaterThanOrEqualTo(expectedItemsCount);
_ = list.LastBlockWithData.Should().Be((int)(itemsCount / list.BlockSize) - (itemsCount % list.BlockSize != 0 ? 0 : 1)); _ = list.LastBlockWithData.Should().Be((int)(itemsCount / targetBlockSize) - (itemsCount % targetBlockSize != 0 ? 0 : 1));
_ = list.LongCount.Should().Be(expectedItemsCount); _ = list.LongCount.Should().Be(expectedItemsCount);
_ = list.Should().Equal(testData); _ = list.Should().Equal(testData);
} }
@@ -78,7 +90,11 @@ namespace Recyclable.CollectionsTests
using var list = new RecyclableLongList<long>(); using var list = new RecyclableLongList<long>();
// Act // 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); list.AddRange(testDataArray);
} }
@@ -110,7 +126,7 @@ namespace Recyclable.CollectionsTests
// Validate // Validate
_ = list.Capacity.Should().Be(0); _ = list.Capacity.Should().Be(0);
_ = list.LastBlockWithData.Should().Be(-1); _ = list.LastBlockWithData.Should().Be(-1);
_ = list.LongCount.Should().Be(0); _ = list.LongCount.Should().Be(itemsCount);
_ = list.Should().BeEmpty(); _ = list.Should().BeEmpty();
} }
@@ -122,7 +138,13 @@ namespace Recyclable.CollectionsTests
using var list = new RecyclableLongList<long>(minBlockSize: targetBlockSize); using var list = new RecyclableLongList<long>(minBlockSize: targetBlockSize);
// Act // 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);
list.AddRange(testDataArray); list.AddRange(testDataArray);
@@ -158,14 +180,9 @@ namespace Recyclable.CollectionsTests
} }
// Validate // Validate
var expectedData = testData.Concat(testData).ToArray(); var expectedData = testData.Concat(testData);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount * 2);
if (itemsCount > 0) _ = list.LongCount.Should().Be(itemsCount * 2);
{
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(targetBlockSize);
}
_ = list.LongCount.Should().Be(expectedData.Length);
_ = list.LastBlockWithData.Should().Be((int)(itemsCount * 2 / list.BlockSize) - (itemsCount * 2 % list.BlockSize != 0 ? 0 : 1)); _ = list.LastBlockWithData.Should().Be((int)(itemsCount * 2 / list.BlockSize) - (itemsCount * 2 % list.BlockSize != 0 ? 0 : 1));
_ = list.Should().Equal(expectedData); _ = list.Should().Equal(expectedData);
} }
@@ -175,7 +192,7 @@ namespace Recyclable.CollectionsTests
public void AddShouldAcceptDuplicates(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize) public void AddShouldAcceptDuplicates(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
{ {
// Prepare // Prepare
testData = testData.Concat(testData).ToArray(); testData = testData.Concat(testData);
// Act // Act
using var list = new RecyclableLongList<long>(minBlockSize: targetBlockSize); using var list = new RecyclableLongList<long>(minBlockSize: targetBlockSize);
@@ -185,11 +202,29 @@ namespace Recyclable.CollectionsTests
} }
// Validate // Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount * 2);
_ = list.LongCount.Should().Be(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.LastBlockWithData.Should().Be((int)(itemsCount * 2 / list.BlockSize) - (itemsCount * 2 % list.BlockSize != 0 ? 0 : 1));
_ = list.Should().Equal(testData); _ = 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] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))] [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void AddShouldAddItems(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize) public void AddShouldAddItems(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
@@ -201,9 +236,11 @@ namespace Recyclable.CollectionsTests
foreach (var index in testData) foreach (var index in testData)
{ {
list.Add(index); list.Add(index);
_ = list.LongCount.Should().Be(index);
} }
// Validate // Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount);
_ = list.LongCount.Should().Be(itemsCount, "we added so many items"); _ = 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.LastBlockWithData.Should().Be((int)(itemsCount / list.BlockSize) - (itemsCount % list.BlockSize == 0 ? 1 : 0));
_ = list.Should().Equal(testData); _ = list.Should().Equal(testData);
@@ -223,11 +260,38 @@ namespace Recyclable.CollectionsTests
} }
// Validate // Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo(itemsCount * 2);
_ = list.LongCount.Should().Be(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.LastBlockWithData.Should().Be((int)(itemsCount * 2 / list.BlockSize) - (itemsCount * 2 % list.BlockSize != 0 ? 0 : 1));
_ = list.Should().Equal(testData.Concat(testData)); _ = 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] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))] [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void ClearShouldRemoveAllItems(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize) public void ClearShouldRemoveAllItems(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
@@ -241,10 +305,31 @@ namespace Recyclable.CollectionsTests
list.Clear(); list.Clear();
// Validate // Validate
_ = list.Capacity.Should().Be(0);
_ = list.LastBlockWithData.Should().Be(-1);
_ = list.LongCount.Should().Be(0); _ = list.LongCount.Should().Be(0);
_ = list.Should().BeEmpty(); _ = 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] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))] [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void ConsecutiveDisposeShouldSucceed(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize) 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) public void ConstructorShouldAcceptDuplicates(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
{ {
// Prepare // Prepare
testData = testData.Concat(testData).ToArray(); testData = testData.Concat(testData);
// Act // Act
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount); using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount);
// Validate // 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.LongCount.Should().Be(itemsCount * 2);
_ = list.Should().Equal(testData); _ = list.Should().Equal(testData);
} }
@@ -280,7 +367,8 @@ namespace Recyclable.CollectionsTests
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize); using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize);
// Validate // 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.LongCount.Should().Be(itemsCount);
_ = list.Should().Equal(testData); _ = list.Should().Equal(testData);
} }
@@ -334,11 +422,57 @@ namespace Recyclable.CollectionsTests
{ {
// Prepare // Prepare
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount); using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount);
long[] copiedItems = new long[itemsCount];
// Act // Act
var actual = new long[itemsCount]; list.CopyTo(copiedItems, 0);
list.CopyTo(actual, 0);
_ = actual.Should().Equal(testData); // 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] [Theory]
@@ -352,27 +486,29 @@ namespace Recyclable.CollectionsTests
list.Dispose(); list.Dispose();
// Validate // Validate
_ = list.LongCount.Should().Be(0L); _ = list.Capacity.Should().Be(0);
_ = list.LastBlockWithData.Should().Be(-1);
_ = list.LongCount.Should().Be(0);
_ = list.Should().BeEmpty(); _ = list.Should().BeEmpty();
} }
[Theory] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // Prepare
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount); using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount);
// Act // Act
var actual = new List<long>((int)itemsCount); var yieldedItems = new List<long>(itemsCount);
using var enumerator = list.GetEnumerator(); using var enumerator = list.GetEnumerator();
while (enumerator.MoveNext()) while (enumerator.MoveNext())
{ {
actual.Add(enumerator.Current); yieldedItems.Add(enumerator.Current);
} }
// Validate // Validate
_ = actual.Should().Equal(testData); _ = yieldedItems.Should().Equal(testData);
} }
[Theory] [Theory]
@@ -383,7 +519,21 @@ namespace Recyclable.CollectionsTests
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount); using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount);
// Act & Validate // 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); _ = list[item - 1].Should().Be(item);
} }
@@ -436,7 +586,7 @@ namespace Recyclable.CollectionsTests
foreach (var itemIndex in itemIndexes) foreach (var itemIndex in itemIndexes)
{ {
// Prepare // Prepare
using var testDataList = testData.ToRecyclableList(); var testDataList = testData.ToList();
var item = testDataList[(int)itemIndex]; var item = testDataList[(int)itemIndex];
// Act // Act
@@ -477,23 +627,52 @@ namespace Recyclable.CollectionsTests
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void InsertShouldMoveItems(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize, in long[] itemIndexes) public void InsertShouldMoveItems(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize, in long[] itemIndexes)
{ {
foreach (var itemIndex in itemIndexes) foreach (var itemIndex in itemIndexes)
{ {
// Prepare // Prepare
using var list = new RecyclableLongList<long>(testData, targetBlockSize, itemsCount); using var list = new RecyclableLongList<long>(testData, targetBlockSize, itemsCount);
long expectedCapacity = CalculateExpectedCapacity(testCase, itemsCount, targetBlockSize); long expectedCapacity = CalculateExpectedCapacity(testCase, itemsCount, targetBlockSize);
var expectedItems = testData.ToList();
var item = list[itemIndex]; var existingItem = expectedItems[(int)itemIndex];
var expectedItems = list.ToList(); var newItem = existingItem + 1;
expectedItems.Insert((int)itemIndex, item); expectedItems.Insert((int)itemIndex, newItem);
// Act // Act
list.Insert(itemIndex, item); list.Insert((int)itemIndex, newItem);
// Validate // 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); [Theory]
_ = list[itemIndex + 1].Should().Be(item); [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.Capacity.Should().Be(expectedCapacity);
_ = list.Count.Should().Be((int)itemsCount + 1); _ = list.Count.Should().Be((int)itemsCount + 1);
_ = list.Should().Equal(expectedItems); _ = 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] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] [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) foreach (var itemIndex in itemIndexes)
{ {
@@ -555,13 +774,34 @@ namespace Recyclable.CollectionsTests
list.RemoveAt(itemIndex); list.RemoveAt(itemIndex);
// Validate // 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.LastBlockWithData.Should().Be((int)(list.LongCount >> list.BlockSizePow2BitShift) - ((list.LongCount & list.BlockSizeMinus1) != 0 ? 0 : 1));
_ = list.LongCount.Should().Be(itemsCount - 1); _ = list.LongCount.Should().Be(itemsCount - 1);
_ = list.Should().Equal(expectedItems); _ = 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] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void RemoveShouldRemoveTheCorrectItem(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize, in long[] itemIndexes) 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(); _ = list.Remove(item).Should().BeTrue();
// Validate // 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.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.Should().Equal(expectedItems);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount);
} }
} }
[Theory] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))] [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 // Prepare
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount); using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount);
// Act // Act
using var actual = new RecyclableList<long>((int)itemsCount); var yieldedItems = new List<long>(itemsCount);
foreach (var item in (IRecyclableVersionedLongList<long>)list) foreach (var item in (IRecyclableVersionedLongList<long>)list)
{ {
actual.Add(item); yieldedItems.Add(item);
} }
// Validate // Validate
_ = actual.Should().Equal(testData); _ = yieldedItems.Should().Equal(testData);
} }
} }
} }