Files
whitemagic/WhiteMagicTest/MemoryBaseTests.cs
T
2026-07-21 22:30:10 +02:00

246 lines
6.7 KiB
C#

using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using WhiteMagic;
using WhiteMagic.Native;
namespace WhiteMagicTest;
/// <summary>
/// Tests for <see cref="MemoryBase"/> abstract contract and <see cref="ExternalReader"/>
/// round-trip (Read&lt;T&gt;/Write&lt;T&gt;, arrays) using the current process as target.
/// </summary>
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<int>(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<byte>(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<long>(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<TestStruct>(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<int>(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<TestStruct>(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<int>(IntPtr.Zero));
}
[Fact]
public void Read_struct_on_invalid_address_returns_default()
{
using var reader = OpenSelf();
var result = reader.Read<TestStruct>(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<int>(IntPtr.Zero, 10));
}
[Fact]
public void Read_bytes_on_invalid_address_returns_empty()
{
using var reader = OpenSelf();
Assert.Empty(reader.ReadBytes(IntPtr.Zero, 10));
}
}
/// <summary>
/// A simple blittable struct for use in tests.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct TestStruct : IEquatable<TestStruct>
{
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})";
}