Reformat / cleanup
All checks were successful
NuGet Publish / build-and-pack (push) Successful in 46s
NuGet Publish / publish-to-gitea (push) Successful in 56s

This commit is contained in:
Joseph Doherty
2026-02-21 08:10:36 -05:00
parent 4c6aaa5a3f
commit a70d8befae
176 changed files with 50555 additions and 49587 deletions

View File

@@ -1,5 +1,4 @@
using System.Reflection;
using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Core.Storage;
using ZB.MOM.WW.CBDD.Core.Transactions;
using ZB.MOM.WW.CBDD.Shared;
@@ -9,12 +8,12 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class CheckpointModeTests
{
/// <summary>
/// Verifies default checkpoint mode truncates WAL.
/// Verifies default checkpoint mode truncates WAL.
/// </summary>
[Fact]
public void Checkpoint_Default_ShouldUseTruncate()
{
var dbPath = NewDbPath();
string dbPath = NewDbPath();
try
{
using var db = new TestDbContext(dbPath);
@@ -36,12 +35,12 @@ public class CheckpointModeTests
}
/// <summary>
/// Verifies passive mode skips when checkpoint lock is contended.
/// Verifies passive mode skips when checkpoint lock is contended.
/// </summary>
[Fact]
public void Checkpoint_Passive_ShouldSkip_WhenLockIsContended()
{
var dbPath = NewDbPath();
string dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
@@ -67,13 +66,13 @@ public class CheckpointModeTests
}
/// <summary>
/// Verifies full checkpoint applies data and appends a checkpoint marker without truncating WAL.
/// Verifies full checkpoint applies data and appends a checkpoint marker without truncating WAL.
/// </summary>
[Fact]
public void Checkpoint_Full_ShouldAppendMarker_AndPreserveWal()
{
var dbPath = NewDbPath();
var walPath = Path.ChangeExtension(dbPath, ".wal");
string dbPath = NewDbPath();
string walPath = Path.ChangeExtension(dbPath, ".wal");
try
{
@@ -82,7 +81,7 @@ public class CheckpointModeTests
db.Users.Insert(new User { Name = "checkpoint-full", Age = 50 });
db.SaveChanges();
var walBefore = db.Storage.GetWalSize();
long walBefore = db.Storage.GetWalSize();
walBefore.ShouldBeGreaterThan(0);
var result = db.Checkpoint(CheckpointMode.Full);
@@ -103,12 +102,12 @@ public class CheckpointModeTests
}
/// <summary>
/// Verifies restart checkpoint clears WAL and allows subsequent writes.
/// Verifies restart checkpoint clears WAL and allows subsequent writes.
/// </summary>
[Fact]
public void Checkpoint_Restart_ShouldResetWal_AndAcceptNewWrites()
{
var dbPath = NewDbPath();
string dbPath = NewDbPath();
try
{
using var db = new TestDbContext(dbPath);
@@ -134,12 +133,12 @@ public class CheckpointModeTests
}
/// <summary>
/// Verifies recovery remains deterministic after a full checkpoint boundary.
/// Verifies recovery remains deterministic after a full checkpoint boundary.
/// </summary>
[Fact]
public void Recover_AfterFullCheckpoint_ShouldApplyLatestCommitDeterministically()
{
var dbPath = NewDbPath();
string dbPath = NewDbPath();
try
{
uint pageId;
@@ -182,12 +181,12 @@ public class CheckpointModeTests
}
/// <summary>
/// Verifies asynchronous mode-based checkpoints return expected result metadata.
/// Verifies asynchronous mode-based checkpoints return expected result metadata.
/// </summary>
[Fact]
public async Task CheckpointAsync_Full_ShouldReturnResult()
{
var dbPath = NewDbPath();
string dbPath = NewDbPath();
try
{
using var db = new TestDbContext(dbPath);
@@ -213,16 +212,18 @@ public class CheckpointModeTests
}
private static string NewDbPath()
=> Path.Combine(Path.GetTempPath(), $"checkpoint_mode_{Guid.NewGuid():N}.db");
{
return Path.Combine(Path.GetTempPath(), $"checkpoint_mode_{Guid.NewGuid():N}.db");
}
private static void CleanupFiles(string dbPath)
{
if (File.Exists(dbPath)) File.Delete(dbPath);
var walPath = Path.ChangeExtension(dbPath, ".wal");
string walPath = Path.ChangeExtension(dbPath, ".wal");
if (File.Exists(walPath)) File.Delete(walPath);
var markerPath = $"{dbPath}.compact.state";
if (File.Exists(markerPath)) File.Delete(markerPath);
}
}
}

View File

@@ -1,7 +1,5 @@
using ZB.MOM.WW.CBDD.Core;
using ZB.MOM.WW.CBDD.Core.Storage;
using System.Text;
using Xunit;
using ZB.MOM.WW.CBDD.Core.Storage;
namespace ZB.MOM.WW.CBDD.Tests;
@@ -10,7 +8,7 @@ public class DictionaryPageTests
private const int PageSize = 16384;
/// <summary>
/// Verifies dictionary page initialization sets expected defaults.
/// Verifies dictionary page initialization sets expected defaults.
/// </summary>
[Fact]
public void Initialize_ShouldSetupEmptyPage()
@@ -30,7 +28,7 @@ public class DictionaryPageTests
}
/// <summary>
/// Verifies insert adds entries and keeps them ordered.
/// Verifies insert adds entries and keeps them ordered.
/// </summary>
[Fact]
public void Insert_ShouldAddEntryAndSort()
@@ -65,7 +63,7 @@ public class DictionaryPageTests
}
/// <summary>
/// Verifies key lookup returns the expected value.
/// Verifies key lookup returns the expected value.
/// </summary>
[Fact]
public void TryFind_ShouldReturnCorrectValue()
@@ -86,7 +84,7 @@ public class DictionaryPageTests
}
/// <summary>
/// Verifies inserts fail when the page is full.
/// Verifies inserts fail when the page is full.
/// </summary>
[Fact]
public void Overflow_ShouldReturnFalse_WhenFull()
@@ -94,18 +92,16 @@ public class DictionaryPageTests
var page = new byte[PageSize];
DictionaryPage.Initialize(page, 1);
string bigKey = new string('X', 250);
var bigKey = new string('X', 250);
int count = 0;
var count = 0;
while (true)
{
// Use unique keys
var key = bigKey + count;
string 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");
}
@@ -118,16 +114,16 @@ public class DictionaryPageTests
}
/// <summary>
/// Verifies global lookup finds keys across chained dictionary pages.
/// Verifies global lookup finds keys across chained dictionary pages.
/// </summary>
[Fact]
public void Chaining_ShouldFindKeysInLinkedPages()
{
var dbPath = Path.Combine(Path.GetTempPath(), $"test_dict_chain_{Guid.NewGuid()}.db");
string 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();
uint page1Id = storage.AllocatePage();
var pageBuffer = new byte[storage.PageSize];
DictionaryPage.Initialize(pageBuffer, page1Id);
@@ -136,7 +132,7 @@ public class DictionaryPageTests
DictionaryPage.Insert(pageBuffer, "KeyA", 200);
// 2. Create Second Page
var page2Id = storage.AllocatePage();
uint page2Id = storage.AllocatePage();
var page2Buffer = new byte[storage.PageSize];
DictionaryPage.Initialize(page2Buffer, page2Id);
@@ -174,18 +170,18 @@ public class DictionaryPageTests
}
/// <summary>
/// Verifies global enumeration returns keys across chained dictionary pages.
/// Verifies global enumeration returns keys across chained dictionary pages.
/// </summary>
[Fact]
public void FindAllGlobal_ShouldRetrieveAllKeys()
{
var dbPath = Path.Combine(Path.GetTempPath(), $"test_dict_findall_{Guid.NewGuid()}.db");
string 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();
uint page1Id = storage.AllocatePage();
uint page2Id = storage.AllocatePage();
uint page3Id = storage.AllocatePage();
var buf = new byte[storage.PageSize];
@@ -226,4 +222,4 @@ public class DictionaryPageTests
if (File.Exists(dbPath)) File.Delete(dbPath);
if (File.Exists(Path.ChangeExtension(dbPath, ".wal"))) File.Delete(Path.ChangeExtension(dbPath, ".wal"));
}
}
}

