Unit tests extended. Various fixes. README.md updated.
This commit is contained in:
1 parent
98347f0727
commit
e941fdf8db
8 files changed
+779
-202
No files matched your search
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user