Files
CBDD/tests/CBDD.Tests/DictionaryPageTests.cs

212 lines
6.9 KiB
C#
Executable File

using ZB.MOM.WW.CBDD.Core;
using ZB.MOM.WW.CBDD.Core.Storage;
using System.Text;
using Xunit;
namespace ZB.MOM.WW.CBDD.Tests;
public class DictionaryPageTests
{
private const int PageSize = 16384;
[Fact]
public void Initialize_ShouldSetupEmptyPage()
{
var page = new byte[PageSize];
DictionaryPage.Initialize(page, 1);
var header = PageHeader.ReadFrom(page);
header.PageType.ShouldBe(PageType.Dictionary);
header.PageId.ShouldBe(1u);
var count = BitConverter.ToUInt16(page, 32); // CountOffset
count.ShouldBe((ushort)0);
var freeSpaceEnd = BitConverter.ToUInt16(page, 34); // FreeSpaceEndOffset
freeSpaceEnd.ShouldBe((ushort)PageSize);
}
[Fact]
public void Insert_ShouldAddEntryAndSort()
{
var page = new byte[PageSize];
DictionaryPage.Initialize(page, 1);
// Insert "B"
bool inserted = DictionaryPage.Insert(page, "B", 20);
inserted.ShouldBeTrue();
// Insert "A" (should go before B)
inserted = DictionaryPage.Insert(page, "A", 10);
inserted.ShouldBeTrue();
// Insert "C" (should go after B)
inserted = DictionaryPage.Insert(page, "C", 30);
inserted.ShouldBeTrue();
// Verify Order
var entries = DictionaryPage.GetAll(page).ToList();
entries.Count.ShouldBe(3);
entries[0].Key.ShouldBe("A");
entries[0].Value.ShouldBe((ushort)10);
entries[1].Key.ShouldBe("B");
entries[1].Value.ShouldBe((ushort)20);
entries[2].Key.ShouldBe("C");
entries[2].Value.ShouldBe((ushort)30);
}
[Fact]
public void TryFind_ShouldReturnCorrectValue()
{
var page = new byte[PageSize];
DictionaryPage.Initialize(page, 1);
DictionaryPage.Insert(page, "Key1", 100);
DictionaryPage.Insert(page, "Key2", 200);
DictionaryPage.Insert(page, "Key3", 300);
bool found = DictionaryPage.TryFind(page, Encoding.UTF8.GetBytes("Key2"), out ushort value);
found.ShouldBeTrue();
value.ShouldBe((ushort)200);
found = DictionaryPage.TryFind(page, Encoding.UTF8.GetBytes("Key999"), out value);
found.ShouldBeFalse();
}
[Fact]
public void Overflow_ShouldReturnFalse_WhenFull()
{
var page = new byte[PageSize];
DictionaryPage.Initialize(page, 1);
string bigKey = new string('X', 250);
int count = 0;
while (true)
{
// Use unique keys
var key = bigKey + count;
if (!DictionaryPage.Insert(page, key, (ushort)count))
{
// Should fail here
break;
}
count++;
if (count > 1000) throw new ShouldAssertException("Should have filled the page much earlier");
}
// Now page is full enough that `bigKey` (250 bytes) shouldn't fit.
// We can't guarantee a small key won't fit (fragmentation/remaining space),
// but a key of the SAME size that triggered the break should definitely fail.
bool inserted = DictionaryPage.Insert(page, bigKey + "X", 9999);
inserted.ShouldBeFalse();
}
[Fact]
public void Chaining_ShouldFindKeysInLinkedPages()
{
var dbPath = Path.Combine(Path.GetTempPath(), $"test_dict_chain_{Guid.NewGuid()}.db");
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
// 1. Create First Page
var page1Id = storage.AllocatePage();
var pageBuffer = new byte[storage.PageSize];
DictionaryPage.Initialize(pageBuffer, page1Id);
// Fill Page 1
DictionaryPage.Insert(pageBuffer, "Key1", 100);
DictionaryPage.Insert(pageBuffer, "KeyA", 200);
// 2. Create Second Page
var page2Id = storage.AllocatePage();
var page2Buffer = new byte[storage.PageSize];
DictionaryPage.Initialize(page2Buffer, page2Id);
// Fill Page 2
DictionaryPage.Insert(page2Buffer, "Key2", 300);
DictionaryPage.Insert(page2Buffer, "KeyB", 400); // 400
// 3. Link Page 1 -> Page 2
var header1 = PageHeader.ReadFrom(pageBuffer);
header1.NextPageId = page2Id;
header1.WriteTo(pageBuffer);
// 4. Write pages to storage
storage.WritePageImmediate(page1Id, pageBuffer);
storage.WritePageImmediate(page2Id, page2Buffer);
// 5. Test Global Find
// Find in Page 1
bool found = DictionaryPage.TryFindGlobal(storage, page1Id, "Key1", out ushort val);
found.ShouldBeTrue();
val.ShouldBe((ushort)100);
// Find in Page 2
found = DictionaryPage.TryFindGlobal(storage, page1Id, "KeyB", out val);
found.ShouldBeTrue();
val.ShouldBe((ushort)400);
// Not Found
found = DictionaryPage.TryFindGlobal(storage, page1Id, "KeyMissing", out val);
found.ShouldBeFalse();
storage.Dispose();
if (File.Exists(dbPath)) File.Delete(dbPath);
if (File.Exists(Path.ChangeExtension(dbPath, ".wal"))) File.Delete(Path.ChangeExtension(dbPath, ".wal"));
}
[Fact]
public void FindAllGlobal_ShouldRetrieveAllKeys()
{
var dbPath = Path.Combine(Path.GetTempPath(), $"test_dict_findall_{Guid.NewGuid()}.db");
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
// 1. Create Chain of 3 Pages
var page1Id = storage.AllocatePage();
var page2Id = storage.AllocatePage();
var page3Id = storage.AllocatePage();
var buf = new byte[storage.PageSize];
// Page 1
DictionaryPage.Initialize(buf, page1Id);
DictionaryPage.Insert(buf, "P1_A", 10);
DictionaryPage.Insert(buf, "P1_B", 11);
var h1 = PageHeader.ReadFrom(buf);
h1.NextPageId = page2Id;
h1.WriteTo(buf);
storage.WritePageImmediate(page1Id, buf);
// Page 2
DictionaryPage.Initialize(buf, page2Id);
DictionaryPage.Insert(buf, "P2_A", 20);
var h2 = PageHeader.ReadFrom(buf);
h2.NextPageId = page3Id;
h2.WriteTo(buf);
storage.WritePageImmediate(page2Id, buf);
// Page 3
DictionaryPage.Initialize(buf, page3Id);
DictionaryPage.Insert(buf, "P3_A", 30);
DictionaryPage.Insert(buf, "P3_B", 31);
DictionaryPage.Insert(buf, "P3_C", 32);
storage.WritePageImmediate(page3Id, buf);
// 2. Execute FindAllGlobal
var allEntries = DictionaryPage.FindAllGlobal(storage, page1Id).ToList();
// 3. Verify
allEntries.Count.ShouldBe(6);
allEntries.ShouldContain(e => e.Key == "P1_A" && e.Value == 10);
allEntries.ShouldContain(e => e.Key == "P2_A" && e.Value == 20);
allEntries.ShouldContain(e => e.Key == "P3_C" && e.Value == 32);
storage.Dispose();
if (File.Exists(dbPath)) File.Delete(dbPath);
if (File.Exists(Path.ChangeExtension(dbPath, ".wal"))) File.Delete(Path.ChangeExtension(dbPath, ".wal"));
}
}