using System.Diagnostics;
using XFESystem.Kernel;
using Xunit;
namespace XFESystem.Desktop.Platform.Tests;
public sealed class NativePointerCaptureTests
{
private static void Drain() { NativePointerCapture.Prepare(); while (NativePointerCapture.TryRead(out _)) { } }
private static void Observe(int x, int y, bool down, int wheel = 0, long ticks = 0) => NativePointerCapture.Observe(x, y, down, false, false, wheel, ticks);
[Fact]
public void SlowFramePreservesBothMouseMakeAndBreakBeforeTheNextDrain()
{
Drain(); NativePointerCapture.Prepare(); long now = NativePointerCapture.ReadTimestamp();
Observe(300, 200, true, ticks: now); Observe(300, 200, false, ticks: now + Stopwatch.Frequency * 80 / 1000);
Assert.True(NativePointerCapture.TryRead(out var make)); Assert.True(make.LeftButton); Assert.False(make.LostReports);
Assert.True(NativePointerCapture.TryRead(out var released)); Assert.False(released.LeftButton); Assert.True(released.TimestampTicks > make.TimestampTicks);
Assert.Equal((300, 200), (make.X, make.Y)); Assert.False(NativePointerCapture.TryRead(out _));
}
[Fact]
public void MotionWheelAndZeroWheelPacketsRemainOrderedWithoutWheelOverwrite()
{
Drain(); Observe(100, 200, false); Observe(120, 240, false, 3); Observe(122, 245, false, 0); Observe(122, 245, false, -1);
var actual = new List<NativePointerReport>(); while (NativePointerCapture.TryRead(out var report)) actual.Add(report);
Assert.Equal(new[] { 100, 120, 122, 122 }, actual.Select(r => r.X)); Assert.Equal(new[] { 0, 3, 0, -1 }, actual.Select(r => r.Wheel));
Assert.Equal(2, actual.Sum(r => r.Wheel));
}
[Fact]
public void MultipleButtonsAndEachTransitionArePreserved()
{
Drain(); NativePointerCapture.Observe(1, 2, true, true, false, 0, 11);
NativePointerCapture.Observe(1, 2, false, true, true, 0, 12);
Assert.True(NativePointerCapture.TryRead(out var first)); Assert.True(first.LeftButton); Assert.True(first.RightButton); Assert.False(first.MiddleButton);
Assert.True(NativePointerCapture.TryRead(out var second)); Assert.False(second.LeftButton); Assert.True(second.RightButton); Assert.True(second.MiddleButton);
}
[Fact]
public void OverflowDiscardsAmbiguousBatchAndReturnsLossWithoutFabricatingRelease()
{
Drain(); Observe(3, 4, true);
for (int i = 0; i < NativePointerCapture.Capacity; i++) Observe(i, i, true, 1, i);
Observe(5, 6, false);
Assert.True(NativePointerCapture.TryRead(out var loss)); Assert.True(loss.LostReports); Assert.Equal(0, loss.Wheel); Assert.Equal(0, loss.TimestampTicks);
Assert.False(NativePointerCapture.TryRead(out _));
Observe(7, 8, true, 0, 100); Observe(7, 8, false, 0, 101);
Assert.True(NativePointerCapture.TryRead(out var make)); Assert.False(make.LostReports); Assert.True(make.LeftButton);
Assert.True(NativePointerCapture.TryRead(out var released)); Assert.False(released.LostReports); Assert.False(released.LeftButton);
}
[Fact]
public void RingWrapRetainsButtonsAndPacketTimestamps()
{
Drain();
for (int i = 0; i < NativePointerCapture.Capacity * 3; i++)
{
Observe(i, i + 1, (i & 1) == 0, i % 3 - 1, i);
Assert.True(NativePointerCapture.TryRead(out var report)); Assert.Equal(i, report.X); Assert.Equal(i + 1, report.Y);
Assert.Equal((i & 1) == 0, report.LeftButton); Assert.Equal(i, report.TimestampTicks); Assert.False(report.LostReports);
}
}
[Fact]
public void InitializedObserveAndDrainAllocateNoManagedObjects()
{
Drain(); NativePointerCapture.Prepare(); Observe(1, 1, false); NativePointerCapture.TryRead(out _);
long before = GC.GetAllocatedBytesForCurrentThread();
for (int i = 0; i < 1000; i++) { Observe(i, i, (i & 1) == 0); NativePointerCapture.TryRead(out _); }
Assert.Equal(before, GC.GetAllocatedBytesForCurrentThread());
}
[Fact]
public void UnpreparedInterruptObservationAndTimestampDoNotInitializeOrAllocateTheRing()
{
Drain(); var field = typeof(NativePointerCapture).GetField("reports", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)!;
var buffer = field.GetValue(null); field.SetValue(null, null);
long before = GC.GetAllocatedBytesForCurrentThread();
for (int i = 0; i < 1000; i++) { Observe(i, i, true); _ = NativePointerCapture.ReadTimestamp(); NativePointerCapture.TryRead(out _); }
long after = GC.GetAllocatedBytesForCurrentThread(); Assert.Equal(before, after); Assert.Null(field.GetValue(null)); Assert.Equal(0, NativePointerCapture.ReadTimestamp());
field.SetValue(null, buffer);
}
}
using System.Diagnostics;
using XFESystem.Kernel;
using Xunit;
namespace XFESystem.Desktop.Platform.Tests;
public sealed class NativePointerCaptureTests
{
private static void Drain() { NativePointerCapture.Prepare(); while (NativePointerCapture.TryRead(out _)) { } }
private static void Observe(int x, int y, bool down, int wheel = 0, long ticks = 0) => NativePointerCapture.Observe(x, y, down, false, false, wheel, ticks);
[Fact]
public void SlowFramePreservesBothMouseMakeAndBreakBeforeTheNextDrain()
{
Drain(); NativePointerCapture.Prepare(); long now = NativePointerCapture.ReadTimestamp();
Observe(300, 200, true, ticks: now); Observe(300, 200, false, ticks: now + Stopwatch.Frequency * 80 / 1000);
Assert.True(NativePointerCapture.TryRead(out var make)); Assert.True(make.LeftButton); Assert.False(make.LostReports);
Assert.True(NativePointerCapture.TryRead(out var released)); Assert.False(released.LeftButton); Assert.True(released.TimestampTicks > make.TimestampTicks);
Assert.Equal((300, 200), (make.X, make.Y)); Assert.False(NativePointerCapture.TryRead(out _));
}
[Fact]
public void MotionWheelAndZeroWheelPacketsRemainOrderedWithoutWheelOverwrite()
{
Drain(); Observe(100, 200, false); Observe(120, 240, false, 3); Observe(122, 245, false, 0); Observe(122, 245, false, -1);
var actual = new List<NativePointerReport>(); while (NativePointerCapture.TryRead(out var report)) actual.Add(report);
Assert.Equal(new[] { 100, 120, 122, 122 }, actual.Select(r => r.X)); Assert.Equal(new[] { 0, 3, 0, -1 }, actual.Select(r => r.Wheel));
Assert.Equal(2, actual.Sum(r => r.Wheel));
}
[Fact]
public void MultipleButtonsAndEachTransitionArePreserved()
{
Drain(); NativePointerCapture.Observe(1, 2, true, true, false, 0, 11);
NativePointerCapture.Observe(1, 2, false, true, true, 0, 12);
Assert.True(NativePointerCapture.TryRead(out var first)); Assert.True(first.LeftButton); Assert.True(first.RightButton); Assert.False(first.MiddleButton);
Assert.True(NativePointerCapture.TryRead(out var second)); Assert.False(second.LeftButton); Assert.True(second.RightButton); Assert.True(second.MiddleButton);
}
[Fact]
public void OverflowDiscardsAmbiguousBatchAndReturnsLossWithoutFabricatingRelease()
{
Drain(); Observe(3, 4, true);
for (int i = 0; i < NativePointerCapture.Capacity; i++) Observe(i, i, true, 1, i);
Observe(5, 6, false);
Assert.True(NativePointerCapture.TryRead(out var loss)); Assert.True(loss.LostReports); Assert.Equal(0, loss.Wheel); Assert.Equal(0, loss.TimestampTicks);
Assert.False(NativePointerCapture.TryRead(out _));
Observe(7, 8, true, 0, 100); Observe(7, 8, false, 0, 101);
Assert.True(NativePointerCapture.TryRead(out var make)); Assert.False(make.LostReports); Assert.True(make.LeftButton);
Assert.True(NativePointerCapture.TryRead(out var released)); Assert.False(released.LostReports); Assert.False(released.LeftButton);
}
[Fact]
public void RingWrapRetainsButtonsAndPacketTimestamps()
{
Drain();
for (int i = 0; i < NativePointerCapture.Capacity * 3; i++)
{
Observe(i, i + 1, (i & 1) == 0, i % 3 - 1, i);
Assert.True(NativePointerCapture.TryRead(out var report)); Assert.Equal(i, report.X); Assert.Equal(i + 1, report.Y);
Assert.Equal((i & 1) == 0, report.LeftButton); Assert.Equal(i, report.TimestampTicks); Assert.False(report.LostReports);
}
}
[Fact]
public void InitializedObserveAndDrainAllocateNoManagedObjects()
{
Drain(); NativePointerCapture.Prepare(); Observe(1, 1, false); NativePointerCapture.TryRead(out _);
long before = GC.GetAllocatedBytesForCurrentThread();
for (int i = 0; i < 1000; i++) { Observe(i, i, (i & 1) == 0); NativePointerCapture.TryRead(out _); }
Assert.Equal(before, GC.GetAllocatedBytesForCurrentThread());
}
[Fact]
public void UnpreparedInterruptObservationAndTimestampDoNotInitializeOrAllocateTheRing()
{
Drain(); var field = typeof(NativePointerCapture).GetField("reports", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)!;
var buffer = field.GetValue(null); field.SetValue(null, null);
long before = GC.GetAllocatedBytesForCurrentThread();
for (int i = 0; i < 1000; i++) { Observe(i, i, true); _ = NativePointerCapture.ReadTimestamp(); NativePointerCapture.TryRead(out _); }
long after = GC.GetAllocatedBytesForCurrentThread(); Assert.Equal(before, after); Assert.Null(field.GetValue(null)); Assert.Equal(0, NativePointerCapture.ReadTimestamp());
field.SetValue(null, buffer);
}
}