Initialize CBDD solution and add a .NET-focused gitignore for generated artifacts.
This commit is contained in:
211
tests/CBDD.Tests/DictionaryPageTests.cs
Executable file
211
tests/CBDD.Tests/DictionaryPageTests.cs
Executable file
@@ -0,0 +1,211 @@
|
||||
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"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user