PersistentMap/TestProject1/FuzzTest.cs
2026-02-11 12:37:03 +01:00

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