using System; using System.Collections.Generic; using System.Diagnostics; using Shouldly; using ZB.MOM.NatsNet.Server.Internal.DataStructures; namespace ZB.MOM.NatsNet.Server.Tests.Internal.DataStructures; public sealed class SequenceSetTests { private static readonly int NumEntries = SequenceSet.NumEntries; // 2048 // --- Basic operations --- [Fact] public void SeqSetBasics_ShouldSucceed() { var ss = new SequenceSet(); var seqs = new ulong[] { 22, 222, 2000, 2, 2, 4 }; foreach (var seq in seqs) { ss.Insert(seq); ss.Exists(seq).ShouldBeTrue(); } ss.Nodes.ShouldBe(1); ss.Size.ShouldBe(seqs.Length - 1); // one duplicate var (lh, rh) = ss.Heights(); lh.ShouldBe(0); rh.ShouldBe(0); } [Fact] public void SeqSetLeftLean_ShouldSucceed() { var ss = new SequenceSet(); for (var i = (ulong)(4 * NumEntries); i > 0; i--) ss.Insert(i); ss.Nodes.ShouldBe(5); ss.Size.ShouldBe(4 * NumEntries); var (lh, rh) = ss.Heights(); lh.ShouldBe(2); rh.ShouldBe(1); } [Fact] public void SeqSetRightLean_ShouldSucceed() { var ss = new SequenceSet(); for (var i = 0UL; i < (ulong)(4 * NumEntries); i++) ss.Insert(i); ss.Nodes.ShouldBe(4); ss.Size.ShouldBe(4 * NumEntries); var (lh, rh) = ss.Heights(); lh.ShouldBe(1); rh.ShouldBe(2); } [Fact] public void SeqSetCorrectness_ShouldSucceed() { var num = 100_000; var maxVal = 500_000; var rng = new Random(42); var reference = new HashSet(num); var ss = new SequenceSet(); for (var i = 0; i < num; i++) { var n = (ulong)rng.NextInt64(maxVal + 1); ss.Insert(n); reference.Add(n); } for (var i = 0UL; i <= (ulong)maxVal; i++) ss.Exists(i).ShouldBe(reference.Contains(i)); } [Fact] public void SeqSetRange_ShouldSucceed() { var num = 2 * NumEntries + 22; var nums = new List(num); for (var i = 0; i < num; i++) nums.Add((ulong)i); var rng = new Random(42); for (var i = nums.Count - 1; i > 0; i--) { var j = rng.Next(i + 1); (nums[i], nums[j]) = (nums[j], nums[i]); } var ss = new SequenceSet(); foreach (var n in nums) ss.Insert(n); var collected = new List(); ss.Range(n => { collected.Add(n); return true; }); collected.Count.ShouldBe(num); for (var i = 0; i < num; i++) collected[i].ShouldBe((ulong)i); // Test early termination. collected.Clear(); ss.Range(n => { if (n >= 10) return false; collected.Add(n); return true; }); collected.Count.ShouldBe(10); for (var i = 0UL; i < 10; i++) collected[(int)i].ShouldBe(i); } [Fact] public void SeqSetDelete_ShouldSucceed() { var ss = new SequenceSet(); var seqs = new ulong[] { 22, 222, 2222, 2, 2, 4 }; foreach (var seq in seqs) ss.Insert(seq); foreach (var seq in seqs) { ss.Delete(seq); ss.Exists(seq).ShouldBeFalse(); } ss.Root.ShouldBeNull(); } [Fact] public void SeqSetInsertAndDeletePedantic_ShouldSucceed() { var ss = new SequenceSet(); var num = 50 * NumEntries + 22; var nums = new List(num); for (var i = 0; i < num; i++) nums.Add((ulong)i); var rng = new Random(42); for (var i = nums.Count - 1; i > 0; i--) { var j = rng.Next(i + 1); (nums[i], nums[j]) = (nums[j], nums[i]); } void AssertBalanced() { SequenceSet.Node.NodeIter(ss.Root, n => { if (n != null && n.Height != (SequenceSet.Node.BalanceFactor(n) == int.MinValue ? 0 : 0)) { // Height check: verify height equals max child height + 1 var expectedH = 1 + Math.Max(n.Left?.Height ?? 0, n.Right?.Height ?? 0); n.Height.ShouldBe(expectedH); } }); var bf = SequenceSet.Node.BalanceFactor(ss.Root); (bf is >= -1 and <= 1).ShouldBeTrue(); } foreach (var n in nums) { ss.Insert(n); AssertBalanced(); } ss.Root.ShouldNotBeNull(); foreach (var n in nums) { ss.Delete(n); AssertBalanced(); ss.Exists(n).ShouldBeFalse(); if (ss.Size > 0) ss.Root.ShouldNotBeNull(); } ss.Root.ShouldBeNull(); } [Fact] public void SeqSetMinMax_ShouldSucceed() { var ss = new SequenceSet(); var seqs = new ulong[] { 22, 222, 2222, 2, 2, 4 }; foreach (var seq in seqs) ss.Insert(seq); var (min, max) = ss.MinMax(); min.ShouldBe(2UL); max.ShouldBe(2222UL); ss.Empty(); var num = 22 * NumEntries + 22; var nums = new List(num); for (var i = 0; i < num; i++) nums.Add((ulong)i); var rng = new Random(42); for (var i = nums.Count - 1; i > 0; i--) { var j = rng.Next(i + 1); (nums[i], nums[j]) = (nums[j], nums[i]); } foreach (var n in nums) ss.Insert(n); (min, max) = ss.MinMax(); min.ShouldBe(0UL); max.ShouldBe((ulong)(num - 1)); } [Fact] public void SeqSetClone_ShouldSucceed() { var num = 100_000; var maxVal = 500_000; var rng = new Random(42); var ss = new SequenceSet(); for (var i = 0; i < num; i++) ss.Insert((ulong)rng.NextInt64(maxVal + 1)); var ssc = ss.Clone(); ssc.Size.ShouldBe(ss.Size); ssc.Nodes.ShouldBe(ss.Nodes); } [Fact] public void SeqSetUnion_ShouldSucceed() { var ss1 = new SequenceSet(); var seqs1 = new ulong[] { 22, 222, 2222, 2, 2, 4 }; foreach (var seq in seqs1) ss1.Insert(seq); var ss2 = new SequenceSet(); var seqs2 = new ulong[] { 33, 333, 3333, 3, 33_333, 333_333 }; foreach (var seq in seqs2) ss2.Insert(seq); var ss = SequenceSet.UnionSets(ss1, ss2); ss.ShouldNotBeNull(); ss!.Size.ShouldBe(11); foreach (var n in seqs1) ss.Exists(n).ShouldBeTrue(); foreach (var n in seqs2) ss.Exists(n).ShouldBeTrue(); } [Fact] public void SeqSetFirst_ShouldSucceed() { var seqs = new ulong[] { 22, 222, 2222, 222_222 }; foreach (var seq in seqs) { var ss = new SequenceSet(); ss.Insert(seq); ss.Root.ShouldNotBeNull(); ss.Root!.Base.ShouldBe((seq / (ulong)NumEntries) * (ulong)NumEntries); ss.Empty(); ss.SetInitialMin(seq); ss.Insert(seq); ss.Root.ShouldNotBeNull(); ss.Root!.Base.ShouldBe(seq); } } [Fact] public void SeqSetDistinctUnion_ShouldSucceed() { var ss1 = new SequenceSet(); var seqs1 = new ulong[] { 1, 10, 100, 200 }; foreach (var seq in seqs1) ss1.Insert(seq); var ss2 = new SequenceSet(); var seqs2 = new ulong[] { 5000, 6100, 6200, 6222 }; foreach (var seq in seqs2) ss2.Insert(seq); var ss = ss1.Clone(); ss.Union(ss2); var allSeqs = new List(seqs1); allSeqs.AddRange(seqs2); ss.Size.ShouldBe(allSeqs.Count); foreach (var seq in allSeqs) ss.Exists(seq).ShouldBeTrue(); } [Fact] public void SeqSetDecodeV1_ShouldSucceed() { var seqs = new ulong[] { 22, 222, 2222, 222_222, 2_222_222 }; var encStr = @"FgEDAAAABQAAAABgAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAADgIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAA=="; var enc = Convert.FromBase64String(encStr); var (ss, _) = SequenceSet.Decode(enc); ss.Size.ShouldBe(seqs.Length); foreach (var seq in seqs) ss.Exists(seq).ShouldBeTrue(); } // --- Encode/Decode round-trip --- [Fact] public void SeqSetEncodeDecode_RoundTrip_ShouldSucceed() { var num = 2_500_000; var maxVal = 5_000_000; var rng = new Random(42); var reference = new HashSet(num); var ss = new SequenceSet(); for (var i = 0; i < num; i++) { var n = (ulong)rng.NextInt64(maxVal + 1); ss.Insert(n); reference.Add(n); } var buf = ss.Encode(null); var (ss2, _) = SequenceSet.Decode(buf); ss2.Nodes.ShouldBe(ss.Nodes); ss2.Size.ShouldBe(ss.Size); } // --- Performance / scale tests (no strict timing assertions) --- [Fact] public void NoRaceSeqSetSizeComparison_ShouldSucceed() { // Insert 5M items; verify correctness (memory comparison is GC-managed, skip strict thresholds) var num = 5_000_000; var maxVal = 7_000_000; var rng = new Random(42); var ss = new SequenceSet(); var reference = new HashSet(num); for (var i = 0; i < num; i++) { var n = (ulong)rng.NextInt64(maxVal + 1); ss.Insert(n); reference.Add(n); } ss.Size.ShouldBe(reference.Count); } [Fact] public void NoRaceSeqSetEncodeLarge_ShouldSucceed() { var num = 2_500_000; var maxVal = 5_000_000; var rng = new Random(42); var ss = new SequenceSet(); for (var i = 0; i < num; i++) ss.Insert((ulong)rng.NextInt64(maxVal + 1)); var buf = ss.Encode(null); var (ss2, _) = SequenceSet.Decode(buf); ss2.Nodes.ShouldBe(ss.Nodes); ss2.Size.ShouldBe(ss.Size); } [Fact] public void NoRaceSeqSetRelativeSpeed_ShouldSucceed() { // Correctness: all inserted items must be findable. // Timing assertions are omitted — performance is validated via BenchmarkDotNet benchmarks. var num = 1_000_000; var maxVal = 3_000_000; var rng = new Random(42); var seqs = new ulong[num]; for (var i = 0; i < num; i++) seqs[i] = (ulong)rng.NextInt64(maxVal + 1); var ss = new SequenceSet(); foreach (var n in seqs) ss.Insert(n); foreach (var n in seqs) ss.Exists(n).ShouldBeTrue(); } }