using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using WhiteMagic;
namespace WhiteMagicTest;
///
/// Tests for : blittable size, marshal-required flag,
/// the separate MarshalSize field, and computed-once behavior.
///
public class MarshalCacheTests
{
[Fact]
public void Size_for_int_is_4()
{
Assert.Equal(4, MarshalCache.Size);
}
[Fact]
public void Size_for_byte_is_1()
{
Assert.Equal(1, MarshalCache.Size);
}
[Fact]
public void Size_for_IntPtr_matches_native_pointer_size()
{
Assert.Equal(IntPtr.Size, MarshalCache.Size);
}
[Fact]
public void Size_for_bool_is_1()
{
Assert.Equal(1, MarshalCache.Size);
}
[Fact]
public void Size_for_enum_matches_underlying_type()
{
Assert.Equal(4, MarshalCache.Size);
}
[Fact]
public void Size_for_blittable_struct_is_accurate()
{
Assert.Equal(8, MarshalCache.Size);
}
[Fact]
public void Size_for_struct_with_bool_is_managed_layout_width()
{
// Regression for the Marshal vs. Unsafe size disagreement on a struct
// whose only field is `bool`: Marshal reports 4 (Win32 BOOL default
// marshaling), but the blittable path (MemoryMarshal.Read) actually
// lays out a bool as 1 byte. MarshalCache.Size must match managed width.
Assert.Equal(Unsafe.SizeOf(), MarshalCache.Size);
Assert.Equal(1, MarshalCache.Size);
}
[Fact]
public void Size_for_struct_with_bools_in_sequence_matches_managed_layout()
{
// Sequential struct { bool, bool } — managed width is 2, Marshal width is 8
// (two BOOLs). The blittable path uses 1 byte per bool, so Size must equal
// the managed width.
Assert.Equal(Unsafe.SizeOf(), MarshalCache.Size);
Assert.Equal(2, MarshalCache.Size);
}
[Fact]
public void TypeRequiresMarshal_is_false_for_blittable_types()
{
Assert.False(MarshalCache.TypeRequiresMarshal);
Assert.False(MarshalCache.TypeRequiresMarshal);
Assert.False(MarshalCache.TypeRequiresMarshal);
}
[Fact]
public void TypeRequiresMarshal_is_true_for_types_with_MarshalAs_field()
{
Assert.True(MarshalCache.TypeRequiresMarshal);
}
[Fact]
public void TypeRequiresMarshal_is_true_for_reference_containing_types()
{
Assert.True(MarshalCache.TypeRequiresMarshal);
}
[Fact]
public void Inline_struct_with_MarshalAs_has_separate_MarshalSize()
{
// Regression for the marshal-path size bug. A struct with an inline
// ByValTStr field has mismatched managed and unmanaged widths: the managed
// width is just the pointer reference (8 bytes); the marshal unroller
// expands it into a 16-WCHAR inline buffer (32 bytes). MarshalCache.Size
// must match what the blittable path uses; MarshalCache.MarshalSize must
// match what the marshal path uses.
Assert.True(MarshalCache.TypeRequiresMarshal);
int expectedManaged = Unsafe.SizeOf(); // 8 (ptr)
int expectedMarshal = Marshal.SizeOf(); // 32 (16 WCHAR)
Assert.Equal(expectedManaged, MarshalCache.Size);
Assert.Equal(expectedMarshal, MarshalCache.MarshalSize);
Assert.NotEqual(MarshalCache.Size,
MarshalCache.MarshalSize);
}
[Fact]
public void MarshalSize_equals_Size_for_blittable_types()
{
// No interop expansion is needed when the type is blittable; both widths
// coincide.
Assert.Equal(MarshalCache.Size, MarshalCache.MarshalSize);
Assert.Equal(MarshalCache.Size, MarshalCache.MarshalSize);
Assert.Equal(MarshalCache.Size, MarshalCache.MarshalSize);
}
[Fact]
public void Properties_are_computed_once_and_cached()
{
int size1 = MarshalCache.Size;
bool marshal1 = MarshalCache.TypeRequiresMarshal;
int size2 = MarshalCache.Size;
bool marshal2 = MarshalCache.TypeRequiresMarshal;
Assert.Equal(size1, size2);
Assert.Equal(marshal1, marshal2);
}
[StructLayout(LayoutKind.Sequential)]
private struct BlittableStruct
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
private struct MarshalAsStruct
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public byte[] Data;
}
[StructLayout(LayoutKind.Sequential)]
private struct SingleBoolStruct
{
public bool Flag;
}
[StructLayout(LayoutKind.Sequential)]
private struct SequentialBoolStruct
{
public bool A;
public bool B;
}
///
/// A struct whose marshal layout carries an inline character buffer but
/// whose CLR managed layout is just a reference pointer. The canonical way
/// to exercise the marshal-vs-managed width split.
///
[StructLayout(LayoutKind.Sequential)]
public struct InlineStrStruct
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
public string Name;
}
}