task 2.5-2.6: string Read/Write with encoding tests
Add 8 tests for string IO: ASCII/UTF8/Unicode round-trip, null-terminator stop, max-length truncation, auto-append of null terminator, empty string. Fix ReadString null-terminator detection for multi-byte encodings (UTF-16): decode string first, then find \0 in characters not bytes. All passing (total: 38).
This commit is contained in:
@@ -141,12 +141,13 @@ public abstract class MemoryBase : IDisposable
|
||||
public virtual string ReadString(IntPtr address, Encoding encoding, int maxLength = 512, bool relative = false)
|
||||
{
|
||||
byte[] buffer = ReadBytes(address, maxLength, relative);
|
||||
int nullIndex = Array.IndexOf<byte>(buffer, 0);
|
||||
string decoded = encoding.GetString(buffer);
|
||||
int nullIndex = decoded.IndexOf('\0');
|
||||
if (nullIndex >= 0)
|
||||
{
|
||||
return encoding.GetString(buffer, 0, nullIndex);
|
||||
return decoded[..nullIndex];
|
||||
}
|
||||
return encoding.GetString(buffer);
|
||||
return decoded;
|
||||
}
|
||||
|
||||
/// <summary>Writes a null-terminated string to the target address.</summary>
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using WhiteMagic;
|
||||
using WhiteMagic.Native;
|
||||
|
||||
namespace WhiteMagicTest;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="MemoryBase.ReadString"/> and <see cref="MemoryBase.WriteString"/>
|
||||
/// with encoding, null-terminator stop, and max-length behavior.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user