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; } }