using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; using WhiteMagic; using WhiteMagic.Native; namespace WhiteMagicTest; /// /// Tests for abstract contract and /// round-trip (Read<T>/Write<T>, arrays) using the current process as target. /// public class MemoryBaseTests { private static ExternalReader OpenSelf() { return new ExternalReader( Process.GetCurrentProcess(), ProcessAccess.VmRead | ProcessAccess.VmWrite | ProcessAccess.VmOperation | ProcessAccess.QueryInformation); } [Fact] public void ImageBase_is_nonzero_for_self() { using var reader = OpenSelf(); Assert.NotEqual(IntPtr.Zero, reader.ImageBase); } [Fact] public void Read_int_writes_and_reads_back() { using var reader = OpenSelf(); int slot = 0; GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned); try { IntPtr addr = pin.AddrOfPinnedObject(); Assert.True(reader.Write(addr, 0x1BADB002)); Assert.Equal(0x1BADB002, reader.Read(addr)); } finally { pin.Free(); } } [Fact] public void Read_byte_writes_and_reads_back() { using var reader = OpenSelf(); byte slot = 0; GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned); try { IntPtr addr = pin.AddrOfPinnedObject(); Assert.True(reader.Write(addr, (byte)0xAB)); Assert.Equal(0xAB, reader.Read(addr)); } finally { pin.Free(); } } [Fact] public void Read_long_writes_and_reads_back() { using var reader = OpenSelf(); long slot = 0; GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned); try { IntPtr addr = pin.AddrOfPinnedObject(); Assert.True(reader.Write(addr, unchecked((long)0xDEADBEEF_CAFEBABE))); Assert.Equal(unchecked((long)0xDEADBEEF_CAFEBABE), reader.Read(addr)); } finally { pin.Free(); } } [Fact] public void Read_blittable_struct_writes_and_reads_back() { using var reader = OpenSelf(); var slot = new TestStruct { X = 42, Y = 99 }; GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned); try { IntPtr addr = pin.AddrOfPinnedObject(); Assert.True(reader.Write(addr, new TestStruct { X = 100, Y = 200 })); var result = reader.Read(addr); Assert.Equal(100, result.X); Assert.Equal(200, result.Y); } finally { pin.Free(); } } [Fact] public void Read_bytes_writes_and_reads_back() { using var reader = OpenSelf(); byte[] buffer = new byte[16]; GCHandle pin = GCHandle.Alloc(buffer, GCHandleType.Pinned); try { IntPtr addr = pin.AddrOfPinnedObject(); byte[] expected = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]; int written = reader.WriteBytes(addr, expected); Assert.Equal(expected.Length, written); byte[] actual = reader.ReadBytes(addr, expected.Length); Assert.Equal(expected, actual); } finally { pin.Free(); } } [Fact] public void Read_int_array_writes_and_reads_back() { using var reader = OpenSelf(); int[] buffer = new int[4]; GCHandle pin = GCHandle.Alloc(buffer, GCHandleType.Pinned); try { IntPtr addr = pin.AddrOfPinnedObject(); int[] expected = [10, 20, 30, 40]; Assert.True(reader.Write(addr, expected)); int[] actual = reader.Read(addr, 4); Assert.Equal(expected, actual); } finally { pin.Free(); } } [Fact] public void Read_struct_array_writes_and_reads_back() { using var reader = OpenSelf(); var buffer = new TestStruct[4]; GCHandle pin = GCHandle.Alloc(buffer, GCHandleType.Pinned); try { IntPtr addr = pin.AddrOfPinnedObject(); var expected = new[] { new TestStruct { X = 1, Y = 2 }, new TestStruct { X = 3, Y = 4 }, new TestStruct { X = 5, Y = 6 }, new TestStruct { X = 7, Y = 8 }, }; Assert.True(reader.Write(addr, expected)); var actual = reader.Read(addr, 4); Assert.Equal(expected, actual); } finally { pin.Free(); } } [Fact] public void Write_returns_false_for_invalid_address() { using var reader = OpenSelf(); Assert.False(reader.Write(IntPtr.Zero, 42)); } [Fact] public void Dispose_closes_handle() { var reader = OpenSelf(); Assert.False(reader.Handle.IsClosed); reader.Dispose(); Assert.True(reader.Handle.IsClosed); } [Fact] public void Double_dispose_does_not_throw() { var reader = OpenSelf(); reader.Dispose(); reader.Dispose(); } // ── Graceful failure on invalid addresses ─────────────────────────────── [Fact] public void Read_int_on_invalid_address_returns_default() { using var reader = OpenSelf(); Assert.Equal(0, reader.Read(IntPtr.Zero)); } [Fact] public void Read_struct_on_invalid_address_returns_default() { using var reader = OpenSelf(); var result = reader.Read(IntPtr.Zero); Assert.Equal(0, result.X); Assert.Equal(0, result.Y); } [Fact] public void Read_int_array_on_invalid_address_returns_empty() { using var reader = OpenSelf(); Assert.Empty(reader.Read(IntPtr.Zero, 10)); } [Fact] public void Read_bytes_on_invalid_address_returns_empty() { using var reader = OpenSelf(); Assert.Empty(reader.ReadBytes(IntPtr.Zero, 10)); } } /// /// A simple blittable struct for use in tests. /// [StructLayout(LayoutKind.Sequential)] public struct TestStruct : IEquatable { public int X; public int Y; public bool Equals(TestStruct other) => X == other.X && Y == other.Y; public override bool Equals(object? obj) => obj is TestStruct other && Equals(other); public override int GetHashCode() => HashCode.Combine(X, Y); public override string ToString() => $"({X}, {Y})"; }