View File

@@ -1,10 +1,8 @@
using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Bson.Schema;
using ZB.MOM.WW.CBDD.Core.Collections;
using ZB.MOM.WW.CBDD.Core.Storage;
using Xunit;
using System.Collections.Generic;
using System.Linq;
using ZB.MOM.WW.CBDD.Bson.Schema;
using System.Diagnostics.CodeAnalysis;
namespace ZB.MOM.WW.CBDD.Tests;
@@ -14,7 +12,7 @@ public class DictionaryPersistenceTests : IDisposable
private readonly StorageEngine _storage;
/// <summary>
/// Initializes a new instance of the <see cref="DictionaryPersistenceTests"/> class.
/// Initializes a new instance of the <see cref="DictionaryPersistenceTests" /> class.
/// </summary>
public DictionaryPersistenceTests()
{
@@ -23,55 +21,18 @@ public class DictionaryPersistenceTests : IDisposable
}
/// <summary>
/// Disposes test resources and removes temporary files.
/// Disposes test resources and removes temporary files.
/// </summary>
public void Dispose()
{
_storage.Dispose();
if (File.Exists(_dbPath)) File.Delete(_dbPath);
var walPath = Path.ChangeExtension(_dbPath, ".wal");
string walPath = Path.ChangeExtension(_dbPath, ".wal");
if (File.Exists(walPath)) File.Delete(walPath);
}
private class MockMapper : DocumentMapperBase<ObjectId, Dictionary<string, object>>
{
private readonly string _collectionName;
private readonly List<string> _keys;
/// <summary>
/// Initializes a new instance of the <see cref="MockMapper"/> class.
/// </summary>
/// <param name="name">The collection name.</param>
/// <param name="keys">The mapper keys.</param>
public MockMapper(string name, params string[] keys)
{
_collectionName = name;
_keys = keys.ToList();
}
/// <inheritdoc />
public override string CollectionName => _collectionName;
/// <inheritdoc />
public override IEnumerable<string> UsedKeys => _keys;
/// <inheritdoc />
public override BsonSchema GetSchema() => new BsonSchema { Title = _collectionName };
/// <inheritdoc />
public override ObjectId GetId(Dictionary<string, object> entity) => throw new NotImplementedException();
/// <inheritdoc />
public override void SetId(Dictionary<string, object> entity, ObjectId id) => throw new NotImplementedException();
/// <inheritdoc />
public override int Serialize(Dictionary<string, object> entity, BsonSpanWriter writer) => throw new NotImplementedException();
/// <inheritdoc />
public override Dictionary<string, object> Deserialize(BsonSpanReader reader) => throw new NotImplementedException();
}
/// <summary>
/// Verifies mapper registration adds all unique dictionary keys.
/// Verifies mapper registration adds all unique dictionary keys.
/// </summary>
[Fact]
public void RegisterMappers_Registers_All_Unique_Keys()
@@ -99,7 +60,7 @@ public class DictionaryPersistenceTests : IDisposable
}
/// <summary>
/// Verifies dictionary keys persist across storage restarts.
/// Verifies dictionary keys persist across storage restarts.
/// </summary>
[Fact]
public void Dictionary_Keys_Persist_Across_Restarts()
@@ -107,7 +68,7 @@ public class DictionaryPersistenceTests : IDisposable
var mapper = new MockMapper("Coll1", "PersistedKey");
_storage.RegisterMappers(new IDocumentMapper[] { mapper });
var originalId = _storage.GetOrAddDictionaryEntry("PersistedKey");
ushort originalId = _storage.GetOrAddDictionaryEntry("PersistedKey");
originalId.ShouldNotBe((ushort)0);
_storage.Dispose();
@@ -115,10 +76,78 @@ public class DictionaryPersistenceTests : IDisposable
// Re-open
using var storage2 = new StorageEngine(_dbPath, PageFileConfig.Default);
var recoveredId = storage2.GetOrAddDictionaryEntry("PersistedKey");
ushort recoveredId = storage2.GetOrAddDictionaryEntry("PersistedKey");
recoveredId.ShouldBe(originalId);
}
/// <summary>
/// Verifies nested schema fields are registered as dictionary keys.
/// </summary>
[Fact]
public void RegisterMappers_Handles_Nested_Keys()
{
var mapper = new NestedMockMapper();
_storage.RegisterMappers(new IDocumentMapper[] { mapper });
_storage.GetOrAddDictionaryEntry("Top").ShouldNotBe((ushort)0);
_storage.GetOrAddDictionaryEntry("Child").ShouldNotBe((ushort)0);
}
[SuppressMessage("ReSharper", "All", Justification = "Test-only stub mapper; members are intentionally not used.")]
private class MockMapper : DocumentMapperBase<ObjectId, Dictionary<string, object>>
{
private readonly string _collectionName;
private readonly List<string> _keys;
/// <summary>
/// Initializes a new instance of the <see cref="MockMapper" /> class.
/// </summary>
/// <param name="name">The collection name.</param>
/// <param name="keys">The mapper keys.</param>
public MockMapper(string name, params string[] keys)
{
_collectionName = name;
_keys = keys.ToList();
}
/// <inheritdoc />
public override string CollectionName => _collectionName;
/// <inheritdoc />
public override IEnumerable<string> UsedKeys => _keys;
/// <inheritdoc />
public override BsonSchema GetSchema()
{
return new BsonSchema { Title = _collectionName };
}
/// <inheritdoc />
public override ObjectId GetId(Dictionary<string, object> entity)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override void SetId(Dictionary<string, object> entity, ObjectId id)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override int Serialize(Dictionary<string, object> entity, BsonSpanWriter writer)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override Dictionary<string, object> Deserialize(BsonSpanReader reader)
{
throw new NotImplementedException();
}
}
[SuppressMessage("ReSharper", "All", Justification = "Test-only stub mapper; members are intentionally not used.")]
private class NestedMockMapper : DocumentMapperBase<ObjectId, object>
{
/// <inheritdoc />
@@ -141,28 +170,27 @@ public class DictionaryPersistenceTests : IDisposable
}
/// <inheritdoc />
public override ObjectId GetId(object entity) => throw new NotImplementedException();
public override ObjectId GetId(object entity)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override void SetId(object entity, ObjectId id) => throw new NotImplementedException();
public override void SetId(object entity, ObjectId id)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override int Serialize(object entity, BsonSpanWriter writer) => throw new NotImplementedException();
public override int Serialize(object entity, BsonSpanWriter writer)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override object Deserialize(BsonSpanReader reader) => throw new NotImplementedException();
}
/// <summary>
/// Verifies nested schema fields are registered as dictionary keys.
/// </summary>
[Fact]
public void RegisterMappers_Handles_Nested_Keys()
{
var mapper = new NestedMockMapper();
_storage.RegisterMappers(new IDocumentMapper[] { mapper });
_storage.GetOrAddDictionaryEntry("Top").ShouldNotBe((ushort)0);
_storage.GetOrAddDictionaryEntry("Child").ShouldNotBe((ushort)0);
public override object Deserialize(BsonSpanReader reader)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,33 +1,29 @@
using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Core.Collections;
using ZB.MOM.WW.CBDD.Core.Compression;
using ZB.MOM.WW.CBDD.Core.Storage;
using ZB.MOM.WW.CBDD.Core.Transactions;
using ZB.MOM.WW.CBDD.Shared;
using ZB.MOM.WW.CBDD.Shared.TestDbContext_TestDbContext_Mappers;
using System.IO.Compression;
using System.IO.MemoryMappedFiles;
using Xunit;
using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Core.Compression;
using ZB.MOM.WW.CBDD.Core.Storage;
using ZB.MOM.WW.CBDD.Shared;
namespace ZB.MOM.WW.CBDD.Tests;
public class DocumentOverflowTests : IDisposable
{
private readonly TestDbContext _db;
private readonly string _dbPath;
private readonly Shared.TestDbContext _db;
/// <summary>
/// Initializes a new instance of the <see cref="DocumentOverflowTests"/> class.
/// Initializes a new instance of the <see cref="DocumentOverflowTests" /> class.
/// </summary>
public DocumentOverflowTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"test_overflow_{Guid.NewGuid()}.db");
// Use default PageSize (16KB)
_db = new Shared.TestDbContext(_dbPath);
_db = new TestDbContext(_dbPath);
}
/// <summary>
/// Releases test resources.
/// Releases test resources.
/// </summary>
public void Dispose()
{
@@ -36,7 +32,7 @@ public class DocumentOverflowTests : IDisposable
}
/// <summary>
/// Verifies inserting a medium-sized document succeeds.
/// Verifies inserting a medium-sized document succeeds.
/// </summary>
[Fact]
public void Insert_MediumDoc_64KB_ShouldSucceed()
@@ -60,7 +56,7 @@ public class DocumentOverflowTests : IDisposable
}
/// <summary>
/// Verifies inserting a large document succeeds.
/// Verifies inserting a large document succeeds.
/// </summary>
[Fact]
public void Insert_LargeDoc_100KB_ShouldSucceed()
@@ -83,7 +79,7 @@ public class DocumentOverflowTests : IDisposable
}
/// <summary>
/// Verifies inserting a very large document succeeds.
/// Verifies inserting a very large document succeeds.
/// </summary>
[Fact]
public void Insert_HugeDoc_3MB_ShouldSucceed()
@@ -109,7 +105,7 @@ public class DocumentOverflowTests : IDisposable
}
/// <summary>
/// Verifies updating from a small payload to a huge payload succeeds.
/// Verifies updating from a small payload to a huge payload succeeds.
/// </summary>
[Fact]
public void Update_SmallToHuge_ShouldSucceed()
@@ -123,7 +119,7 @@ public class DocumentOverflowTests : IDisposable
var hugeString = new string('U', 3 * 1024 * 1024);
user.Name = hugeString;
var updated = _db.Users.Update(user);
bool updated = _db.Users.Update(user);
_db.SaveChanges();
updated.ShouldBeTrue();
@@ -133,17 +129,17 @@ public class DocumentOverflowTests : IDisposable
}
/// <summary>
/// Verifies bulk inserts with mixed payload sizes succeed.
/// Verifies bulk inserts with mixed payload sizes succeed.
/// </summary>
[Fact]
public void InsertBulk_MixedSizes_ShouldSucceed()
{
var users = new List<User>
{
new User { Id = ObjectId.NewObjectId(), Name = "Small 1", Age = 1 },
new User { Id = ObjectId.NewObjectId(), Name = new string('M', 100 * 1024), Age = 2 }, // 100KB
new User { Id = ObjectId.NewObjectId(), Name = "Small 2", Age = 3 },
new User { Id = ObjectId.NewObjectId(), Name = new string('H', 3 * 1024 * 1024), Age = 4 } // 3MB
new() { Id = ObjectId.NewObjectId(), Name = "Small 1", Age = 1 },
new() { Id = ObjectId.NewObjectId(), Name = new string('M', 100 * 1024), Age = 2 }, // 100KB
new() { Id = ObjectId.NewObjectId(), Name = "Small 2", Age = 3 },
new() { Id = ObjectId.NewObjectId(), Name = new string('H', 3 * 1024 * 1024), Age = 4 } // 3MB
};
var ids = _db.Users.InsertBulk(users);
@@ -158,12 +154,12 @@ public class DocumentOverflowTests : IDisposable
}
/// <summary>
/// Verifies huge inserts succeed with compression enabled and small page configuration.
/// Verifies huge inserts succeed with compression enabled and small page configuration.
/// </summary>
[Fact]
public void Insert_HugeDoc_WithCompressionEnabledAndSmallPages_ShouldSucceed()
{
var localDbPath = Path.Combine(Path.GetTempPath(), $"test_overflow_compression_{Guid.NewGuid():N}.db");
string localDbPath = Path.Combine(Path.GetTempPath(), $"test_overflow_compression_{Guid.NewGuid():N}.db");
var options = new CompressionOptions
{
EnableCompression = true,
@@ -175,7 +171,7 @@ public class DocumentOverflowTests : IDisposable
try
{
using var db = new Shared.TestDbContext(localDbPath, TinyPageConfig(), options);
using var db = new TestDbContext(localDbPath, TinyPageConfig(), options);
var huge = new string('Z', 2 * 1024 * 1024);
var id = db.Users.Insert(new User
{
@@ -197,12 +193,13 @@ public class DocumentOverflowTests : IDisposable
}
/// <summary>
/// Verifies updates from huge to small payloads succeed with compression enabled.
/// Verifies updates from huge to small payloads succeed with compression enabled.
/// </summary>
[Fact]
public void Update_HugeToSmall_WithCompressionEnabled_ShouldSucceed()
{
var localDbPath = Path.Combine(Path.GetTempPath(), $"test_overflow_compression_update_{Guid.NewGuid():N}.db");
string localDbPath =
Path.Combine(Path.GetTempPath(), $"test_overflow_compression_update_{Guid.NewGuid():N}.db");
var options = new CompressionOptions
{
EnableCompression = true,
@@ -214,7 +211,7 @@ public class DocumentOverflowTests : IDisposable
try
{
using var db = new Shared.TestDbContext(localDbPath, TinyPageConfig(), options);
using var db = new TestDbContext(localDbPath, TinyPageConfig(), options);
var user = new User
{
Id = ObjectId.NewObjectId(),
@@ -251,10 +248,10 @@ public class DocumentOverflowTests : IDisposable
private static void CleanupLocalFiles(string dbPath)
{
var walPath = Path.ChangeExtension(dbPath, ".wal");
string walPath = Path.ChangeExtension(dbPath, ".wal");
var markerPath = $"{dbPath}.compact.state";
if (File.Exists(dbPath)) File.Delete(dbPath);
if (File.Exists(walPath)) File.Delete(walPath);
if (File.Exists(markerPath)) File.Delete(markerPath);
}
}
}

View File

@@ -1,4 +1,6 @@
using System.IO.Compression;
using System.Text;
using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Core.Compression;
using ZB.MOM.WW.CBDD.Core.Storage;
using ZB.MOM.WW.CBDD.Shared;
@@ -8,12 +10,12 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class MaintenanceDiagnosticsAndMigrationTests
{
/// <summary>
/// Verifies diagnostics APIs return page usage, compression, and fragmentation data.
/// Verifies diagnostics APIs return page usage, compression, and fragmentation data.
/// </summary>
[Fact]
public void DiagnosticsApis_ShouldReturnPageUsageCompressionAndFragmentationData()
{
var dbPath = NewDbPath();
string dbPath = NewDbPath();
try
{
@@ -28,13 +30,11 @@ public class MaintenanceDiagnosticsAndMigrationTests
using var db = new TestDbContext(dbPath, options);
for (var i = 0; i < 40; i++)
{
db.Users.Insert(new User
{
Name = BuildPayload(i, 9000),
Age = i
});
}
db.SaveChanges();
db.ForceCheckpoint();
@@ -47,7 +47,8 @@ public class MaintenanceDiagnosticsAndMigrationTests
byCollection.Any(x => x.CollectionName.Equals("users", StringComparison.OrdinalIgnoreCase)).ShouldBeTrue();
var compressionByCollection = db.GetCompressionRatioByCollection();
var usersCompression = compressionByCollection.First(x => x.CollectionName.Equals("users", StringComparison.OrdinalIgnoreCase));
var usersCompression = compressionByCollection.First(x =>
x.CollectionName.Equals("users", StringComparison.OrdinalIgnoreCase));
usersCompression.DocumentCount.ShouldBeGreaterThan(0);
usersCompression.BytesBeforeCompression.ShouldBeGreaterThan(0);
usersCompression.BytesAfterCompression.ShouldBeGreaterThan(0);
@@ -65,26 +66,24 @@ public class MaintenanceDiagnosticsAndMigrationTests
}
/// <summary>
/// Verifies compression migration dry-run and apply modes return deterministic stats and preserve data.
/// Verifies compression migration dry-run and apply modes return deterministic stats and preserve data.
/// </summary>
[Fact]
public void MigrateCompression_DryRunAndApply_ShouldReturnDeterministicStatsAndPreserveData()
{
var dbPath = NewDbPath();
string dbPath = NewDbPath();
try
{
using var db = new TestDbContext(dbPath, CompressionOptions.Default);
var ids = new List<ZB.MOM.WW.CBDD.Bson.ObjectId>();
var ids = new List<ObjectId>();
for (var i = 0; i < 60; i++)
{
ids.Add(db.Users.Insert(new User
{
Name = BuildPayload(i, 12000),
Age = i % 17
}));
}
db.SaveChanges();
db.ForceCheckpoint();
@@ -132,7 +131,7 @@ public class MaintenanceDiagnosticsAndMigrationTests
private static string BuildPayload(int seed, int approxLength)
{
var builder = new System.Text.StringBuilder(approxLength + 128);
var builder = new StringBuilder(approxLength + 128);
var i = 0;
while (builder.Length < approxLength)
{
@@ -148,11 +147,13 @@ public class MaintenanceDiagnosticsAndMigrationTests
}
private static string NewDbPath()
=> Path.Combine(Path.GetTempPath(), $"maint_diag_migrate_{Guid.NewGuid():N}.db");
{
return Path.Combine(Path.GetTempPath(), $"maint_diag_migrate_{Guid.NewGuid():N}.db");
}
private static void CleanupFiles(string dbPath)
{
var walPath = Path.ChangeExtension(dbPath, ".wal");
string walPath = Path.ChangeExtension(dbPath, ".wal");
var markerPath = $"{dbPath}.compact.state";
var tempPath = $"{dbPath}.compact.tmp";
var backupPath = $"{dbPath}.compact.bak";
@@ -163,4 +164,4 @@ public class MaintenanceDiagnosticsAndMigrationTests
if (File.Exists(tempPath)) File.Delete(tempPath);
if (File.Exists(backupPath)) File.Delete(backupPath);
}
}
}

View File

@@ -1,11 +1,8 @@
using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Core.Collections;
using ZB.MOM.WW.CBDD.Core.Indexing;
using ZB.MOM.WW.CBDD.Core.Storage;
using ZB.MOM.WW.CBDD.Core.Transactions;
using ZB.MOM.WW.CBDD.Shared;
using ZB.MOM.WW.CBDD.Shared.TestDbContext_TestDbContext_Mappers;
using Xunit;
namespace ZB.MOM.WW.CBDD.Tests;
@@ -15,7 +12,7 @@ public class MetadataPersistenceTests : IDisposable
private readonly string _walPath;
/// <summary>
/// Initializes a new instance of the <see cref="MetadataPersistenceTests"/> class.
/// Initializes a new instance of the <see cref="MetadataPersistenceTests" /> class.
/// </summary>
public MetadataPersistenceTests()
{
@@ -24,7 +21,16 @@ public class MetadataPersistenceTests : IDisposable
}
/// <summary>
/// Tests index definitions are persisted and reloaded.
/// Disposes the resources used by this instance.
/// </summary>
public void Dispose()
{
if (File.Exists(_dbPath)) File.Delete(_dbPath);
if (File.Exists(_walPath)) File.Delete(_walPath);
}
/// <summary>
/// Tests index definitions are persisted and reloaded.
/// </summary>
[Fact]
public void IndexDefinitions_ArePersisted_AndReloaded()
@@ -66,19 +72,19 @@ public class MetadataPersistenceTests : IDisposable
}
/// <summary>
/// Tests ensure index does not recreate if index exists.
/// Tests ensure index does not recreate if index exists.
/// </summary>
[Fact]
public void EnsureIndex_DoesNotRecreate_IfIndexExists()
{
// 1. Create index
using (var context = new Shared.TestDbContext(_dbPath))
using (var context = new TestDbContext(_dbPath))
{
context.Users.EnsureIndex(u => u.Age);
}
// 2. Re-open and EnsureIndex again - should be fast/no-op
using (var context = new Shared.TestDbContext(_dbPath))
using (var context = new TestDbContext(_dbPath))
{
var mapper = new ZB_MOM_WW_CBDD_Shared_UserMapper();
@@ -99,13 +105,4 @@ public class MetadataPersistenceTests : IDisposable
results.Count().ShouldBe(1);
}
}
/// <summary>
/// Disposes the resources used by this instance.
/// </summary>
public void Dispose()
{
if (File.Exists(_dbPath)) File.Delete(_dbPath);
if (File.Exists(_walPath)) File.Delete(_walPath);
}
}
}

View File

@@ -1,52 +1,12 @@
using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Core.Collections;
using Xunit;
using System.Collections.Generic;
using System;
using System.Linq;
namespace ZB.MOM.WW.CBDD.Tests;
public class RobustnessTests
{
public struct Point
{
/// <summary>
/// Gets or sets the X.
/// </summary>
public int X { get; set; }
/// <summary>
/// Gets or sets the Y.
/// </summary>
public int Y { get; set; }
}
public class RobustEntity
{
/// <summary>
/// Gets or sets the NullableInts.
/// </summary>
public List<int?> NullableInts { get; set; } = new();
/// <summary>
/// Gets or sets the Map.
/// </summary>
public Dictionary<string, int> Map { get; set; } = new();
/// <summary>
/// Gets or sets the EnumerableStrings.
/// </summary>
public IEnumerable<string> EnumerableStrings { get; set; } = Array.Empty<string>();
/// <summary>
/// Gets or sets the Location.
/// </summary>
public Point Location { get; set; }
/// <summary>
/// Gets or sets the NullableLocation.
/// </summary>
public Point? NullableLocation { get; set; }
}
/// <summary>
/// Executes GenerateSchema_RobustnessChecks.
/// Executes GenerateSchema_RobustnessChecks.
/// </summary>
[Fact]
public void GenerateSchema_RobustnessChecks()
@@ -83,4 +43,45 @@ public class RobustnessTests
nullableLocation.IsNullable.ShouldBeTrue();
nullableLocation.NestedSchema.ShouldNotBeNull();
}
}
public struct Point
{
/// <summary>
/// Gets or sets the X.
/// </summary>
public int X { get; set; }
/// <summary>
/// Gets or sets the Y.
/// </summary>
public int Y { get; set; }
}
public class RobustEntity
{
/// <summary>
/// Gets or sets the NullableInts.
/// </summary>
public List<int?> NullableInts { get; set; } = new();
/// <summary>
/// Gets or sets the Map.
/// </summary>
public Dictionary<string, int> Map { get; set; } = new();
/// <summary>
/// Gets or sets the EnumerableStrings.
/// </summary>
public IEnumerable<string> EnumerableStrings { get; set; } = Array.Empty<string>();
/// <summary>
/// Gets or sets the Location.
/// </summary>
public Point Location { get; set; }
/// <summary>
/// Gets or sets the NullableLocation.
/// </summary>
public Point? NullableLocation { get; set; }
}
}

View File

@@ -1,11 +1,13 @@
using ZB.MOM.WW.CBDD.Core.Storage;
using Xunit;
namespace ZB.MOM.WW.CBDD.Tests;
public class StorageEngineDictionaryTests
{
private string GetTempDbPath() => Path.Combine(Path.GetTempPath(), $"test_storage_dict_{Guid.NewGuid()}.db");
private string GetTempDbPath()
{
return Path.Combine(Path.GetTempPath(), $"test_storage_dict_{Guid.NewGuid()}.db");
}
private void Cleanup(string path)
{
@@ -14,34 +16,37 @@ public class StorageEngineDictionaryTests
}
/// <summary>
/// Verifies dictionary pages are initialized and return normalized keys.
/// Verifies dictionary pages are initialized and return normalized keys.
/// </summary>
[Fact]
public void StorageEngine_ShouldInitializeDictionary()
{
var path = GetTempDbPath();
string path = GetTempDbPath();
try
{
using (var storage = new StorageEngine(path, PageFileConfig.Default))
{
// Should generate ID > 100
var id = storage.GetOrAddDictionaryEntry("TestKey");
ushort id = storage.GetOrAddDictionaryEntry("TestKey");
(id > DictionaryPage.ReservedValuesEnd).ShouldBeTrue();
var key = storage.GetDictionaryKey(id);
string? key = storage.GetDictionaryKey(id);
key.ShouldBe("testkey");
}
}
finally { Cleanup(path); }
finally
{
Cleanup(path);
}
}
/// <summary>
/// Verifies dictionary entries persist across reopen.
/// Verifies dictionary entries persist across reopen.
/// </summary>
[Fact]
public void StorageEngine_ShouldPersistDictionary()
{
var path = GetTempDbPath();
string path = GetTempDbPath();
try
{
ushort id1, id2;
@@ -54,8 +59,8 @@ public class StorageEngineDictionaryTests
// Reopen
using (var storage = new StorageEngine(path, PageFileConfig.Default))
{
var val1 = storage.GetOrAddDictionaryEntry("Key1");
var val2 = storage.GetOrAddDictionaryEntry("Key2");
ushort val1 = storage.GetOrAddDictionaryEntry("Key1");
ushort val2 = storage.GetOrAddDictionaryEntry("Key2");
val1.ShouldBe(id1);
val2.ShouldBe(id2);
@@ -64,16 +69,19 @@ public class StorageEngineDictionaryTests
storage.GetDictionaryKey(val2).ShouldBe("key2");
}
}
finally { Cleanup(path); }
finally
{
Cleanup(path);
}
}
/// <summary>
/// Verifies dictionary handling scales to many keys and remains durable.
/// Verifies dictionary handling scales to many keys and remains durable.
/// </summary>
[Fact]
public void StorageEngine_ShouldHandleManyKeys()
{
var path = GetTempDbPath();
string path = GetTempDbPath();
try
{
const int keyCount = 3000;
@@ -81,10 +89,10 @@ public class StorageEngineDictionaryTests
using (var storage = new StorageEngine(path, PageFileConfig.Default))
{
for (int i = 0; i < keyCount; i++)
for (var i = 0; i < keyCount; i++)
{
var key = $"Key_{i}";
var id = storage.GetOrAddDictionaryEntry(key);
ushort id = storage.GetOrAddDictionaryEntry(key);
expectedIds[key] = id;
}
}
@@ -92,22 +100,25 @@ public class StorageEngineDictionaryTests
// Reopen and Verify
using (var storage = new StorageEngine(path, PageFileConfig.Default))
{
for (int i = 0; i < keyCount; i++)
for (var i = 0; i < keyCount; i++)
{
var key = $"Key_{i}";
var id = storage.GetOrAddDictionaryEntry(key); // Should get existing
ushort id = storage.GetOrAddDictionaryEntry(key); // Should get existing
id.ShouldBe(expectedIds[key]);
var loadedKey = storage.GetDictionaryKey(id);
string? loadedKey = storage.GetDictionaryKey(id);
loadedKey.ShouldBe(key.ToLowerInvariant());
}
// Add new one
var newId = storage.GetOrAddDictionaryEntry("NewKeyAfterReopen");
ushort newId = storage.GetOrAddDictionaryEntry("NewKeyAfterReopen");
(newId > 0).ShouldBeTrue();
expectedIds.ContainsValue(newId).ShouldBeFalse();
}
}
finally { Cleanup(path); }
finally
{
Cleanup(path);
}
}
}
}

View File

@@ -6,12 +6,12 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class StorageEngineTransactionProtocolTests
{
/// <summary>
/// Verifies preparing an unknown transaction returns false.
/// Verifies preparing an unknown transaction returns false.
/// </summary>
[Fact]
public void PrepareTransaction_Should_ReturnFalse_For_Unknown_Transaction()
{
var dbPath = NewDbPath();
string dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
@@ -24,12 +24,12 @@ public class StorageEngineTransactionProtocolTests
}
/// <summary>
/// Verifies committing a detached transaction object throws.
/// Verifies committing a detached transaction object throws.
/// </summary>
[Fact]
public void CommitTransaction_With_TransactionObject_Should_Throw_When_Not_Active()
{
var dbPath = NewDbPath();
string dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
@@ -44,18 +44,18 @@ public class StorageEngineTransactionProtocolTests
}
/// <summary>
/// Verifies committing a transaction object persists writes and clears active state.
/// Verifies committing a transaction object persists writes and clears active state.
/// </summary>
[Fact]
public void CommitTransaction_With_TransactionObject_Should_Commit_Writes()
{
var dbPath = NewDbPath();
string dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
using var txn = storage.BeginTransaction();
var pageId = storage.AllocatePage();
uint pageId = storage.AllocatePage();
var data = new byte[storage.PageSize];
data[0] = 0xAB;
@@ -75,12 +75,12 @@ public class StorageEngineTransactionProtocolTests
}
/// <summary>
/// Verifies committing by identifier with no writes does not throw.
/// Verifies committing by identifier with no writes does not throw.
/// </summary>
[Fact]
public void CommitTransaction_ById_With_NoWrites_Should_Not_Throw()
{
var dbPath = NewDbPath();
string dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
@@ -93,18 +93,18 @@ public class StorageEngineTransactionProtocolTests
}
/// <summary>
/// Verifies committed transaction cache moves into readable state and active count is cleared.
/// Verifies committed transaction cache moves into readable state and active count is cleared.
/// </summary>
[Fact]
public void MarkTransactionCommitted_Should_Move_Cache_And_Clear_ActiveCount()
{
var dbPath = NewDbPath();
string dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
using var txn = storage.BeginTransaction();
var pageId = storage.AllocatePage();
uint pageId = storage.AllocatePage();
var data = new byte[storage.PageSize];
data[5] = 0x5A;
storage.WritePage(pageId, txn.TransactionId, data);
@@ -124,17 +124,17 @@ public class StorageEngineTransactionProtocolTests
}
/// <summary>
/// Verifies rollback discards uncommitted page writes.
/// Verifies rollback discards uncommitted page writes.
/// </summary>
[Fact]
public void RollbackTransaction_Should_Discard_Uncommitted_Write()
{
var dbPath = NewDbPath();
string dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
var pageId = storage.AllocatePage();
uint pageId = storage.AllocatePage();
var baseline = new byte[storage.PageSize];
baseline[0] = 0x11;
storage.WritePageImmediate(pageId, baseline);
@@ -159,18 +159,18 @@ public class StorageEngineTransactionProtocolTests
}
/// <summary>
/// Verifies marking a transaction committed transitions state correctly.
/// Verifies marking a transaction committed transitions state correctly.
/// </summary>
[Fact]
public void Transaction_MarkCommitted_Should_Transition_State()
{
var dbPath = NewDbPath();
string dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
using var txn = storage.BeginTransaction();
var pageId = storage.AllocatePage();
uint pageId = storage.AllocatePage();
var data = new byte[storage.PageSize];
data[3] = 0x33;
storage.WritePage(pageId, txn.TransactionId, data);
@@ -191,18 +191,18 @@ public class StorageEngineTransactionProtocolTests
}
/// <summary>
/// Verifies preparing then committing writes WAL data and updates transaction state.
/// Verifies preparing then committing writes WAL data and updates transaction state.
/// </summary>
[Fact]
public void Transaction_Prepare_Should_Write_Wal_And_Transition_State()
{
var dbPath = NewDbPath();
string dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
using var txn = storage.BeginTransaction();
var pageId = storage.AllocatePage();
uint pageId = storage.AllocatePage();
var data = new byte[storage.PageSize];
data[11] = 0x7B;
storage.WritePage(pageId, txn.TransactionId, data);
@@ -220,16 +220,18 @@ public class StorageEngineTransactionProtocolTests
}
private static string NewDbPath()
=> Path.Combine(Path.GetTempPath(), $"storage_txn_{Guid.NewGuid():N}.db");
{
return Path.Combine(Path.GetTempPath(), $"storage_txn_{Guid.NewGuid():N}.db");
}
private static void CleanupFiles(string dbPath)
{
if (File.Exists(dbPath)) File.Delete(dbPath);
var walPath = Path.ChangeExtension(dbPath, ".wal");
string walPath = Path.ChangeExtension(dbPath, ".wal");
if (File.Exists(walPath)) File.Delete(walPath);
var altWalPath = dbPath + "-wal";
string altWalPath = dbPath + "-wal";
if (File.Exists(altWalPath)) File.Delete(altWalPath);
}
}
}