using XFESystem.Kernel; using Xunit; namespace XFESystem.NativeInterop.Tests; public sealed class NativePointerFrameProbeTests { [Fact] public void RealCounterAndActualConsumedReportBindToPresent() { var probe = new NativePointerFrameProbe(); probe.Start(1_000_000, 1000, 50, new(100, 100, 0)); probe.ObserveConsumed(101, 1100, 1200, 80, 90); probe.CapturePresent(51, 1300, 1400); var dump = probe.Stop(1500, 51, new(101, 101, 0)); Assert.False(probe.IsCapturing); Assert.Contains("|FramesBefore|50|FramesAfter|51", dump); Assert.Contains("|ObservedBefore|100|ObservedAfter|101|ConsumedBefore|100|ConsumedAfter|101", dump); Assert.Contains("FRAME|Seq|51|BeginTicks|1300|EndTicks|1400|InputSeq|101|InputTicks|1100|DispatchTicks|1200|X|80|Y|90\n", dump); Assert.EndsWith("XFE_POINTER_TIMING_END\n", dump); } [Fact] public void AbortedAndNoOpPresentCallsCannotIncreaseCapturedFrames() { var probe = new NativePointerFrameProbe(); probe.Start(1000, 10, 50, new(0, 0, 0)); probe.CapturePresent(50, 12, 13); probe.CapturePresent(50, 14, 15); string dump = probe.Stop(20, 50, new(0, 0, 0)); Assert.DoesNotContain("\nFRAME|", dump); Assert.Contains("|IgnoredDuplicatePresentCalls|2", dump); Assert.Contains("|FramesBefore|50|FramesAfter|50", dump); } [Fact] public void RingOverflowIsExplicitAndCannotHideSlowFrames() { var probe = new NativePointerFrameProbe(); probe.Start(1000, 0, 0, new(0, 0, 0)); for (int index = 1; index <= NativePointerFrameProbe.Capacity + 3; index++) probe.CapturePresent(index, index * 10, index * 10 + 1); var dump = probe.Stop(50000, NativePointerFrameProbe.Capacity + 3, new(0, 0, 0)); Assert.Equal(NativePointerFrameProbe.Capacity, dump.Split('\n').Count(line => line.StartsWith("FRAME|"))); Assert.Contains("|DroppedFrames|3", dump); } [Fact] public void MissingCounterSequenceAndInvalidInputRemainVisible() { var probe = new NativePointerFrameProbe(); probe.Start(1000, 10, 10, new(1, 1, 0)); probe.ObserveConsumed(2, 15, 14, 0, 0); probe.CapturePresent(12, 20, 21); var dump = probe.Stop(30, 12, new(2, 2, 1)); Assert.Contains("|DroppedFrames|1|InvalidInputReports|1", dump); Assert.Contains("|LostBefore|0|LostAfter|1", dump); } [Fact] public void HotPathHasNoManagedAllocationAfterPreparation() { var probe = new NativePointerFrameProbe(); probe.Start(1000, 0, 0, new(0, 0, 0)); // Warm each method before the measured allocation counter is taken. probe.ObserveConsumed(1, 1, 2, 1, 1); probe.CapturePresent(1, 3, 4); long before = GC.GetAllocatedBytesForCurrentThread(); for (int index = 2; index <= 3000; index++) { probe.ObserveConsumed(index, index * 10, index * 10 + 1, index, 1); probe.CapturePresent(index, index * 10 + 2, index * 10 + 3); } Assert.Equal(before, GC.GetAllocatedBytesForCurrentThread()); } [Fact] public void RestartDiscardsOnlyPriorMeasurementNotSourceCounters() { var probe = new NativePointerFrameProbe(); probe.Start(1000, 1, 1, new(1, 1, 0)); probe.CapturePresent(2, 2, 3); _ = probe.Stop(4, 2, new(1, 1, 0)); probe.Start(1000, 10, 20, new(30, 30, 0)); probe.CapturePresent(21, 11, 12); var dump = probe.Stop(15, 21, new(30, 30, 0)); Assert.Contains("|FramesBefore|20|FramesAfter|21", dump); Assert.DoesNotContain("FRAME|Seq|2|", dump); } [Fact] public void NativeDumpUsesBoundedChunksAndSealsEveryCapturedFrame() { var probe = new NativePointerFrameProbe(); probe.Start(1000, 0, 0, new(0, 0, 0)); for (int index = 1; index <= NativePointerFrameProbe.Capacity; index++) probe.CapturePresent(index, index * 10, index * 10 + 1); var chunks = new List(); probe.Stop(50000, NativePointerFrameProbe.Capacity, new(0, 0, 0), chunks.Add); Assert.True(chunks.Count > 1); Assert.All(chunks, chunk => Assert.True(chunk.Length <= 16_000)); string dump = string.Concat(chunks); Assert.Equal(NativePointerFrameProbe.Capacity, dump.Split('\n').Count(line => line.StartsWith("FRAME|"))); Assert.Contains("|FramesPresentedDelta|4096|CapturedFrames|4096|Overflow|0", dump); } }