using System.Collections.Concurrent; using ZB.MOM.WW.LocalDb.Hlc; namespace ZB.MOM.WW.LocalDb.Tests; public sealed class HybridLogicalClockTests { [Fact] public void Next_IsStrictlyMonotonic() { var clock = new HybridLogicalClock(); var prev = clock.Next(); for (var i = 0; i < 1000; i++) { var next = clock.Next(); Assert.True(next > prev, $"call {i}: {next} !> {prev}"); prev = next; } } [Fact] public void Next_UsesUtcPhysicalMs() { const long fixedMs = 1_700_000_000_123; var clock = new HybridLogicalClock(utcNowMs: () => fixedMs); Assert.Equal(fixedMs, HybridLogicalClock.PhysicalMs(clock.Next())); } [Fact] public void Next_SameMs_IncrementsCounter() { const long fixedMs = 1_700_000_000_000; var clock = new HybridLogicalClock(utcNowMs: () => fixedMs); var first = clock.Next(); var second = clock.Next(); var third = clock.Next(); Assert.Equal(0, HybridLogicalClock.Counter(first)); Assert.Equal(1, HybridLogicalClock.Counter(second)); Assert.Equal(2, HybridLogicalClock.Counter(third)); } [Fact] public void Observe_RemoteAhead_AdvancesPastRemote() { // Frozen wall clock well behind the remote stamp. var clock = new HybridLogicalClock(utcNowMs: () => 1_000); var remote = 5_000_000_000L << 16; clock.Observe(remote); Assert.True(clock.Next() > remote); } [Fact] public void Observe_RemoteBehind_NoRegression() { var clock = new HybridLogicalClock(utcNowMs: () => 1_700_000_000_000); var current = clock.Next(); clock.Observe(current - 1_000_000); // a stamp from the past Assert.Equal(current, clock.Current); } [Fact] public void Resume_FromPersistedValue_NeverRegresses() { // Persisted value is far ahead of what the (injected) wall clock yields. var persisted = (2_000_000_000_000L << 16) | 0x00FF; var clock = new HybridLogicalClock(initialValue: persisted, utcNowMs: () => 1_000); Assert.True(clock.Next() > persisted); } [Fact] public void CounterOverflow_RollsPhysicalForward() { const long ms = 1_700_000_000_000; // Counter already maxed out under a frozen clock at the same physical ms. var maxed = (ms << 16) | 0xFFFF; var clock = new HybridLogicalClock(initialValue: maxed, utcNowMs: () => ms); var next = clock.Next(); Assert.Equal(ms + 1, HybridLogicalClock.PhysicalMs(next)); Assert.Equal(0, HybridLogicalClock.Counter(next)); } [Fact] public void Next_ConcurrentCallers_NoDuplicates_StrictlyIncreasing() { var clock = new HybridLogicalClock(); var results = new ConcurrentBag(); Parallel.For(0, 8, _ => { for (var i = 0; i < 1000; i++) results.Add(clock.Next()); }); Assert.Equal(8000, results.Count); Assert.Equal(8000, results.Distinct().Count()); } }