160 lines
No EOL
5.2 KiB
C#
160 lines
No EOL
5.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Xunit;
|
|
using Xunit.Abstractions;
|
|
using PersistentMap;
|
|
|
|
public class BTreeFuzzTests
|
|
{
|
|
private readonly ITestOutputHelper _output;
|
|
private readonly IntStrategy _strategy = new();
|
|
|
|
public BTreeFuzzTests(ITestOutputHelper output)
|
|
{
|
|
_output = output;
|
|
}
|
|
|
|
[Fact]
|
|
public void Fuzz_Insert_And_Remove_consistency()
|
|
{
|
|
// CONFIGURATION
|
|
const int Iterations = 100_000; // High enough to trigger all splits/merges
|
|
const int KeyRange = 5000; // Small enough to cause frequent collisions
|
|
int Seed = Environment.TickCount;
|
|
|
|
// ORACLES
|
|
var reference = new SortedDictionary<int, int>();
|
|
var subject = BaseOrderedMap<int, int, IntStrategy>.CreateTransient(_strategy);
|
|
|
|
var random = new Random(Seed);
|
|
_output.WriteLine($"Starting Fuzz Test with Seed: {Seed}");
|
|
|
|
try
|
|
{
|
|
for (int i = 0; i < Iterations; i++)
|
|
{
|
|
// 1. Pick an Action: 70% Insert/Update, 30% Remove
|
|
bool isInsert = random.NextDouble() < 0.7;
|
|
int key = random.Next(KeyRange);
|
|
int val = key * 100;
|
|
|
|
if (isInsert)
|
|
{
|
|
// ACTION: INSERT
|
|
reference[key] = val;
|
|
subject.Set(key, val);
|
|
}
|
|
else
|
|
{
|
|
// ACTION: REMOVE
|
|
if (reference.ContainsKey(key))
|
|
{
|
|
reference.Remove(key);
|
|
subject.Remove(key);
|
|
}
|
|
else
|
|
{
|
|
// Try removing non-existent key (should be safe)
|
|
subject.Remove(key);
|
|
}
|
|
}
|
|
|
|
// 2. VERIFY CONSISTENCY (Expensive but necessary)
|
|
// We check consistency every 1000 ops or if the tree is small,
|
|
// to keep the test fast enough.
|
|
if (i % 1000 == 0 || reference.Count < 100)
|
|
{
|
|
AssertConsistency(reference, subject);
|
|
}
|
|
}
|
|
|
|
// Final check
|
|
AssertConsistency(reference, subject);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
_output.WriteLine($"FAILED at iteration with SEED: {Seed}");
|
|
throw; // Re-throw to fail the test
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Fuzz_Range_Queries()
|
|
{
|
|
// Validates that your Range Enumerator matches LINQ on the reference
|
|
const int Iterations = 1000;
|
|
const int KeyRange = 2000;
|
|
int Seed = Environment.TickCount;
|
|
|
|
var reference = new SortedDictionary<int, int>();
|
|
var subject = BaseOrderedMap<int, int, IntStrategy>.CreateTransient(_strategy);
|
|
var random = new Random(Seed);
|
|
|
|
// Fill Data
|
|
for(int i=0; i<KeyRange; i++)
|
|
{
|
|
int k = random.Next(KeyRange);
|
|
reference[k] = k;
|
|
subject.Set(k, k);
|
|
}
|
|
|
|
var persistent = subject.ToPersistent();
|
|
|
|
for (int i = 0; i < Iterations; i++)
|
|
{
|
|
int min = random.Next(KeyRange);
|
|
int max = min + random.Next(KeyRange - min); // Ensure max >= min
|
|
|
|
// 1. Reference Result (LINQ)
|
|
// Note: SortedDictionary doesn't have a direct Range query, so we filter memory.
|
|
var expected = reference
|
|
.Where(kv => kv.Key >= min && kv.Key <= max)
|
|
.Select(kv => kv.Key)
|
|
.ToList();
|
|
|
|
// 2. Subject Result
|
|
var actual = persistent.Range(min, max)
|
|
.Select(kv => kv.Key)
|
|
.ToList();
|
|
|
|
// 3. Compare
|
|
if (!expected.SequenceEqual(actual))
|
|
{
|
|
_output.WriteLine($"Range Mismatch! Range: [{min}, {max}]");
|
|
_output.WriteLine($"Expected: {string.Join(",", expected)}");
|
|
_output.WriteLine($"Actual: {string.Join(",", actual)}");
|
|
Assert.Fail("Range query results differ.");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void AssertConsistency(SortedDictionary<int, int> expected, TransientMap<int, int, IntStrategy> actual)
|
|
{
|
|
// 1. Count
|
|
// if (expected.Count != actual.Count)
|
|
// {
|
|
// throw new Exception($"Count Mismatch! Expected {expected.Count}, Got {actual.Count}");
|
|
// }
|
|
|
|
// 2. Full Scan Verification
|
|
using var enumerator = actual.GetEnumerator();
|
|
foreach (var kvp in expected)
|
|
{
|
|
if (!enumerator.MoveNext())
|
|
{
|
|
throw new Exception("Enumerator ended too early!");
|
|
}
|
|
|
|
if (enumerator.Current.Key != kvp.Key || enumerator.Current.Value != kvp.Value)
|
|
{
|
|
throw new Exception($"Content Mismatch! Expected [{kvp.Key}:{kvp.Value}], Got [{enumerator.Current.Key}:{enumerator.Current.Value}]");
|
|
}
|
|
}
|
|
|
|
if (enumerator.MoveNext())
|
|
{
|
|
throw new Exception("Enumerator has extra items!");
|
|
}
|
|
}
|
|
} |