feat(batch14): complete group2 lifecycle checks and wave2 tests
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
|
||||
@@ -59,6 +60,38 @@ public sealed partial class ConcurrencyTests2
|
||||
}
|
||||
}
|
||||
|
||||
[Fact] // T:2476
|
||||
public void NoRaceFilestoreBinaryStreamSnapshotEncodingLargeGaps_ShouldSucceed()
|
||||
{
|
||||
WithStore((fs, _) =>
|
||||
{
|
||||
const int numMsgs = 5000;
|
||||
var payload = new byte[128];
|
||||
|
||||
fs.StoreMsg("zzz", null, payload, 0).Seq.ShouldBe(1UL);
|
||||
for (var i = 2; i < numMsgs; i++)
|
||||
{
|
||||
var (seq, _) = fs.StoreMsg("zzz", null, null, 0);
|
||||
seq.ShouldBeGreaterThan(1UL);
|
||||
fs.RemoveMsg(seq).Removed.ShouldBeTrue();
|
||||
}
|
||||
|
||||
fs.StoreMsg("zzz", null, payload, 0).Seq.ShouldBe((ulong)numMsgs);
|
||||
Should.NotThrow(() => InvokePrivate(fs, "SyncBlocks"));
|
||||
|
||||
var (snapshot, err) = fs.EncodedStreamState(0);
|
||||
err.ShouldBeNull();
|
||||
StoreParity.IsEncodedStreamState(snapshot).ShouldBeTrue();
|
||||
snapshot.Length.ShouldBeLessThan(2048);
|
||||
|
||||
var state = fs.State();
|
||||
state.FirstSeq.ShouldBe(1UL);
|
||||
state.LastSeq.ShouldBe((ulong)numMsgs);
|
||||
state.Msgs.ShouldBe(2UL);
|
||||
state.NumDeleted.ShouldBe(numMsgs - 2);
|
||||
});
|
||||
}
|
||||
|
||||
private static void WithStore(Action<JetStreamFileStore, string> action, StreamConfig? cfg = null)
|
||||
{
|
||||
var root = NewRoot();
|
||||
@@ -94,5 +127,12 @@ public sealed partial class ConcurrencyTests2
|
||||
};
|
||||
}
|
||||
|
||||
private static void InvokePrivate(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 string NewRoot() => Path.Combine(Path.GetTempPath(), $"impl-fs-c2-{Guid.NewGuid():N}");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
|
||||
@@ -374,6 +376,201 @@ public sealed partial class JetStreamFileStoreTests
|
||||
});
|
||||
}
|
||||
|
||||
[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<LostStreamData?>(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<LostStreamData?>(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<MessageBlock>();
|
||||
var bim = new Dictionary<uint, MessageBlock>();
|
||||
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<T>(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<T>(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<T>(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)
|
||||
|
||||
Reference in New Issue
Block a user