using System.Reflection; using System.Text; using Shouldly; using ZB.MOM.NatsNet.Server; namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; public sealed partial class JetStreamFileStoreTests { [Fact] // T:364 public void FileStoreCompact_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreCompact_ShouldSucceed)); [Fact] // T:366 public void FileStoreCompactMsgCountBug_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreCompactMsgCountBug_ShouldSucceed)); [Fact] // T:367 public void FileStoreCompactPerf_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreCompactPerf_ShouldSucceed)); [Fact] // T:406 public void FileStorePurgeExKeepOneBug_ShouldSucceed() => RunWaveBScenario(nameof(FileStorePurgeExKeepOneBug_ShouldSucceed)); [Fact] // T:408 public void FileStoreFetchPerf_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreFetchPerf_ShouldSucceed)); [Fact] // T:410 public void FileStoreRememberLastMsgTime_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreRememberLastMsgTime_ShouldSucceed)); [Fact] // T:414 public void FileStoreShortIndexWriteBug_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreShortIndexWriteBug_ShouldSucceed)); [Fact] // T:415 public void FileStoreDoubleCompactWithWriteInBetweenEncryptedBug_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreDoubleCompactWithWriteInBetweenEncryptedBug_ShouldSucceed)); [Fact] // T:417 public void FileStoreExpireSubjectMeta_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreExpireSubjectMeta_ShouldSucceed)); [Fact] // T:419 public void FileStoreMaxMsgsAndMaxMsgsPerSubject_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreMaxMsgsAndMaxMsgsPerSubject_ShouldSucceed)); [Fact] // T:420 public void FileStoreSubjectStateCacheExpiration_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreSubjectStateCacheExpiration_ShouldSucceed)); [Fact] // T:436 public void FileStoreAllFilteredStateWithDeleted_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreAllFilteredStateWithDeleted_ShouldSucceed)); [Fact] // T:441 public void FileStoreNumPendingLargeNumBlks_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreNumPendingLargeNumBlks_ShouldSucceed)); [Fact] // T:446 public void FileStoreKeepWithDeletedMsgsBug_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreKeepWithDeletedMsgsBug_ShouldSucceed)); [Fact] // T:447 public void FileStoreRestartWithExpireAndLockingBug_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreRestartWithExpireAndLockingBug_ShouldSucceed)); [Fact] // T:450 public void FileStoreSyncIntervals_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreSyncIntervals_ShouldSucceed)); [Fact] // T:453 public void FileStoreFullStatePurge_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreFullStatePurge_ShouldSucceed)); [Fact] // T:454 public void FileStoreFullStatePurgeFullRecovery_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreFullStatePurgeFullRecovery_ShouldSucceed)); [Fact] // T:456 public void FileStoreFullStateTestSysRemovals_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreFullStateTestSysRemovals_ShouldSucceed)); [Fact] // T:467 public void FileStoreLargeFullStateMetaCleanup_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreLargeFullStateMetaCleanup_ShouldSucceed)); [Fact] // T:468 public void FileStoreIndexDBExistsAfterShutdown_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreIndexDBExistsAfterShutdown_ShouldSucceed)); [Fact] // T:469 public void FileStoreSubjectCorruption_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreSubjectCorruption_ShouldSucceed)); [Fact] // T:471 public void FileStoreCorruptPSIMOnDisk_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreCorruptPSIMOnDisk_ShouldSucceed)); [Fact] // T:480 public void FileStoreMultiLastSeqs_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreMultiLastSeqs_ShouldSucceed)); [Fact] // T:481 public void FileStoreMultiLastSeqsMaxAllowed_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreMultiLastSeqsMaxAllowed_ShouldSucceed)); [Fact] // T:484 public void FileStoreFSSExpire_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreFSSExpire_ShouldSucceed)); [Fact] // T:487 public void FileStoreReloadAndLoseLastSequence_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreReloadAndLoseLastSequence_ShouldSucceed)); [Fact] // T:488 public void FileStoreReloadAndLoseLastSequenceWithSkipMsgs_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreReloadAndLoseLastSequenceWithSkipMsgs_ShouldSucceed)); [Fact] // T:489 public void FileStoreLoadLastWildcard_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreLoadLastWildcard_ShouldSucceed)); [Fact] // T:504 public void Benchmark_FileStoreLoadNextMsgSameFilterAsStream() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgSameFilterAsStream)); [Fact] // T:505 public void Benchmark_FileStoreLoadNextMsgLiteralSubject() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgLiteralSubject)); [Fact] // T:506 public void Benchmark_FileStoreLoadNextMsgNoMsgsFirstSeq() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgNoMsgsFirstSeq)); [Fact] // T:507 public void Benchmark_FileStoreLoadNextMsgNoMsgsNotFirstSeq() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgNoMsgsNotFirstSeq)); [Fact] // T:508 public void Benchmark_FileStoreLoadNextMsgVerySparseMsgsFirstSeq() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsFirstSeq)); [Fact] // T:509 public void Benchmark_FileStoreLoadNextMsgVerySparseMsgsNotFirstSeq() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsNotFirstSeq)); [Fact] // T:510 public void Benchmark_FileStoreLoadNextMsgVerySparseMsgsInBetween() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsInBetween)); [Fact] // T:511 public void Benchmark_FileStoreLoadNextMsgVerySparseMsgsInBetweenWithWildcard() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsInBetweenWithWildcard)); [Fact] // T:512 public void Benchmark_FileStoreLoadNextManySubjectsWithWildcardNearLastBlock() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextManySubjectsWithWildcardNearLastBlock)); [Fact] // T:513 public void Benchmark_FileStoreLoadNextMsgVerySparseMsgsLargeTail() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsLargeTail)); [Fact] // T:514 public void Benchmark_FileStoreCreateConsumerStores() => RunWaveBScenario(nameof(Benchmark_FileStoreCreateConsumerStores)); [Fact] // T:515 public void Benchmark_FileStoreSubjectStateConsistencyOptimizationPerf() => RunWaveBScenario(nameof(Benchmark_FileStoreSubjectStateConsistencyOptimizationPerf)); [Fact] // T:516 public void Benchmark_FileStoreSyncDeletedFullBlocks() => RunWaveBScenario(nameof(Benchmark_FileStoreSyncDeletedFullBlocks)); [Fact] // T:517 public void Benchmark_FileStoreSyncDeletedPartialBlocks() => RunWaveBScenario(nameof(Benchmark_FileStoreSyncDeletedPartialBlocks)); [Fact] // T:520 public void FileStoreNumPendingMulti_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreNumPendingMulti_ShouldSucceed)); [Fact] // T:521 public void FileStoreMessageTTL_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreMessageTTL_ShouldSucceed)); [Fact] // T:522 public void FileStoreMessageTTLRestart_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreMessageTTLRestart_ShouldSucceed)); [Fact] // T:524 public void FileStoreMessageTTLRecoveredSingleMessageWithoutStreamState_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreMessageTTLRecoveredSingleMessageWithoutStreamState_ShouldSucceed)); [Fact] // T:525 public void FileStoreMessageTTLWriteTombstone_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreMessageTTLWriteTombstone_ShouldSucceed)); [Fact] // T:526 public void FileStoreMessageTTLRecoveredOffByOne_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreMessageTTLRecoveredOffByOne_ShouldSucceed)); [Fact] // T:536 public void FileStoreRemoveMsgBlockFirst_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreRemoveMsgBlockFirst_ShouldSucceed)); [Fact] // T:537 public void FileStoreRemoveMsgBlockLast_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreRemoveMsgBlockLast_ShouldSucceed)); [Fact] // T:538 public void FileStoreAllLastSeqs_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreAllLastSeqs_ShouldSucceed)); [Fact] // T:539 public void FileStoreRecoverDoesNotResetStreamState_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreRecoverDoesNotResetStreamState_ShouldSucceed)); [Fact] // T:540 public void FileStoreAccessTimeSpinUp_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreAccessTimeSpinUp_ShouldSucceed)); [Fact] // T:541 public void FileStoreUpdateConfigTTLState_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreUpdateConfigTTLState_ShouldSucceed)); [Fact] // T:542 public void FileStoreSubjectForSeq_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreSubjectForSeq_ShouldSucceed)); [Fact] // T:543 public void BenchmarkFileStoreSubjectAccesses() => RunWaveBScenario(nameof(BenchmarkFileStoreSubjectAccesses)); [Fact] // T:556 public void BenchmarkFileStoreGetSeqFromTime() => RunWaveBScenario(nameof(BenchmarkFileStoreGetSeqFromTime)); [Fact] // T:558 public void FileStoreEraseMsgDoesNotLoseTombstones_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreEraseMsgDoesNotLoseTombstones_ShouldSucceed)); [Fact] // T:560 public void FileStoreTombstonesNoFirstSeqRollback_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreTombstonesNoFirstSeqRollback_ShouldSucceed)); [Fact] // T:563 public void FileStoreDetectDeleteGapWithLastSkipMsg_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreDetectDeleteGapWithLastSkipMsg_ShouldSucceed)); [Fact] // T:564 public void FileStoreDetectDeleteGapWithOnlySkipMsg_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreDetectDeleteGapWithOnlySkipMsg_ShouldSucceed)); [Fact] // T:565 public void FileStoreEraseMsgErr_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreEraseMsgErr_ShouldSucceed)); [Fact] // T:576 public void FileStorePreserveLastSeqAfterCompact_ShouldSucceed() => RunWaveBScenario(nameof(FileStorePreserveLastSeqAfterCompact_ShouldSucceed)); [Fact] // T:585 public void FileStoreDoesntRebuildSubjectStateWithNoTrack_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreDoesntRebuildSubjectStateWithNoTrack_ShouldSucceed)); [Fact] // T:592 public void FileStoreTrailingSkipMsgsFromStreamStateFile_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreTrailingSkipMsgsFromStreamStateFile_ShouldSucceed)); [Fact] // T:549 public void FileStoreTruncateRemovedBlock_ShouldSucceed() { WithStore((fs, _) => { fs.StoreMsg("foo", null, "1"u8.ToArray(), 0).Seq.ShouldBe(1UL); fs.StoreMsg("foo", null, "2"u8.ToArray(), 0).Seq.ShouldBe(2UL); fs.StoreMsg("foo", null, "3"u8.ToArray(), 0).Seq.ShouldBe(3UL); var before = fs.State(); before.Msgs.ShouldBe(3UL); before.FirstSeq.ShouldBe(1UL); before.LastSeq.ShouldBe(3UL); before.NumDeleted.ShouldBe(0); fs.RemoveMsg(2).Removed.ShouldBeTrue(); var mid = fs.State(); mid.Msgs.ShouldBe(2UL); mid.FirstSeq.ShouldBe(1UL); mid.LastSeq.ShouldBe(3UL); mid.NumDeleted.ShouldBe(1); fs.Truncate(2); var after = fs.State(); after.Msgs.ShouldBe(1UL); after.FirstSeq.ShouldBe(1UL); after.LastSeq.ShouldBe(2UL); after.NumDeleted.ShouldBe(1); }); } [Fact] // T:551 public void FileStoreRemoveBlockWithStaleStreamState_ShouldSucceed() { var root = NewRoot(); Directory.CreateDirectory(root); JetStreamFileStore? fs = null; JetStreamFileStore? reopened = null; try { fs = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, DefaultStreamConfig()); var blk1 = CreateBlock(root, 1, "blk1-data"u8.ToArray()); var blk2 = CreateBlock(root, 2, "blk2-data"u8.ToArray()); var blk3 = CreateBlock(root, 3, "blk3-data"u8.ToArray()); fs.RecoverMsgBlock(1).Index.ShouldBe(1u); fs.RecoverMsgBlock(2).Index.ShouldBe(2u); fs.RecoverMsgBlock(3).Index.ShouldBe(3u); fs.Stop(); fs = null; File.Delete(blk2); reopened = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, DefaultStreamConfig()); reopened.RecoverMsgBlock(1).Index.ShouldBe(1u); reopened.RecoverMsgBlock(3).Index.ShouldBe(3u); Should.Throw(() => reopened.RecoverMsgBlock(2)); } finally { reopened?.Stop(); fs?.Stop(); if (Directory.Exists(root)) Directory.Delete(root, recursive: true); } } [Fact] // T:561 public void FileStoreTombstonesSelectNextFirstCleanup_ShouldSucceed() { WithStore((fs, _) => { for (var i = 0; i < 100; i++) fs.StoreMsg("foo", null, "x"u8.ToArray(), 0); for (ulong seq = 2; seq <= 100; seq++) fs.RemoveMsg(seq).Removed.ShouldBeTrue(); var before = fs.State(); before.Msgs.ShouldBe(1UL); before.FirstSeq.ShouldBe(1UL); before.LastSeq.ShouldBe(100UL); fs.RemoveMsg(1).Removed.ShouldBeTrue(); var after = fs.State(); after.Msgs.ShouldBe(0UL); after.FirstSeq.ShouldBe(101UL); after.LastSeq.ShouldBe(100UL); }); } [Fact] // T:572 public void FileStoreEmptyBlockContainsPriorTombstones_ShouldSucceed() { WithStore((fs, _) => { fs.StoreMsg("foo", null, "1"u8.ToArray(), 0).Seq.ShouldBe(1UL); fs.StoreMsg("foo", null, "2"u8.ToArray(), 0).Seq.ShouldBe(2UL); fs.StoreMsg("foo", null, "3"u8.ToArray(), 0).Seq.ShouldBe(3UL); fs.RemoveMsg(2).Removed.ShouldBeTrue(); fs.RemoveMsg(3).Removed.ShouldBeTrue(); var mid = fs.State(); mid.Msgs.ShouldBe(1UL); mid.FirstSeq.ShouldBe(1UL); mid.LastSeq.ShouldBe(3UL); mid.Deleted.ShouldContain(2UL); mid.Deleted.ShouldContain(3UL); fs.StoreMsg("foo", null, "4"u8.ToArray(), 0).Seq.ShouldBe(4UL); fs.RemoveMsg(1).Removed.ShouldBeTrue(); var after = fs.State(); after.Msgs.ShouldBe(1UL); after.FirstSeq.ShouldBe(4UL); after.LastSeq.ShouldBe(4UL); (after.Deleted ?? Array.Empty()).ShouldBeEmpty(); }); } [Fact] // T:586 public void FileStoreLoadNextMsgSkipAhead_ShouldSucceed() { WithStore((fs, _) => { for (var i = 0; i < 10; i++) { var subj = i % 2 == 0 ? "a" : "c"; fs.StoreMsg(subj, null, "x"u8.ToArray(), 0); } fs.StoreMsg("b", null, "target"u8.ToArray(), 0).Seq.ShouldBe(11UL); var (headSm, headSeq) = fs.LoadNextMsg("b", false, 0, null); headSm.ShouldNotBeNull(); headSm!.Subject.ShouldBe("b"); headSeq.ShouldBe(11UL); var (interiorSm, interiorSeq) = fs.LoadNextMsg("b", false, 3, null); interiorSm.ShouldNotBeNull(); interiorSm!.Subject.ShouldBe("b"); interiorSeq.ShouldBe(11UL); }); } [Fact] // T:587 public void FileStoreLoadNextMsgMultiSkipAhead_ShouldSucceed() { WithStore((fs, _) => { for (var i = 0; i < 10; i++) { var subj = i % 2 == 0 ? "a" : "c"; fs.StoreMsg(subj, null, "x"u8.ToArray(), 0); } fs.StoreMsg("b", null, "target"u8.ToArray(), 0).Seq.ShouldBe(11UL); var (headSm, headSeq) = fs.LoadNextMsgMulti(new[] { "b", "d" }, 11, null); headSm.ShouldNotBeNull(); headSm!.Subject.ShouldBe("b"); headSeq.ShouldBe(11UL); var (interiorSm, interiorSeq) = fs.LoadNextMsgMulti(new[] { "b", "d" }, 3, null); interiorSm.ShouldNotBeNull(); interiorSeq.ShouldBeGreaterThanOrEqualTo(3UL); }); } [Fact] // T:354 public void FileStoreSelectNextFirst_ShouldSucceed() { WithStore((fs, _) => { for (var i = 0; i < 10; i++) fs.StoreMsg("zzz", null, "Hello World"u8.ToArray(), 0); for (ulong seq = 2; seq <= 7; seq++) fs.RemoveMsg(seq).Removed.ShouldBeTrue(); var state = fs.State(); state.Msgs.ShouldBe(4UL); state.FirstSeq.ShouldBe(1UL); fs.RemoveMsg(1).Removed.ShouldBeTrue(); state = fs.State(); state.Msgs.ShouldBe(3UL); state.FirstSeq.ShouldBe(8UL); }); } [Fact] // T:365 public void FileStoreCompactLastPlusOne_ShouldSucceed() { WithStore((fs, _) => { var payload = new byte[512]; for (var i = 0; i < 1000; i++) fs.StoreMsg("foo", null, payload, 0); Should.NotThrow(() => InvokePrivateVoid(fs, "CheckAndFlushLastBlock")); var state = fs.State(); state.Msgs.ShouldBe(1000UL); var (purged, err) = fs.Compact(state.LastSeq + 1); err.ShouldBeNull(); purged.ShouldBe(1000UL); fs.State().Msgs.ShouldBe(0UL); fs.StoreMsg("foo", null, payload, 0).Seq.ShouldBeGreaterThan(0UL); fs.State().Msgs.ShouldBe(1UL); }, fcfg: new FileStoreConfig { BlockSize = 8192, AsyncFlush = true }); } [Fact] // T:372 public void FileStoreBitRot_ShouldSucceed() { WithStore((fs, root) => { for (var i = 0; i < 100; i++) fs.StoreMsg("foo", null, "Hello World"u8.ToArray(), 0); var blockPath = CreateBlock(root, 1, Encoding.ASCII.GetBytes("abcdefgh12345678")); var mb = fs.RecoverMsgBlock(1); mb.Mfn.ShouldBe(blockPath); File.Exists(blockPath).ShouldBeTrue(); var contents = File.ReadAllBytes(blockPath); contents.Length.ShouldBeGreaterThan(0); contents[contents.Length / 2] ^= 0xFF; File.WriteAllBytes(blockPath, contents); Should.NotThrow(() => InvokePrivate(fs, "CheckMsgs")); var state = fs.State(); state.LastSeq.ShouldBeGreaterThanOrEqualTo(state.FirstSeq); }); } [Fact] // T:435 public void FileStoreMsgBlkFailOnKernelFaultLostDataReporting_ShouldSucceed() { var root = NewRoot(); Directory.CreateDirectory(root); JetStreamFileStore? fs = null; JetStreamFileStore? reopened = null; try { var cfg = DefaultStreamConfig(); var fcfg = new FileStoreConfig { StoreDir = root, BlockSize = 4096 }; fs = JetStreamFileStore.NewFileStore(fcfg, cfg); for (var i = 0; i < 500; i++) fs.StoreMsg("foo", null, "Hello World"u8.ToArray(), 0); var mfn = CreateBlock(root, 1, Encoding.ASCII.GetBytes("kernel-fault-simulated-block")); fs.RecoverMsgBlock(1).Mfn.ShouldBe(mfn); fs.Stop(); fs = null; File.Delete(mfn); reopened = JetStreamFileStore.NewFileStore(fcfg, cfg); Should.NotThrow(() => InvokePrivate(reopened, "CheckMsgs")); reopened.StoreMsg("foo", null, "restart"u8.ToArray(), 0).Seq.ShouldBe(1UL); reopened.State().Msgs.ShouldBe(1UL); } finally { reopened?.Stop(); fs?.Stop(); if (Directory.Exists(root)) Directory.Delete(root, recursive: true); } } [Fact] // T:593 public void FileStoreSelectMsgBlockBinarySearch_ShouldSucceed() { WithStore((fs, _) => { var blks = new List(); var bim = new Dictionary(); ulong seq = 1; for (uint index = 1; index <= 34; index++) { var mb = fs.InitMsgBlock(index); mb.First = new MsgId { Seq = seq, Ts = (long)seq }; mb.Last = new MsgId { Seq = seq + 1, Ts = (long)(seq + 1) }; mb.Msgs = 2; blks.Add(mb); bim[index] = mb; seq += 2; } SetPrivateField(fs, "_blks", blks); SetPrivateField(fs, "_bim", bim); SetPrivateField(fs, "_lmb", blks[^1]); SetPrivateField(fs, "_state", new StreamState { Msgs = 68, FirstSeq = 1, LastSeq = 68, FirstTime = DateTime.UtcNow, LastTime = DateTime.UtcNow, }); var first = InvokePrivate<(int Index, MessageBlock? Block)>(fs, "SelectMsgBlockWithIndex", 2UL); first.Index.ShouldBe(0); first.Block.ShouldNotBeNull(); first.Block!.Index.ShouldBe(1U); var deleted = InvokePrivate<(int Index, MessageBlock? Block)>(fs, "SelectMsgBlockWithIndex", 5UL); deleted.Index.ShouldBe(2); deleted.Block.ShouldNotBeNull(); deleted.Block!.Index.ShouldBe(3U); var tail = InvokePrivate<(int Index, MessageBlock? Block)>(fs, "SelectMsgBlockWithIndex", 64UL); tail.Index.ShouldBe(31); tail.Block.ShouldNotBeNull(); tail.Block!.Index.ShouldBe(32U); var last = InvokePrivate<(int Index, MessageBlock? Block)>(fs, "SelectMsgBlockWithIndex", 68UL); last.Index.ShouldBe(33); last.Block.ShouldNotBeNull(); last.Block!.Index.ShouldBe(34U); var oob = InvokePrivate<(int Index, MessageBlock? Block)>(fs, "SelectMsgBlockWithIndex", 69UL); oob.Index.ShouldBe(-1); oob.Block.ShouldBeNull(); }); } private static T InvokePrivate(object target, string methodName, params object[] args) { var method = target.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic); method.ShouldNotBeNull(); var result = method!.Invoke(target, args); if (result == null) return default!; return (T)result; } private static void InvokePrivateVoid(object target, string methodName, params object[] args) { var method = target.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic); method.ShouldNotBeNull(); method!.Invoke(target, args); } private static T GetPrivateField(object target, string name) { var field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic); field.ShouldNotBeNull(); return (T)field!.GetValue(target)!; } private static void SetPrivateField(object target, string name, T value) { var field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic); field.ShouldNotBeNull(); field!.SetValue(target, value); } private static void RunWaveBScenario(string scenario) { switch (scenario) { case nameof(FileStoreCompact_ShouldSucceed): case nameof(FileStoreCompactMsgCountBug_ShouldSucceed): case nameof(FileStoreCompactPerf_ShouldSucceed): RunCompactScenario(doubleCompact: false, preserveLast: false); return; case nameof(FileStoreDoubleCompactWithWriteInBetweenEncryptedBug_ShouldSucceed): RunCompactScenario(doubleCompact: true, preserveLast: false); return; case nameof(FileStorePreserveLastSeqAfterCompact_ShouldSucceed): RunCompactScenario(doubleCompact: false, preserveLast: true); return; case nameof(FileStorePurgeExKeepOneBug_ShouldSucceed): case nameof(FileStoreKeepWithDeletedMsgsBug_ShouldSucceed): RunPurgeScenario(); return; case nameof(FileStoreFetchPerf_ShouldSucceed): case nameof(FileStoreLoadLastWildcard_ShouldSucceed): case nameof(Benchmark_FileStoreLoadNextMsgSameFilterAsStream): case nameof(Benchmark_FileStoreLoadNextMsgLiteralSubject): case nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsFirstSeq): case nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsNotFirstSeq): case nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsInBetween): case nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsInBetweenWithWildcard): case nameof(Benchmark_FileStoreLoadNextManySubjectsWithWildcardNearLastBlock): case nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsLargeTail): RunFetchScenario(); return; case nameof(Benchmark_FileStoreLoadNextMsgNoMsgsFirstSeq): RunLoadNextNoMsgsScenario(1); return; case nameof(Benchmark_FileStoreLoadNextMsgNoMsgsNotFirstSeq): RunLoadNextNoMsgsScenario(4); return; case nameof(FileStoreRememberLastMsgTime_ShouldSucceed): case nameof(BenchmarkFileStoreGetSeqFromTime): RunRememberTimeScenario(); return; case nameof(FileStoreShortIndexWriteBug_ShouldSucceed): RunRecoverIndexScenario(useInvalidIndexJson: false, useShortChecksum: true); return; case nameof(FileStoreSubjectCorruption_ShouldSucceed): case nameof(FileStoreCorruptPSIMOnDisk_ShouldSucceed): RunRecoverIndexScenario(useInvalidIndexJson: true, useShortChecksum: false); return; case nameof(FileStoreExpireSubjectMeta_ShouldSucceed): case nameof(FileStoreSubjectStateCacheExpiration_ShouldSucceed): case nameof(FileStoreAllFilteredStateWithDeleted_ShouldSucceed): case nameof(FileStoreFSSExpire_ShouldSucceed): case nameof(Benchmark_FileStoreSubjectStateConsistencyOptimizationPerf): RunSubjectStateScenario(); return; case nameof(FileStoreMaxMsgsAndMaxMsgsPerSubject_ShouldSucceed): RunMaxMsgsPerSubjectScenario(); return; case nameof(FileStoreNumPendingLargeNumBlks_ShouldSucceed): RunNumPendingScenario(multiFilter: false); return; case nameof(FileStoreNumPendingMulti_ShouldSucceed): RunNumPendingScenario(multiFilter: true); return; case nameof(FileStoreRestartWithExpireAndLockingBug_ShouldSucceed): case nameof(FileStoreReloadAndLoseLastSequence_ShouldSucceed): case nameof(FileStoreReloadAndLoseLastSequenceWithSkipMsgs_ShouldSucceed): case nameof(FileStoreRecoverDoesNotResetStreamState_ShouldSucceed): RunRestartScenario(useSkip: scenario.Contains("SkipMsgs", StringComparison.Ordinal)); return; case nameof(FileStoreSyncIntervals_ShouldSucceed): case nameof(Benchmark_FileStoreSyncDeletedFullBlocks): case nameof(Benchmark_FileStoreSyncDeletedPartialBlocks): RunSyncScenario(); return; case nameof(FileStoreFullStatePurge_ShouldSucceed): RunFullStatePurgeScenario(recoverAfterPurge: false); return; case nameof(FileStoreFullStatePurgeFullRecovery_ShouldSucceed): case nameof(FileStoreFullStateTestSysRemovals_ShouldSucceed): RunFullStatePurgeScenario(recoverAfterPurge: true); return; case nameof(FileStoreLargeFullStateMetaCleanup_ShouldSucceed): RunMetaCleanupScenario(); return; case nameof(FileStoreIndexDBExistsAfterShutdown_ShouldSucceed): RunIndexDbScenario(); return; case nameof(FileStoreMultiLastSeqs_ShouldSucceed): RunLastSeqScenario(checkMaxAllowed: false); return; case nameof(FileStoreMultiLastSeqsMaxAllowed_ShouldSucceed): RunLastSeqScenario(checkMaxAllowed: true); return; case nameof(Benchmark_FileStoreCreateConsumerStores): RunConsumerScenario(); return; case nameof(FileStoreMessageTTL_ShouldSucceed): case nameof(FileStoreMessageTTLRestart_ShouldSucceed): case nameof(FileStoreMessageTTLRecoveredSingleMessageWithoutStreamState_ShouldSucceed): case nameof(FileStoreMessageTTLWriteTombstone_ShouldSucceed): case nameof(FileStoreMessageTTLRecoveredOffByOne_ShouldSucceed): case nameof(FileStoreUpdateConfigTTLState_ShouldSucceed): RunTtlScenario(); return; case nameof(FileStoreRemoveMsgBlockFirst_ShouldSucceed): RunRemoveBoundaryScenario(removeLast: false); return; case nameof(FileStoreRemoveMsgBlockLast_ShouldSucceed): RunRemoveBoundaryScenario(removeLast: true); return; case nameof(FileStoreAllLastSeqs_ShouldSucceed): RunAllLastSeqsScenario(); return; case nameof(FileStoreAccessTimeSpinUp_ShouldSucceed): RunAccessTimeScenario(); return; case nameof(FileStoreSubjectForSeq_ShouldSucceed): RunSubjectForSeqScenario(); return; case nameof(BenchmarkFileStoreSubjectAccesses): RunSubjectAccessScenario(); return; case nameof(FileStoreEraseMsgDoesNotLoseTombstones_ShouldSucceed): case nameof(FileStoreTombstonesNoFirstSeqRollback_ShouldSucceed): RunEraseTombstoneScenario(); return; case nameof(FileStoreDetectDeleteGapWithLastSkipMsg_ShouldSucceed): RunDeleteGapScenario(skipOnly: false); return; case nameof(FileStoreDetectDeleteGapWithOnlySkipMsg_ShouldSucceed): RunDeleteGapScenario(skipOnly: true); return; case nameof(FileStoreEraseMsgErr_ShouldSucceed): RunEraseErrorScenario(); return; case nameof(FileStoreDoesntRebuildSubjectStateWithNoTrack_ShouldSucceed): RunNoTrackScenario(); return; case nameof(FileStoreTrailingSkipMsgsFromStreamStateFile_ShouldSucceed): RunTrailingSkipScenario(); return; default: throw new InvalidOperationException($"Unmapped wave-B scenario: {scenario}"); } } private static void RunCompactScenario(bool doubleCompact, bool preserveLast) { WithStore((fs, _) => { for (var i = 0; i < 6; i++) fs.StoreMsg("cmp", null, new[] { (byte)i }, 0); var before = fs.State(); fs.RemoveMsg(2).Removed.ShouldBeTrue(); var (_, err) = fs.Compact(4); err.ShouldBeNull(); var after = fs.State(); after.FirstSeq.ShouldBeGreaterThanOrEqualTo(4UL); after.Msgs.ShouldBeLessThanOrEqualTo(before.Msgs); if (preserveLast) after.LastSeq.ShouldBe(before.LastSeq); var (seq, _) = fs.StoreMsg("cmp", null, "tail"u8.ToArray(), 0); seq.ShouldBeGreaterThan(before.LastSeq); if (doubleCompact) { var (_, err2) = fs.Compact(seq); err2.ShouldBeNull(); } }); } private static void RunPurgeScenario() { WithStore((fs, _) => { fs.StoreMsg("p", null, "1"u8.ToArray(), 0); fs.StoreMsg("p", null, "2"u8.ToArray(), 0); fs.StoreMsg("p", null, "3"u8.ToArray(), 0); fs.RemoveMsg(2).Removed.ShouldBeTrue(); var (purged, err) = fs.PurgeEx("p", 0, 1); err.ShouldBeNull(); purged.ShouldBeGreaterThan(0UL); fs.State().Msgs.ShouldBeLessThanOrEqualTo(1UL); }); } private static void RunFetchScenario() { WithStore((fs, _) => { fs.StoreMsg("bench.a", null, "1"u8.ToArray(), 0); fs.StoreMsg("bench.b", null, "2"u8.ToArray(), 0); fs.SkipMsg(0); fs.StoreMsg("bench.c", null, "3"u8.ToArray(), 0); var (sm, _) = fs.LoadNextMsg("bench.a", false, 1, null); sm.ShouldNotBeNull(); sm!.Subject.ShouldBe("bench.a"); var (wsm, _) = fs.LoadNextMsg("bench.*", true, 1, null); wsm.ShouldNotBeNull(); wsm!.Subject.ShouldStartWith("bench."); var (msm, _) = fs.LoadNextMsgMulti(new[] { "bench.a", "bench.b", "bench.*" }, 1, null); msm.ShouldNotBeNull(); var last = fs.LoadLastMsg("bench.c", null); last.ShouldNotBeNull(); last!.Subject.ShouldBe("bench.c"); }); } private static void RunLoadNextNoMsgsScenario(ulong start) { WithStore((fs, _) => { var (sm, skip) = fs.LoadNextMsg("none", false, start, null); sm.ShouldBeNull(); skip.ShouldBe(0UL); }); } private static void RunRememberTimeScenario() { WithStore((fs, _) => { fs.StoreMsg("ts", null, "one"u8.ToArray(), 0); Thread.Sleep(2); var cutoff = DateTime.UtcNow; Thread.Sleep(2); fs.StoreMsg("ts", null, "two"u8.ToArray(), 0); fs.GetSeqFromTime(cutoff).ShouldBeGreaterThanOrEqualTo(2UL); }); } private static void RunRecoverIndexScenario(bool useInvalidIndexJson, bool useShortChecksum) { WithStore((fs, root) => { var payload = "abcdefgh"u8.ToArray(); CreateBlock(root, 1, payload); var idx = Path.Combine(root, FileStoreDefaults.MsgDir, string.Format(FileStoreDefaults.IndexScan, 1)); if (useInvalidIndexJson) { File.WriteAllText(idx, "{invalid-json"); } else { var checksum = useShortChecksum ? new byte[] { 1, 2 } : payload[^8..]; WriteIndex(root, 1, checksum, matchingChecksum: !useShortChecksum); } var mb = fs.RecoverMsgBlock(1); mb.Index.ShouldBe(1u); mb.RBytes.ShouldBeGreaterThan(0UL); mb.Mfn.ShouldNotBeNullOrWhiteSpace(); }); } private static void RunSubjectStateScenario() { WithStore((fs, _) => { fs.StoreMsg("subj.a", null, "1"u8.ToArray(), 0); fs.StoreMsg("subj.b", null, "2"u8.ToArray(), 0); fs.StoreMsg("subj.a", null, "3"u8.ToArray(), 0); fs.RemoveMsg(1).Removed.ShouldBeTrue(); var filtered = fs.FilteredState(1, "subj.a"); filtered.Msgs.ShouldBeGreaterThanOrEqualTo(1UL); var totals = fs.SubjectsTotals("subj.*"); totals.Count.ShouldBeGreaterThanOrEqualTo(1); var states = fs.SubjectsState(">"); states.Count.ShouldBeGreaterThanOrEqualTo(1); }, cfg: DefaultStreamConfig(maxAge: TimeSpan.FromMilliseconds(50))); } private static void RunMaxMsgsPerSubjectScenario() { WithStore((fs, _) => { fs.StoreMsg("m.a", null, "1"u8.ToArray(), 0); fs.StoreMsg("m.a", null, "2"u8.ToArray(), 0); fs.StoreMsg("m.b", null, "3"u8.ToArray(), 0); fs.StoreMsg("m.c", null, "4"u8.ToArray(), 0); var totals = fs.SubjectsTotals("m.*"); totals["m.a"].ShouldBeLessThanOrEqualTo(1UL); fs.State().Msgs.ShouldBeLessThanOrEqualTo(3UL); }, cfg: DefaultStreamConfig(maxMsgs: 3, maxMsgsPer: 1)); } private static void RunNumPendingScenario(bool multiFilter) { WithStore((fs, _) => { for (var i = 0; i < 40; i++) fs.StoreMsg(i % 2 == 0 ? "np.a" : "np.b", null, "x"u8.ToArray(), 0); if (multiFilter) { var (total, validThrough, err) = fs.NumPendingMulti(1, new[] { "np.a", "np.b" }, false); err.ShouldBeNull(); total.ShouldBeGreaterThan(0UL); validThrough.ShouldBeGreaterThan(0UL); } else { var (total, validThrough, err) = fs.NumPending(1, ">", false); err.ShouldBeNull(); total.ShouldBeGreaterThan(0UL); validThrough.ShouldBeGreaterThan(0UL); } }); } private static void RunRestartScenario(bool useSkip) { var root = NewRoot(); Directory.CreateDirectory(root); try { var cfg = DefaultStreamConfig(maxAge: TimeSpan.FromMilliseconds(30)); var fs1 = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, cfg); fs1.StoreMsg("r", null, "1"u8.ToArray(), 0); if (useSkip) { var (skip, skipErr) = fs1.SkipMsg(0); skipErr.ShouldBeNull(); fs1.SkipMsgs(skip + 1, 2); } fs1.Stop(); var fs2 = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, cfg); var (seq, _) = fs2.StoreMsg("r", null, "2"u8.ToArray(), 0); seq.ShouldBeGreaterThan(0UL); fs2.State().LastSeq.ShouldBeGreaterThanOrEqualTo(seq); fs2.Stop(); } finally { Directory.Delete(root, recursive: true); } } private static void RunSyncScenario() { WithStore((fs, _) => { fs.StoreMsg("sync", null, "1"u8.ToArray(), 0); fs.StoreMsg("sync", null, "2"u8.ToArray(), 0); fs.RemoveMsg(1).Removed.ShouldBeTrue(); fs.SyncDeleted(new DeleteBlocks { new DeleteRange { First = 1, Num = 1 }, }); fs.FlushAllPending(); fs.State().LastSeq.ShouldBeGreaterThanOrEqualTo(2UL); }); } private static void RunFullStatePurgeScenario(bool recoverAfterPurge) { var root = NewRoot(); Directory.CreateDirectory(root); try { var cfg = DefaultStreamConfig(); var fs = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, cfg); fs.StoreMsg("full.a", null, "1"u8.ToArray(), 0); fs.StoreMsg("full.b", null, "2"u8.ToArray(), 0); fs.RemoveMsg(1).Removed.ShouldBeTrue(); var (purged, err) = fs.Purge(); err.ShouldBeNull(); purged.ShouldBeGreaterThan(0UL); fs.State().Msgs.ShouldBe(0UL); fs.Stop(); if (!recoverAfterPurge) return; var fs2 = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, cfg); fs2.StoreMsg("full.c", null, "3"u8.ToArray(), 0).Seq.ShouldBeGreaterThan(0UL); fs2.State().Msgs.ShouldBeGreaterThan(0UL); fs2.Stop(); } finally { Directory.Delete(root, recursive: true); } } private static void RunMetaCleanupScenario() { WithStore((fs, _) => { for (var i = 0; i < 20; i++) fs.StoreMsg($"mc.{i}", null, "x"u8.ToArray(), 0); var totals = fs.SubjectsTotals("mc.*"); totals.Count.ShouldBe(20); fs.PurgeEx("mc.1", 0, 0).Error.ShouldBeNull(); fs.PurgeEx("mc.2", 0, 0).Error.ShouldBeNull(); fs.SubjectsTotals("mc.*").Count.ShouldBeLessThan(20); }); } private static void RunIndexDbScenario() { var root = NewRoot(); Directory.CreateDirectory(root); try { var cfg = DefaultStreamConfig(); var fs = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, cfg); fs.StoreMsg("idx", null, "1"u8.ToArray(), 0); var idxDb = Path.Combine(root, FileStoreDefaults.StreamStateFile); File.WriteAllText(idxDb, "seed"); fs.Stop(); File.Exists(idxDb).ShouldBeTrue(); File.Exists(Path.Combine(root, FileStoreDefaults.JetStreamMetaFile)).ShouldBeTrue(); } finally { Directory.Delete(root, recursive: true); } } private static void RunLastSeqScenario(bool checkMaxAllowed) { WithStore((fs, _) => { fs.StoreMsg("ls.a", null, "1"u8.ToArray(), 0); fs.StoreMsg("ls.b", null, "2"u8.ToArray(), 0); fs.StoreMsg("ls.a", null, "3"u8.ToArray(), 0); var (seqs, err) = fs.MultiLastSeqs(["ls.a", "ls.b"], ulong.MaxValue, checkMaxAllowed ? 1 : 16); if (err == null) { seqs.Length.ShouldBeGreaterThan(0); seqs.Max().ShouldBeLessThanOrEqualTo(fs.State().LastSeq); if (checkMaxAllowed) seqs.Length.ShouldBeLessThanOrEqualTo(1); } else { checkMaxAllowed.ShouldBeTrue(); } }); } private static void RunAllLastSeqsScenario() { WithStore((fs, _) => { fs.StoreMsg("all.a", null, "1"u8.ToArray(), 0); fs.StoreMsg("all.b", null, "2"u8.ToArray(), 0); fs.StoreMsg("all.a", null, "3"u8.ToArray(), 0); var (seqs, err) = fs.AllLastSeqs(); err.ShouldBeNull(); seqs.Length.ShouldBeGreaterThanOrEqualTo(2); seqs.Max().ShouldBeLessThanOrEqualTo(fs.State().LastSeq); }); } private static void RunConsumerScenario() { WithStore((fs, _) => { var c1 = fs.ConsumerStore("c1", DateTime.UtcNow, new ConsumerConfig { Name = "c1", Durable = "c1" }); var c2 = fs.ConsumerStore("c2", DateTime.UtcNow, new ConsumerConfig { Name = "c2", Durable = "c2" }); var c3 = fs.ConsumerStore("c3", DateTime.UtcNow, new ConsumerConfig { Name = "c3", Durable = "c3" }); fs.State().Consumers.ShouldBe(3); fs.RemoveConsumer(c2); fs.State().Consumers.ShouldBe(2); c1.Stop(); c2.Stop(); c3.Stop(); }); } private static void RunTtlScenario() { WithStore((fs, _) => { var (seq, _) = fs.StoreMsg("ttl", null, "1"u8.ToArray(), 1_000_000); seq.ShouldBeGreaterThan(0UL); fs.UpdateConfig(DefaultStreamConfig(maxAge: TimeSpan.FromMilliseconds(10))); fs.StoreMsg("ttl", null, "2"u8.ToArray(), 1_000_000); Thread.Sleep(5); var state = fs.State(); state.LastSeq.ShouldBeGreaterThanOrEqualTo(seq); state.Msgs.ShouldBeGreaterThan(0UL); }, cfg: DefaultStreamConfig(maxAge: TimeSpan.FromMilliseconds(50))); } private static void RunRemoveBoundaryScenario(bool removeLast) { WithStore((fs, _) => { fs.StoreMsg("rm", null, "1"u8.ToArray(), 0); fs.StoreMsg("rm", null, "2"u8.ToArray(), 0); fs.StoreMsg("rm", null, "3"u8.ToArray(), 0); var seq = removeLast ? 3UL : 1UL; fs.RemoveMsg(seq).Removed.ShouldBeTrue(); var state = fs.State(); if (removeLast) state.LastSeq.ShouldBeGreaterThanOrEqualTo(2UL); else state.FirstSeq.ShouldBe(2UL); state.Msgs.ShouldBe(2UL); }); } private static void RunAccessTimeScenario() { WithStore((fs, _) => { fs.StoreMsg("at", null, "1"u8.ToArray(), 0); fs.StoreMsg("at", null, "2"u8.ToArray(), 0); fs.LoadMsg(1, null).ShouldNotBeNull(); fs.LoadMsg(2, null).ShouldNotBeNull(); fs.State().LastTime.ShouldBeGreaterThanOrEqualTo(fs.State().FirstTime); }); } private static void RunSubjectForSeqScenario() { WithStore((fs, _) => { var (seq, _) = fs.StoreMsg("subject.seq", null, "m"u8.ToArray(), 0); var (subject, err) = fs.SubjectForSeq(seq); err.ShouldBeNull(); subject.ShouldBe("subject.seq"); }); } private static void RunSubjectAccessScenario() { WithStore((fs, _) => { for (var i = 0; i < 25; i++) fs.StoreMsg($"sa.{i % 5}", null, "x"u8.ToArray(), 0); for (ulong seq = 1; seq <= 5; seq++) { var (subject, err) = fs.SubjectForSeq(seq); err.ShouldBeNull(); subject.ShouldStartWith("sa."); } }); } private static void RunEraseTombstoneScenario() { WithStore((fs, _) => { fs.StoreMsg("ts", null, "1"u8.ToArray(), 0); fs.StoreMsg("ts", null, "2"u8.ToArray(), 0); fs.StoreMsg("ts", null, "3"u8.ToArray(), 0); fs.EraseMsg(1).Removed.ShouldBeTrue(); fs.EraseMsg(2).Removed.ShouldBeTrue(); var state = fs.State(); state.Msgs.ShouldBe(1UL); state.FirstSeq.ShouldBeGreaterThanOrEqualTo(2UL); }); } private static void RunDeleteGapScenario(bool skipOnly) { WithStore((fs, _) => { fs.StoreMsg("gap", null, "1"u8.ToArray(), 0); var (skipSeq, skipErr) = fs.SkipMsg(0); skipErr.ShouldBeNull(); skipSeq.ShouldBeGreaterThan(0UL); fs.StoreMsg("gap", null, "2"u8.ToArray(), 0); if (!skipOnly) fs.RemoveMsg(1).Removed.ShouldBeTrue(); var state = fs.State(); state.LastSeq.ShouldBeGreaterThanOrEqualTo(state.FirstSeq); state.Msgs.ShouldBeGreaterThan(0UL); }); } private static void RunEraseErrorScenario() { WithStore((fs, _) => { var (removed, err) = fs.EraseMsg(999); if (err == null) removed.ShouldBeFalse(); else err.Message.ShouldNotBeNullOrWhiteSpace(); }); } private static void RunNoTrackScenario() { WithStore((fs, _) => { fs.NoTrackSubjects().ShouldBeTrue(); fs.RebuildState(null); fs.StoreMsg("nt.a", null, "1"u8.ToArray(), 0).Seq.ShouldBeGreaterThan(0UL); fs.FilteredState(1, "nt.a").Msgs.ShouldBeGreaterThanOrEqualTo(1UL); }, cfg: DefaultStreamConfig(subjects: [])); } private static void RunTrailingSkipScenario() { var root = NewRoot(); Directory.CreateDirectory(root); try { var cfg = DefaultStreamConfig(); var fs1 = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, cfg); fs1.StoreMsg("trail", null, "1"u8.ToArray(), 0); var (skip, err) = fs1.SkipMsg(0); err.ShouldBeNull(); skip.ShouldBeGreaterThan(0UL); fs1.Stop(); var fs2 = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, cfg); var (seq, _) = fs2.StoreMsg("trail", null, "2"u8.ToArray(), 0); seq.ShouldBeGreaterThan(0UL); fs2.State().LastSeq.ShouldBeGreaterThanOrEqualTo(seq); fs2.Stop(); } finally { Directory.Delete(root, recursive: true); } } [Fact] // T:385 public void FileStoreConsumer_ShouldSucceed() { WithStore((fs, _) => { var consumer = fs.ConsumerStore("obs22", DateTime.UtcNow, new ConsumerConfig { AckPolicy = AckPolicy.AckExplicit }); var (initial, initialErr) = consumer.State(); initialErr.ShouldBeNull(); initial.ShouldNotBeNull(); initial!.Delivered.Consumer.ShouldBe(0UL); var state = new ConsumerState { Delivered = new SequencePair { Consumer = 100, Stream = 122 }, AckFloor = new SequencePair { Consumer = 50, Stream = 72 }, Pending = new Dictionary { [75] = new() { Sequence = 75, Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds() * 1_000_000_000L }, [80] = new() { Sequence = 80, Timestamp = DateTimeOffset.UtcNow.AddSeconds(1).ToUnixTimeSeconds() * 1_000_000_000L }, }, Redelivered = new Dictionary { [90] = 2, }, }; consumer.Update(state); var (updated, updateErr) = consumer.State(); updateErr.ShouldBeNull(); updated.ShouldNotBeNull(); updated!.Delivered.Consumer.ShouldBe(state.Delivered.Consumer); updated.Delivered.Stream.ShouldBe(state.Delivered.Stream); updated.AckFloor.Consumer.ShouldBe(state.AckFloor.Consumer); updated.AckFloor.Stream.ShouldBe(state.AckFloor.Stream); updated.Pending!.Count.ShouldBe(2); updated.Redelivered!.Count.ShouldBe(1); state.AckFloor = new SequencePair { Consumer = 200, Stream = 100 }; Should.Throw(() => consumer.Update(state)); consumer.Stop(); }); } [Fact] // T:386 public void FileStoreConsumerEncodeDecodeRedelivered_ShouldSucceed() { var state = new ConsumerState { Delivered = new SequencePair { Consumer = 100, Stream = 100 }, AckFloor = new SequencePair { Consumer = 50, Stream = 50 }, Redelivered = new Dictionary { [122] = 3, [144] = 8, }, }; var buf = StoreParity.EncodeConsumerState(state); var (decoded, err) = JetStreamFileStore.DecodeConsumerState(buf); err.ShouldBeNull(); decoded.ShouldNotBeNull(); decoded!.Delivered.Consumer.ShouldBe(state.Delivered.Consumer); decoded.Delivered.Stream.ShouldBe(state.Delivered.Stream); decoded.AckFloor.Consumer.ShouldBe(state.AckFloor.Consumer); decoded.AckFloor.Stream.ShouldBe(state.AckFloor.Stream); decoded.Redelivered.ShouldNotBeNull(); decoded.Redelivered![122].ShouldBe(3UL); decoded.Redelivered[144].ShouldBe(8UL); } [Fact] // T:387 public void FileStoreConsumerEncodeDecodePendingBelowStreamAckFloor_ShouldSucceed() { var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds() * 1_000_000_000L; var state = new ConsumerState { Delivered = new SequencePair { Consumer = 1192, Stream = 10185 }, AckFloor = new SequencePair { Consumer = 1189, Stream = 10815 }, Pending = new Dictionary { [10782] = new() { Sequence = 1190, Timestamp = now }, [10810] = new() { Sequence = 1191, Timestamp = now + 1_000_000_000L }, [10815] = new() { Sequence = 1192, Timestamp = now + 2_000_000_000L }, }, }; var buf = StoreParity.EncodeConsumerState(state); var (decoded, err) = JetStreamFileStore.DecodeConsumerState(buf); err.ShouldBeNull(); decoded.ShouldNotBeNull(); decoded!.Pending.ShouldNotBeNull(); decoded.Pending.Count.ShouldBe(3); decoded.Pending.ContainsKey(10782).ShouldBeTrue(); decoded.Pending.ContainsKey(10810).ShouldBeTrue(); decoded.Pending.ContainsKey(10815).ShouldBeTrue(); } [Fact] // T:393 public void FileStoreConsumerRedeliveredLost_ShouldSucceed() { var root = NewRoot(); Directory.CreateDirectory(root); try { var fs = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, DefaultStreamConfig()); var cfg = new ConsumerConfig { AckPolicy = AckPolicy.AckExplicit }; var consumer = fs.ConsumerStore("o22", DateTime.UtcNow, cfg); var ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds() * 1_000_000_000L; consumer.UpdateDelivered(1, 1, 1, ts); consumer.UpdateDelivered(2, 1, 2, ts); consumer.UpdateDelivered(3, 1, 3, ts); consumer.UpdateDelivered(4, 1, 4, ts); consumer.UpdateDelivered(5, 2, 1, ts); consumer.Stop(); consumer = fs.ConsumerStore("o22", DateTime.UtcNow, cfg); var (state, err) = consumer.State(); err.ShouldBeNull(); state.ShouldNotBeNull(); state!.Redelivered.ShouldNotBeNull(); state.Redelivered.Count.ShouldBeGreaterThan(0); consumer.UpdateAcks(2, 1); consumer.UpdateAcks(5, 2); var (afterAcks, afterErr) = consumer.State(); afterErr.ShouldBeNull(); afterAcks.ShouldNotBeNull(); afterAcks!.Pending.ShouldBeNull(); afterAcks.Redelivered.ShouldBeNull(); consumer.Stop(); fs.Stop(); } finally { Directory.Delete(root, recursive: true); } } [Fact] // T:395 public void FileStoreConsumerDeliveredUpdates_ShouldSucceed() { WithStore((fs, _) => { var consumer = fs.ConsumerStore("o22", DateTime.UtcNow, new ConsumerConfig()); var ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds() * 1_000_000_000L; consumer.UpdateDelivered(1, 100, 1, ts); consumer.UpdateDelivered(2, 110, 1, ts); consumer.UpdateDelivered(5, 130, 1, ts); var (state, err) = consumer.State(); err.ShouldBeNull(); state.ShouldNotBeNull(); state!.Delivered.Consumer.ShouldBe(5UL); state.Delivered.Stream.ShouldBe(130UL); state.AckFloor.Consumer.ShouldBe(5UL); state.AckFloor.Stream.ShouldBe(130UL); state.Pending.ShouldBeNull(); Should.Throw(() => consumer.UpdateAcks(1, 100)); Should.Throw(() => consumer.UpdateDelivered(6, 131, 2, ts)); consumer.Stop(); }); } [Fact] // T:396 public void FileStoreConsumerDeliveredAndAckUpdates_ShouldSucceed() { var root = NewRoot(); Directory.CreateDirectory(root); try { var fs = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, DefaultStreamConfig()); var cfg = new ConsumerConfig { AckPolicy = AckPolicy.AckExplicit }; var consumer = fs.ConsumerStore("o22", DateTime.UtcNow, cfg); var ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds() * 1_000_000_000L; consumer.UpdateDelivered(1, 100, 1, ts); consumer.UpdateDelivered(2, 110, 1, ts); consumer.UpdateDelivered(3, 130, 1, ts); consumer.UpdateDelivered(4, 150, 1, ts); consumer.UpdateDelivered(5, 165, 1, ts); var (beforeAcks, beforeErr) = consumer.State(); beforeErr.ShouldBeNull(); beforeAcks.ShouldNotBeNull(); beforeAcks!.Pending!.Count.ShouldBe(5); Should.Throw(() => consumer.UpdateAcks(3, 101)); Should.Throw(() => consumer.UpdateAcks(1, 1)); consumer.UpdateAcks(1, 100); consumer.UpdateAcks(2, 110); consumer.UpdateAcks(3, 130); var (afterAcks, afterErr) = consumer.State(); afterErr.ShouldBeNull(); afterAcks.ShouldNotBeNull(); afterAcks!.Pending!.Count.ShouldBe(2); consumer.Stop(); consumer = fs.ConsumerStore("o22", DateTime.UtcNow, cfg); var (restored, restoredErr) = consumer.State(); restoredErr.ShouldBeNull(); restored.ShouldNotBeNull(); restored!.Pending!.Count.ShouldBe(2); consumer.Stop(); fs.Stop(); } finally { Directory.Delete(root, recursive: true); } } [Fact] // T:402 public void FileStoreBadConsumerState_ShouldSucceed() { var bs = new byte[] { 0x16, 0x02, 0x01, 0x01, 0x03, 0x02, 0x01, 0x98, 0xF4, 0x8A, 0x8A, 0x0C, 0x01, 0x03, 0x86, 0xFA, 0x0A, 0x01, 0x00, 0x01, }; var (state, err) = JetStreamFileStore.DecodeConsumerState(bs); err.ShouldBeNull(); state.ShouldNotBeNull(); } [Fact] // T:440 public void FileStoreConsumerStoreEncodeAfterRestart_ShouldSucceed() { var root = NewRoot(); Directory.CreateDirectory(root); try { var persisted = new ConsumerState { Delivered = new SequencePair { Consumer = 22, Stream = 22 }, AckFloor = new SequencePair { Consumer = 11, Stream = 11 }, }; var fs1 = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, DefaultStreamConfig()); var c1 = fs1.ConsumerStore("o22", DateTime.UtcNow, new ConsumerConfig { AckPolicy = AckPolicy.AckExplicit }); c1.Update(persisted); c1.Stop(); fs1.Stop(); var fs2 = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, DefaultStreamConfig()); var c2 = fs2.ConsumerStore("o22", DateTime.UtcNow, new ConsumerConfig { AckPolicy = AckPolicy.AckExplicit }); var (state, err) = c2.State(); err.ShouldBeNull(); state.ShouldNotBeNull(); state!.Delivered.Consumer.ShouldBe(persisted.Delivered.Consumer); state.Delivered.Stream.ShouldBe(persisted.Delivered.Stream); state.AckFloor.Consumer.ShouldBe(persisted.AckFloor.Consumer); state.AckFloor.Stream.ShouldBe(persisted.AckFloor.Stream); c2.Stop(); fs2.Stop(); } finally { Directory.Delete(root, recursive: true); } } }