using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; using WhiteMagic; using WhiteMagic.Native; namespace WhiteMagicTest; /// /// Tests for and /// with encoding, null-terminator stop, and max-length behavior. /// public class StringReadWriteTests { private static ExternalReader OpenSelf() { return new ExternalReader( Process.GetCurrentProcess(), ProcessAccess.VmRead | ProcessAccess.VmWrite | ProcessAccess.VmOperation | ProcessAccess.QueryInformation); } [Fact] public void WriteString_ascii_then_ReadString_round_trips() { using var reader = OpenSelf(); byte[] slot = new byte[64]; GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned); try { IntPtr addr = pin.AddrOfPinnedObject(); Assert.True(reader.WriteString(addr, "hello", Encoding.ASCII)); string result = reader.ReadString(addr, Encoding.ASCII); Assert.Equal("hello", result); } finally { pin.Free(); } } [Fact] public void WriteString_utf8_then_ReadString_round_trips() { using var reader = OpenSelf(); byte[] slot = new byte[64]; GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned); try { IntPtr addr = pin.AddrOfPinnedObject(); Assert.True(reader.WriteString(addr, "héllo wörld", Encoding.UTF8)); string result = reader.ReadString(addr, Encoding.UTF8); Assert.Equal("héllo wörld", result); } finally { pin.Free(); } } [Fact] public void WriteString_unicode_then_ReadString_round_trips() { using var reader = OpenSelf(); byte[] slot = new byte[128]; GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned); try { IntPtr addr = pin.AddrOfPinnedObject(); Assert.True(reader.WriteString(addr, "Hello\u00A9\u00AE\u20AC", Encoding.Unicode)); string result = reader.ReadString(addr, Encoding.Unicode); Assert.Equal("Hello\u00A9\u00AE\u20AC", result); } finally { pin.Free(); } } [Fact] public void ReadString_stops_at_null_terminator() { using var reader = OpenSelf(); byte[] slot = Encoding.ASCII.GetBytes("hello\0world"); GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned); try { IntPtr addr = pin.AddrOfPinnedObject(); string result = reader.ReadString(addr, Encoding.ASCII, maxLength: 64); Assert.Equal("hello", result); } finally { pin.Free(); } } [Fact] public void ReadString_respects_max_length() { using var reader = OpenSelf(); byte[] slot = Encoding.ASCII.GetBytes("hello world this is a test"); GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned); try { IntPtr addr = pin.AddrOfPinnedObject(); string result = reader.ReadString(addr, Encoding.ASCII, maxLength: 5); Assert.Equal("hello", result); } finally { pin.Free(); } } [Fact] public void WriteString_appends_null_terminator_automatically() { using var reader = OpenSelf(); byte[] slot = new byte[32]; GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned); try { IntPtr addr = pin.AddrOfPinnedObject(); // Write without terminator Assert.True(reader.WriteString(addr, "test", Encoding.ASCII)); // The written bytes should end with \0 byte[] read = reader.ReadBytes(addr, 8); Assert.Equal((byte)'t', read[0]); Assert.Equal((byte)'e', read[1]); Assert.Equal((byte)'s', read[2]); Assert.Equal((byte)'t', read[3]); Assert.Equal(0, read[4]); // null terminator } finally { pin.Free(); } } [Fact] public void ReadString_empty_buffer_returns_empty_string() { using var reader = OpenSelf(); byte[] slot = new byte[1] { 0 }; GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned); try { IntPtr addr = pin.AddrOfPinnedObject(); string result = reader.ReadString(addr, Encoding.ASCII, maxLength: 1); Assert.Equal("", result); } finally { pin.Free(); } } [Fact] public void WriteString_empty_string_writes_only_null() { using var reader = OpenSelf(); byte[] slot = new byte[8]; GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned); try { IntPtr addr = pin.AddrOfPinnedObject(); // Write a marker first reader.WriteBytes(addr, [0xAB, 0xCD, 0xEF, 0x00]); // Now overwrite with empty string Assert.True(reader.WriteString(addr, "", Encoding.ASCII)); byte[] read = reader.ReadBytes(addr, 4); Assert.Equal(0, read[0]); // null } finally { pin.Free(); } } }