Did some code cleanup,
added some extra thingies. switched to spans. Let google gemini do whatever it wanted..
This commit is contained in:
parent
978d0873dc
commit
7bea233edc
11 changed files with 944 additions and 248 deletions
|
|
@ -21,7 +21,7 @@ public class BTreeFuzzTests
|
|||
// CONFIGURATION
|
||||
const int Iterations = 100_000; // High enough to trigger all splits/merges
|
||||
const int KeyRange = 5000; // Small enough to cause frequent collisions
|
||||
const bool showOps = true;
|
||||
const bool showOps = false;
|
||||
int Seed = 2135974; // Environment.TickCount;
|
||||
|
||||
// ORACLES
|
||||
|
|
@ -167,4 +167,4 @@ public class BTreeFuzzTests
|
|||
throw new Exception("Enumerator has extra items!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
181
TestProject1/OrderedQueriesTest.cs
Normal file
181
TestProject1/OrderedQueriesTest.cs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
using System.Linq;
|
||||
using Xunit;
|
||||
using PersistentMap;
|
||||
|
||||
namespace PersistentMap.Tests
|
||||
{
|
||||
public class BTreeExtendedOperationsTests
|
||||
{
|
||||
// Helper to quickly spin up a populated map
|
||||
private TransientMap<int, string, IntStrategy> CreateMap(params int[] keys)
|
||||
{
|
||||
var map = BaseOrderedMap<int, string, IntStrategy>.CreateTransient(new IntStrategy());
|
||||
foreach (var key in keys)
|
||||
{
|
||||
map.Set(key, $"val_{key}");
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinMax_OnEmptyTree_ReturnsFalse()
|
||||
{
|
||||
var map = CreateMap();
|
||||
|
||||
bool hasMin = map.TryGetMin(out int minKey, out string minVal);
|
||||
bool hasMax = map.TryGetMax(out int maxKey, out string maxVal);
|
||||
|
||||
Assert.False(hasMin);
|
||||
Assert.False(hasMax);
|
||||
Assert.Equal(default, minKey);
|
||||
Assert.Equal(default, maxKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinMax_OnPopulatedTree_ReturnsCorrectExtremes()
|
||||
{
|
||||
var map = CreateMap(50, 10, 40, 20, 30); // Insert out of order
|
||||
|
||||
bool hasMin = map.TryGetMin(out int minKey, out string minVal);
|
||||
bool hasMax = map.TryGetMax(out int maxKey, out string maxVal);
|
||||
|
||||
Assert.True(hasMin);
|
||||
Assert.Equal(10, minKey);
|
||||
Assert.Equal("val_10", minVal);
|
||||
|
||||
Assert.True(hasMax);
|
||||
Assert.Equal(50, maxKey);
|
||||
Assert.Equal("val_50", maxVal);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(20, true, 30)] // Exact match, get next
|
||||
[InlineData(25, true, 30)] // Missing key, gets first greater
|
||||
[InlineData(50, false, 0)] // Max element has no successor
|
||||
[InlineData(60, false, 0)] // Out of bounds high
|
||||
public void Successor_ReturnsCorrectNextKey(int searchKey, bool expectedSuccess, int expectedNextKey)
|
||||
{
|
||||
var map = CreateMap(10, 20, 30, 40, 50);
|
||||
|
||||
bool success = map.TryGetSuccessor(searchKey, out int nextKey, out string nextVal);
|
||||
|
||||
Assert.Equal(expectedSuccess, success);
|
||||
if (expectedSuccess)
|
||||
{
|
||||
Assert.Equal(expectedNextKey, nextKey);
|
||||
Assert.Equal($"val_{expectedNextKey}", nextVal);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(40, true, 30)] // Exact match, get previous
|
||||
[InlineData(35, true, 30)] // Missing key, gets largest smaller
|
||||
[InlineData(10, false, 0)] // Min element has no predecessor
|
||||
[InlineData(5, false, 0)] // Out of bounds low
|
||||
public void Predecessor_ReturnsCorrectPreviousKey(int searchKey, bool expectedSuccess, int expectedPrevKey)
|
||||
{
|
||||
var map = CreateMap(10, 20, 30, 40, 50);
|
||||
|
||||
bool success = map.TryGetPredecessor(searchKey, out int prevKey, out string prevVal);
|
||||
|
||||
Assert.Equal(expectedSuccess, success);
|
||||
if (expectedSuccess)
|
||||
{
|
||||
Assert.Equal(expectedPrevKey, prevKey);
|
||||
Assert.Equal($"val_{expectedPrevKey}", prevVal);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SuccessorPredecessor_CrossNodeBoundaries_WorksCorrectly()
|
||||
{
|
||||
// Insert 200 elements to guarantee leaf node splits (Capacity is 64)
|
||||
// and internal node creation.
|
||||
var keys = Enumerable.Range(1, 200).ToArray();
|
||||
var map = CreateMap(keys);
|
||||
|
||||
// Test boundaries between multiple leaves
|
||||
for (int i = 1; i < 200; i++)
|
||||
{
|
||||
// Successor
|
||||
bool sFound = map.TryGetSuccessor(i, out int next, out _);
|
||||
Assert.True(sFound);
|
||||
Assert.Equal(i + 1, next);
|
||||
|
||||
// Predecessor
|
||||
bool pFound = map.TryGetPredecessor(i + 1, out int prev, out _);
|
||||
Assert.True(pFound);
|
||||
Assert.Equal(i, prev);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetOperations_Intersect_ReturnsCommonElements()
|
||||
{
|
||||
var mapA = CreateMap(1, 2, 3, 4, 5);
|
||||
var mapB = CreateMap(4, 5, 6, 7, 8);
|
||||
|
||||
var intersect = mapA.Intersect(mapB).Select(kvp => kvp.Key).ToArray();
|
||||
|
||||
Assert.Equal(new[] { 4, 5 }, intersect);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetOperations_Except_ReturnsElementsOnlyInFirstMap()
|
||||
{
|
||||
var mapA = CreateMap(1, 2, 3, 4, 5);
|
||||
var mapB = CreateMap(4, 5, 6, 7, 8);
|
||||
|
||||
var aExceptB = mapA.Except(mapB).Select(kvp => kvp.Key).ToArray();
|
||||
var bExceptA = mapB.Except(mapA).Select(kvp => kvp.Key).ToArray();
|
||||
|
||||
Assert.Equal(new[] { 1, 2, 3 }, aExceptB);
|
||||
Assert.Equal(new[] { 6, 7, 8 }, bExceptA);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetOperations_SymmetricExcept_ReturnsNonOverlappingElements()
|
||||
{
|
||||
var mapA = CreateMap(1, 2, 3, 4, 5);
|
||||
var mapB = CreateMap(4, 5, 6, 7, 8);
|
||||
|
||||
var symmetric = mapA.SymmetricExcept(mapB).Select(kvp => kvp.Key).ToArray();
|
||||
|
||||
// Should return elements exclusively in A or B, but not both.
|
||||
// Expected sorted naturally: 1, 2, 3, 6, 7, 8
|
||||
Assert.Equal(new[] { 1, 2, 3, 6, 7, 8 }, symmetric);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetOperations_WithEmptyMaps_HandleGracefully()
|
||||
{
|
||||
var populatedMap = CreateMap(1, 2, 3);
|
||||
var emptyMap = CreateMap();
|
||||
|
||||
// Intersect with empty is empty
|
||||
Assert.Empty(populatedMap.Intersect(emptyMap));
|
||||
Assert.Empty(emptyMap.Intersect(populatedMap));
|
||||
|
||||
// Populated Except empty is Populated
|
||||
Assert.Equal(new[] { 1, 2, 3 }, populatedMap.Except(emptyMap).Select(k => k.Key));
|
||||
|
||||
// Empty Except Populated is empty
|
||||
Assert.Empty(emptyMap.Except(populatedMap));
|
||||
|
||||
// Symmetric Except with empty is just the populated map
|
||||
Assert.Equal(new[] { 1, 2, 3 }, populatedMap.SymmetricExcept(emptyMap).Select(k => k.Key));
|
||||
Assert.Equal(new[] { 1, 2, 3 }, emptyMap.SymmetricExcept(populatedMap).Select(k => k.Key));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetOperations_CompleteOverlap_HandlesCorrectly()
|
||||
{
|
||||
var mapA = CreateMap(1, 2, 3);
|
||||
var mapB = CreateMap(1, 2, 3);
|
||||
|
||||
Assert.Equal(new[] { 1, 2, 3 }, mapA.Intersect(mapB).Select(k => k.Key));
|
||||
Assert.Empty(mapA.Except(mapB));
|
||||
Assert.Empty(mapA.SymmetricExcept(mapB));
|
||||
}
|
||||
}
|
||||
}
|
||||
25
TestProject1/TestProject1.csproj
Normal file
25
TestProject1/TestProject1.csproj
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
|
||||
<PackageReference Include="xunit" Version="2.9.3"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PersistentMap\PersistentMap.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
70
TestProject1/UnitTest1.cs
Normal file
70
TestProject1/UnitTest1.cs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
namespace TestProject1;
|
||||
|
||||
using Xunit;
|
||||
using PersistentMap;
|
||||
|
||||
public class BasicTests
|
||||
{
|
||||
private readonly UnicodeStrategy _strategy = new UnicodeStrategy();
|
||||
|
||||
[Fact]
|
||||
public void Transient_InsertAndGet_Works()
|
||||
{
|
||||
var map = BaseOrderedMap<string, int, UnicodeStrategy>.CreateTransient(_strategy);
|
||||
|
||||
map.Set("Apple", 1);
|
||||
map.Set("Banana", 2);
|
||||
map.Set("Cherry", 3);
|
||||
|
||||
Assert.True(map.TryGetValue("Apple", out int v1));
|
||||
Assert.Equal(1, v1);
|
||||
|
||||
Assert.True(map.TryGetValue("Banana", out int v2));
|
||||
Assert.Equal(2, v2);
|
||||
|
||||
Assert.False(map.TryGetValue("Date", out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Transient_Update_Works()
|
||||
{
|
||||
var map = BaseOrderedMap<string, int, UnicodeStrategy>.CreateTransient(_strategy);
|
||||
map.Set("Key", 100);
|
||||
map.Set("Key", 200); // Overwrite
|
||||
|
||||
map.TryGetValue("Key", out int val);
|
||||
Assert.Equal(200, val);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Transient_Remove_Works()
|
||||
{
|
||||
var map = BaseOrderedMap<string, int, UnicodeStrategy>.CreateTransient(_strategy);
|
||||
map.Set("A", 1);
|
||||
map.Set("B", 2);
|
||||
map.Set("C", 3);
|
||||
|
||||
map.Remove("B");
|
||||
|
||||
Assert.True(map.ContainsKey("A"));
|
||||
Assert.False(map.ContainsKey("B"));
|
||||
Assert.True(map.ContainsKey("C"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Transient_PrefixCollision_HandlesCollision()
|
||||
{
|
||||
// UnicodeStrategy only packs the first 4 chars.
|
||||
// "Test1" and "Test2" have the same prefix "Test".
|
||||
var map = BaseOrderedMap<string, int, UnicodeStrategy>.CreateTransient(_strategy);
|
||||
|
||||
map.Set("Test1", 1);
|
||||
map.Set("Test2", 2);
|
||||
|
||||
Assert.True(map.TryGetValue("Test1", out var v1));
|
||||
Assert.Equal(1, v1);
|
||||
|
||||
Assert.True(map.TryGetValue("Test2", out var v2));
|
||||
Assert.Equal(2, v2);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue