344 lines
9.0 KiB
C#
344 lines
9.0 KiB
C#
|
|
using System;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using System.Linq;
|
||
|
|
using FluentAssertions;
|
||
|
|
using SqrtSpace.SpaceTime.Collections;
|
||
|
|
using Xunit;
|
||
|
|
|
||
|
|
namespace SqrtSpace.SpaceTime.Tests.Collections;
|
||
|
|
|
||
|
|
public class AdaptiveDictionaryTests
|
||
|
|
{
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_StartsAsArray()
|
||
|
|
{
|
||
|
|
// Arrange & Act
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(0);
|
||
|
|
dict["one"] = 1;
|
||
|
|
dict["two"] = 2;
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
dict.Count.Should().Be(2);
|
||
|
|
dict.CurrentImplementation.Should().Be(ImplementationType.Array);
|
||
|
|
dict["one"].Should().Be(1);
|
||
|
|
dict["two"].Should().Be(2);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_TransitionsFromArrayToDictionary()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(0);
|
||
|
|
|
||
|
|
// Act - Add items up to array threshold
|
||
|
|
for (int i = 0; i < 20; i++) // ArrayThreshold is typically 16
|
||
|
|
{
|
||
|
|
dict[$"key{i}"] = i;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
dict.CurrentImplementation.Should().Be(ImplementationType.Dictionary);
|
||
|
|
dict.Count.Should().Be(20);
|
||
|
|
dict["key10"].Should().Be(10);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_TransitionsToBTree()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<int, string>(0);
|
||
|
|
|
||
|
|
// Act - Add items beyond dictionary threshold
|
||
|
|
for (int i = 0; i < 15_000; i++) // DictionaryThreshold is typically 10,000
|
||
|
|
{
|
||
|
|
dict[i] = $"value{i}";
|
||
|
|
}
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
dict.CurrentImplementation.Should().Be(ImplementationType.SortedDictionary);
|
||
|
|
dict.Count.Should().Be(15_000);
|
||
|
|
dict[5000].Should().Be("value5000");
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_TransitionsToExternal()
|
||
|
|
{
|
||
|
|
// Skip this test if running in CI or memory-constrained environment
|
||
|
|
if (Environment.GetEnvironmentVariable("CI") == "true")
|
||
|
|
return;
|
||
|
|
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<int, string>(0); // Use default thresholds
|
||
|
|
|
||
|
|
// Act
|
||
|
|
for (int i = 0; i < 1500; i++)
|
||
|
|
{
|
||
|
|
dict[i] = $"value{i}";
|
||
|
|
}
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
dict.CurrentImplementation.Should().Be(ImplementationType.External);
|
||
|
|
dict.Count.Should().Be(1500);
|
||
|
|
dict[750].Should().Be("value750");
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_Add_AddsNewItem()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(0);
|
||
|
|
|
||
|
|
// Act
|
||
|
|
dict.Add("one", 1);
|
||
|
|
dict.Add("two", 2);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
dict.Count.Should().Be(2);
|
||
|
|
dict.ContainsKey("one").Should().BeTrue();
|
||
|
|
dict.ContainsKey("two").Should().BeTrue();
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_Add_ThrowsOnDuplicate()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(0);
|
||
|
|
dict.Add("one", 1);
|
||
|
|
|
||
|
|
// Act & Assert
|
||
|
|
var action = () => dict.Add("one", 2);
|
||
|
|
action.Should().Throw<ArgumentException>();
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_Remove_RemovesItem()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(0);
|
||
|
|
dict["one"] = 1;
|
||
|
|
dict["two"] = 2;
|
||
|
|
dict["three"] = 3;
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var removed = dict.Remove("two");
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
removed.Should().BeTrue();
|
||
|
|
dict.Count.Should().Be(2);
|
||
|
|
dict.ContainsKey("two").Should().BeFalse();
|
||
|
|
dict["one"].Should().Be(1);
|
||
|
|
dict["three"].Should().Be(3);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_Remove_ReturnsFalseForNonExistent()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(0);
|
||
|
|
dict["one"] = 1;
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var removed = dict.Remove("two");
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
removed.Should().BeFalse();
|
||
|
|
dict.Count.Should().Be(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_TryGetValue_GetsExistingValue()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(0);
|
||
|
|
dict["one"] = 1;
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var found = dict.TryGetValue("one", out var value);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
found.Should().BeTrue();
|
||
|
|
value.Should().Be(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_TryGetValue_ReturnsFalseForNonExistent()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(0);
|
||
|
|
dict["one"] = 1;
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var found = dict.TryGetValue("two", out var value);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
found.Should().BeFalse();
|
||
|
|
value.Should().Be(default(int));
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_Clear_RemovesAllItems()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(0);
|
||
|
|
for (int i = 0; i < 50; i++)
|
||
|
|
{
|
||
|
|
dict[$"key{i}"] = i;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Act
|
||
|
|
dict.Clear();
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
dict.Count.Should().Be(0);
|
||
|
|
dict.ContainsKey("key10").Should().BeFalse();
|
||
|
|
dict.CurrentImplementation.Should().Be(ImplementationType.Array); // Reset to array
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_Keys_ReturnsAllKeys()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(0);
|
||
|
|
dict["one"] = 1;
|
||
|
|
dict["two"] = 2;
|
||
|
|
dict["three"] = 3;
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var keys = dict.Keys.ToList();
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
keys.Should().HaveCount(3);
|
||
|
|
keys.Should().BeEquivalentTo(new[] { "one", "two", "three" });
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_Values_ReturnsAllValues()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(0);
|
||
|
|
dict["one"] = 1;
|
||
|
|
dict["two"] = 2;
|
||
|
|
dict["three"] = 3;
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var values = dict.Values.ToList();
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
values.Should().HaveCount(3);
|
||
|
|
values.Should().BeEquivalentTo(new[] { 1, 2, 3 });
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_Enumeration_ReturnsAllPairs()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(0);
|
||
|
|
dict["one"] = 1;
|
||
|
|
dict["two"] = 2;
|
||
|
|
dict["three"] = 3;
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var pairs = dict.ToList();
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
pairs.Should().HaveCount(3);
|
||
|
|
pairs.Should().Contain(kvp => kvp.Key == "one" && kvp.Value == 1);
|
||
|
|
pairs.Should().Contain(kvp => kvp.Key == "two" && kvp.Value == 2);
|
||
|
|
pairs.Should().Contain(kvp => kvp.Key == "three" && kvp.Value == 3);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_WithForceStrategy_UsesSpecifiedImplementation()
|
||
|
|
{
|
||
|
|
// Arrange & Act
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(0,
|
||
|
|
strategy: AdaptiveStrategy.ForceDictionary);
|
||
|
|
|
||
|
|
dict["one"] = 1;
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
dict.CurrentImplementation.Should().Be(ImplementationType.Dictionary);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_CopyTo_CopiesAllPairs()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(0);
|
||
|
|
dict["one"] = 1;
|
||
|
|
dict["two"] = 2;
|
||
|
|
dict["three"] = 3;
|
||
|
|
var array = new KeyValuePair<string, int>[5];
|
||
|
|
|
||
|
|
// Act
|
||
|
|
dict.CopyTo(array, 1);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
array[0].Should().Be(default(KeyValuePair<string, int>));
|
||
|
|
array.Skip(1).Take(3).Should().BeEquivalentTo(dict);
|
||
|
|
array[4].Should().Be(default(KeyValuePair<string, int>));
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_DataPersistenceAcrossTransitions()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<int, string>(0);
|
||
|
|
var testData = Enumerable.Range(0, 100)
|
||
|
|
.ToDictionary(i => i, i => $"value{i}");
|
||
|
|
|
||
|
|
// Act - Add data forcing multiple transitions
|
||
|
|
foreach (var kvp in testData)
|
||
|
|
{
|
||
|
|
dict[kvp.Key] = kvp.Value;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Assert - Verify all data is preserved
|
||
|
|
dict.Count.Should().Be(100);
|
||
|
|
foreach (var kvp in testData)
|
||
|
|
{
|
||
|
|
dict[kvp.Key].Should().Be(kvp.Value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_ConcurrentModification_ThrowsException()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(0);
|
||
|
|
dict["one"] = 1;
|
||
|
|
dict["two"] = 2;
|
||
|
|
|
||
|
|
// Act & Assert
|
||
|
|
var action = () =>
|
||
|
|
{
|
||
|
|
foreach (var kvp in dict)
|
||
|
|
{
|
||
|
|
dict["three"] = 3; // Modify during enumeration
|
||
|
|
}
|
||
|
|
};
|
||
|
|
action.Should().Throw<InvalidOperationException>();
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_NullKey_ThrowsException()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(0);
|
||
|
|
|
||
|
|
// Act & Assert
|
||
|
|
var action = () => dict[null!] = 1;
|
||
|
|
action.Should().Throw<ArgumentNullException>();
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void AdaptiveDictionary_InitialCapacity_PreallocatesSpace()
|
||
|
|
{
|
||
|
|
// Arrange & Act
|
||
|
|
var dict = new AdaptiveDictionary<string, int>(capacity: 100);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
dict.Count.Should().Be(0);
|
||
|
|
dict.CurrentImplementation.Should().Be(ImplementationType.Dictionary);
|
||
|
|
}
|
||
|
|
}
|