212 lines
6.9 KiB
C#
Executable File
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"));
|
|
}
|
|
}
|