Files
sqrtspace-dotnet/tests/SqrtSpace.SpaceTime.Tests/Collections/AdaptiveDictionaryTests.cs
2025-07-20 03:41:39 -04:00

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);
}
}