Add ICustomMarshaler tests (#19195)
authorHugh Bellamy <hughbellars@gmail.com>
Sun, 18 Nov 2018 18:31:28 +0000 (18:31 +0000)
committerAaron Robinson <arobins@microsoft.com>
Sun, 18 Nov 2018 18:31:28 +0000 (10:31 -0800)
* Add ICustomMarshaler tests

tests/src/Interop/ICustomMarshaler/ICustomMarshaler.cs [new file with mode: 0644]
tests/src/Interop/ICustomMarshaler/ICustomMarshaler.csproj [new file with mode: 0644]
tests/src/Interop/common/XunitBase.cs [new file with mode: 0644]

diff --git a/tests/src/Interop/ICustomMarshaler/ICustomMarshaler.cs b/tests/src/Interop/ICustomMarshaler/ICustomMarshaler.cs
new file mode 100644 (file)
index 0000000..b07b810
--- /dev/null
@@ -0,0 +1,597 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace System.Runtime.InteropServices.Tests
+{
+    public class ICustomMarshalerTests : XunitBase
+    {
+        // To avoid having to create a native test library to reference in tests that
+        // interact with native libraries, we can use a simple method from the C standard
+        // library. Unfortunately, the C standard library has different names on Windows
+        // vs Unix.
+#if Windows
+        public const string LibcLibrary = "msvcrt.dll";
+#else
+        public const string LibcLibrary = "libc";
+#endif
+
+        [Fact]
+        public void CustomMarshaler_StringType_Success()
+        {
+            int val = 64001;
+            Assert.Equal(val, MarshalerOnStringTypeMethod(val.ToString()));
+        }
+
+        public class StringForwardingCustomMarshaler : ICustomMarshaler
+        {
+            public void CleanUpManagedData(object ManagedObj) { }
+            public void CleanUpNativeData(IntPtr pNativeData) { Marshal.ZeroFreeCoTaskMemAnsi(pNativeData); }
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj) => Marshal.StringToCoTaskMemAnsi((string)ManagedObj);
+            public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+            public static ICustomMarshaler GetInstance(string cookie) => new StringForwardingCustomMarshaler();
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi")]
+        public static extern int MarshalerOnStringTypeMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StringForwardingCustomMarshaler))] string str);
+
+        [Fact]
+        public void CustomMarshaler_ArrayType_Success()
+        {
+            int val = 64001;
+            Assert.Equal(val, MarshalerOnArrayTypeMethod(new string[] { val.ToString() }));
+        }
+
+        public class ArrayForwardingCustomMarshaler : ICustomMarshaler
+        {
+            public void CleanUpManagedData(object ManagedObj) { }
+            public void CleanUpNativeData(IntPtr pNativeData) { Marshal.ZeroFreeCoTaskMemAnsi(pNativeData); }
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj) => Marshal.StringToCoTaskMemAnsi(((string[])ManagedObj)[0]);
+            public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+            public static ICustomMarshaler GetInstance(string cookie) => new ArrayForwardingCustomMarshaler();
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi")]
+        public static extern int MarshalerOnArrayTypeMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalType = "System.Runtime.InteropServices.Tests.ICustomMarshalerTests+ArrayForwardingCustomMarshaler")] string[] str);
+
+        [Fact]
+        public void CustomMarshaler_BoxedValueType_Success()
+        {
+            int val = 64001;
+            Assert.Equal(val * 2, MarshalerOnBoxedValueTypeMethod(val));
+        }
+
+        public class BoxedValueTypeCustomMarshaler : ICustomMarshaler
+        {
+            public void CleanUpManagedData(object ManagedObj) { }
+            public void CleanUpNativeData(IntPtr pNativeData) { Marshal.ZeroFreeCoTaskMemAnsi(pNativeData); }
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj)
+            {
+                int unboxedValueType = (int)ManagedObj * 2;
+                return Marshal.StringToCoTaskMemAnsi(unboxedValueType.ToString());
+            }
+
+            public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+            public static ICustomMarshaler GetInstance(string cookie) => new BoxedValueTypeCustomMarshaler();
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi")]
+        public static extern int MarshalerOnBoxedValueTypeMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(BoxedValueTypeCustomMarshaler))] object i);
+
+        [Fact]
+        public void Parameter_CustomMarshalerProvidedOnClassType_ForwardsCorrectly()
+        {
+            int val = 64001;
+            Assert.Equal((val * 2).ToString(), MarshalerOnClassTypeMethod(new StringContainer { Value = val.ToString() }).Value);
+        }
+
+        public class StringContainer
+        {
+            public string Value { get; set; }
+        }
+
+        public class ClassForwardingCustomMarshaler : ICustomMarshaler
+        {
+            private bool CleanedString { get; set; }
+
+            public void CleanUpManagedData(object ManagedObj) {}
+
+            public void CleanUpNativeData(IntPtr pNativeData)
+            {
+                if (CleanedString)
+                {
+                    return;
+                }
+
+                Marshal.ZeroFreeCoTaskMemAnsi(pNativeData);
+                CleanedString = true;
+            }
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj)
+            {
+                return Marshal.StringToCoTaskMemAnsi(((StringContainer)ManagedObj).Value);
+            }
+
+            public object MarshalNativeToManaged(IntPtr pNativeData)
+            {
+                int doubleValue = pNativeData.ToInt32() * 2;
+                return new StringContainer { Value = doubleValue.ToString() };
+            }
+
+            public static ICustomMarshaler GetInstance(string cookie) => new ClassForwardingCustomMarshaler();
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ClassForwardingCustomMarshaler))]
+        public static extern StringContainer MarshalerOnClassTypeMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ClassForwardingCustomMarshaler))] StringContainer str);
+
+        [Fact]
+        public void Parameter_CustomMarshalerProvided_CallsMethodsInCorrectOrdering()
+        {
+            Assert.Empty(OrderTrackingCustomMarshaler.Events);
+
+            string val1 = "64001";
+            Assert.Equal(val1, OrderTrackingMethod(val1));
+
+            string[] expectedOrderingFirstCall = new string[]
+            {
+                "Called GetInstance",
+                "Called MarshalManagedToNative",
+                "Called MarshalNativeToManaged",
+                "Called CleanUpNativeData"
+            };
+            Assert.Equal(expectedOrderingFirstCall, OrderTrackingCustomMarshaler.Events);
+
+            // GetInstance is only called once.
+            string val2 = "234";
+            Assert.Equal(val2, OrderTrackingMethod(val2));
+            IEnumerable<string> expectedOrderingSecondCall = expectedOrderingFirstCall.Concat(new string[]
+            {
+                "Called MarshalManagedToNative",
+                "Called MarshalNativeToManaged",
+                "Called CleanUpNativeData"
+            });
+            Assert.Equal(expectedOrderingSecondCall, OrderTrackingCustomMarshaler.Events);
+        }
+
+        // This should only be used *once*, as it uses static state.
+        public class OrderTrackingCustomMarshaler : ICustomMarshaler
+        {
+            public static List<string> Events { get; } = new List<string>();
+            public static IntPtr MarshaledNativeData { get; set; }
+
+            public void CleanUpManagedData(object ManagedObj)
+            {
+                Events.Add("Called CleanUpManagedData");
+            }
+
+            public void CleanUpNativeData(IntPtr pNativeData)
+            {
+                Assert.Equal(MarshaledNativeData, pNativeData);
+                Marshal.ZeroFreeCoTaskMemAnsi(pNativeData);
+
+                Events.Add("Called CleanUpNativeData");
+            }
+
+            public int GetNativeDataSize()
+            {
+                Events.Add("Called GetNativeDataSize");
+                return 0;
+            }
+
+            public IntPtr MarshalManagedToNative(object ManagedObj)
+            {
+                Events.Add("Called MarshalManagedToNative");
+                MarshaledNativeData = Marshal.StringToCoTaskMemAnsi((string)ManagedObj);
+                return MarshaledNativeData;
+            }
+
+            public object MarshalNativeToManaged(IntPtr pNativeData)
+            {
+                Events.Add("Called MarshalNativeToManaged");
+                return pNativeData.ToInt32().ToString();
+            }
+
+            public static ICustomMarshaler GetInstance(string cookie)
+            {
+                Assert.Empty(cookie);
+                Events.Add("Called GetInstance");
+                return new OrderTrackingCustomMarshaler();
+            }
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(OrderTrackingCustomMarshaler))]
+        public static extern string OrderTrackingMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(OrderTrackingCustomMarshaler))] string str);
+
+        [Fact]
+        public void CustomMarshaler_BothMarshalTypeRefAndMarshalTypeProvided_PicksMarshalType()
+        {
+            Assert.Equal(2, BothTypeRefAndTypeMethod("64001"));
+        }
+
+        public class OverridingCustomMarshaler : ICustomMarshaler
+        {
+            public void CleanUpManagedData(object ManagedObj) { }
+            public void CleanUpNativeData(IntPtr pNativeData) { Marshal.ZeroFreeCoTaskMemAnsi(pNativeData); }
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj) => Marshal.StringToCoTaskMemAnsi("2");
+            public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+            public static ICustomMarshaler GetInstance(string cookie) => new OverridingCustomMarshaler();
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int BothTypeRefAndTypeMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalType = "System.Runtime.InteropServices.Tests.ICustomMarshalerTests+OverridingCustomMarshaler", MarshalTypeRef = typeof(StringForwardingCustomMarshaler))] string str);
+
+        [Fact]
+        public void Parameter_CookieProvided_PassesCookieToGetInstance()
+        {
+            int val = 64001;
+            Assert.Equal(val, CustomCookieMethod(val.ToString()));
+            Assert.Equal("Cookie", CookieTrackingCustomMarshaler.Cookie);
+        }
+
+        public class CookieTrackingCustomMarshaler : ICustomMarshaler
+        {
+            public static string Cookie { get; set; }
+
+            public void CleanUpManagedData(object ManagedObj) { }
+            public void CleanUpNativeData(IntPtr pNativeData) { Marshal.ZeroFreeCoTaskMemAnsi(pNativeData); }
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj) => Marshal.StringToCoTaskMemAnsi((string)ManagedObj);
+            public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+            public static ICustomMarshaler GetInstance(string cookie)
+            {
+                Cookie = cookie;
+                return new CookieTrackingCustomMarshaler();
+            }
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int CustomCookieMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(CookieTrackingCustomMarshaler), MarshalCookie = "Cookie")] string str);
+
+        [Fact]
+        public void Parameter_NotCustomMarshalerType_UsesSpecifiedMarshaler()
+        {
+            int val = 64001;
+            Assert.Equal(val, NonCustomMarshalerTypeMethod(val.ToString()));
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int NonCustomMarshalerTypeMethod([MarshalAs(UnmanagedType.LPStr, MarshalTypeRef = typeof(OverridingCustomMarshaler))] string str);
+
+        [Fact]
+        public void CustomMarshaler_Generic_Success()
+        {
+            Assert.Equal(234, GenericGetInstanceCustomMarshalerMethod("64001"));
+        }
+
+        public class GenericCustomMarshaler<T> : ICustomMarshaler
+        {
+            public void CleanUpManagedData(object ManagedObj) { }
+            public void CleanUpNativeData(IntPtr pNativeData) { Marshal.ZeroFreeCoTaskMemAnsi(pNativeData); }
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj) => Marshal.StringToCoTaskMemAnsi("234");
+            public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+            public static ICustomMarshaler GetInstance(string cookie)
+            {
+                return new GenericCustomMarshaler<int>();
+            }
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int GenericGetInstanceCustomMarshalerMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(GenericCustomMarshaler<int>))] string str);
+
+        [Fact]
+        public void CustomMarshaler_ValueTypeWithStringType_Success()
+        {
+            Assert.Equal(234, ValueTypeMarshalerOnStringTypeMethod("64001"));
+        }
+
+        public struct CustomMarshalerValueType : ICustomMarshaler
+        {
+            public void CleanUpManagedData(object ManagedObj) { }
+            public void CleanUpNativeData(IntPtr pNativeData) { Marshal.ZeroFreeCoTaskMemAnsi(pNativeData); }
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj) => Marshal.StringToCoTaskMemAnsi("234");
+            public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+            public static ICustomMarshaler GetInstance(string cookie)
+            {
+                return new CustomMarshalerValueType();
+            }
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int ValueTypeMarshalerOnStringTypeMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(CustomMarshalerValueType))] string str);
+
+        [Fact]
+        public void Parameter_MarshalerOnValueType_ThrowsMarshalDirectiveException()
+        {
+            Assert.Throws<MarshalDirectiveException>(() => MarshalerOnValueTypeMethod(0));
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int MarshalerOnValueTypeMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StringForwardingCustomMarshaler))] int str);
+
+        [Fact]
+        public unsafe void Parameter_MarshalerOnPointer_ThrowsMarshalDirectiveException()
+        {
+            Assert.Throws<MarshalDirectiveException>(() => MarshalerOnPointerMethod(null));
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static unsafe extern int MarshalerOnPointerMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StringForwardingCustomMarshaler))] int* str);
+
+        [Fact]
+        public void Parameter_NullICustomMarshaler_ThrowsTypeLoadException()
+        {
+            Assert.Throws<TypeLoadException>(() => NullCustomMarshalerMethod(""));
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int NullCustomMarshalerMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = null)] string str);
+
+        [Fact]
+        public void Parameter_NotICustomMarshaler_ThrowsApplicationException()
+        {
+            Assert.Throws<ApplicationException>(() => NonICustomMarshalerMethod(""));
+        }
+    
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int NonICustomMarshalerMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(string))] string str);
+
+        [Fact]
+        public void Parameter_OpenGenericICustomMarshaler_ThrowsTypeLoadException()
+        {
+            Assert.Throws<TypeLoadException>(() => OpenGenericICustomMarshalerMethod(""));
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int OpenGenericICustomMarshalerMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(GenericCustomMarshaler<>))] string str);
+
+        [Fact]
+        public void Parameter_GetInstanceMethodDoesntExist_ThrowsApplicationException()
+        {
+            Assert.Throws<ApplicationException>(() => NoGetInstanceMethod(""));
+        }
+
+        public class NoGetInstanceCustomMarshaler : ICustomMarshaler
+        {
+            public void CleanUpManagedData(object ManagedObj) { }
+            public void CleanUpNativeData(IntPtr pNativeData) { }
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj) => IntPtr.Zero;
+            public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int NoGetInstanceMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(NoGetInstanceCustomMarshaler))] string str);
+
+        [Fact]
+        public void Parameter_GetInstanceMethodInstanceMethod_ThrowsApplicationException()
+        {
+            Assert.Throws<ApplicationException>(() => InstanceGetInstanceMethod(""));
+        }
+
+        public class InstanceGetInstanceCustomMarshaler : ICustomMarshaler
+        {
+            public void CleanUpManagedData(object ManagedObj) { }
+            public void CleanUpNativeData(IntPtr pNativeData) { }
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj) => IntPtr.Zero;
+            public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+            public ICustomMarshaler GetInstance(string cookie) => new InstanceGetInstanceCustomMarshaler();
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int InstanceGetInstanceMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(InstanceGetInstanceCustomMarshaler))] string str);
+
+        [Fact]
+        public void Parameter_GetInstanceMethodNoParameters_ThrowsApplicationException()
+        {
+            Assert.Throws<ApplicationException>(() => NoParametersGetInstanceMethod(""));
+        }
+
+        public class NoParameterGetInstanceCustomMarshaler : ICustomMarshaler
+        {
+            public void CleanUpManagedData(object ManagedObj) { }
+            public void CleanUpNativeData(IntPtr pNativeData) { }
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj) => IntPtr.Zero;
+            public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+            public static ICustomMarshaler GetInstance() => new NoParameterGetInstanceCustomMarshaler();
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int NoParametersGetInstanceMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(NoParameterGetInstanceCustomMarshaler))] string str);
+
+        [Fact]
+        public void Parameter_GetInstanceMethodNonStringParameter_ThrowsApplicationException()
+        {
+            Assert.Throws<ApplicationException>(() => NonStringGetInstanceMethod(""));
+        }
+
+        public class NonStringGetInstanceCustomMarshaler : ICustomMarshaler
+        {
+            public void CleanUpManagedData(object ManagedObj) { }
+            public void CleanUpNativeData(IntPtr pNativeData) { }
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj) => IntPtr.Zero;
+            public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+            public static ICustomMarshaler GetInstance(int x) => new NonStringGetInstanceCustomMarshaler();
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int NonStringGetInstanceMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(NonStringGetInstanceCustomMarshaler))] string str);
+
+        [Fact]
+        public void Parameter_GetInstanceMethodReturnsVoid_ThrowsApplicationException()
+        {
+            Assert.Throws<ApplicationException>(() => VoidGetInstanceMethod(""));
+        }
+
+        public class VoidGetInstanceCustomMarshaler : ICustomMarshaler
+        {
+            public void CleanUpManagedData(object ManagedObj) { }
+            public void CleanUpNativeData(IntPtr pNativeData) { }
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj) => IntPtr.Zero;
+            public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+            public static void GetInstance(string cookie) { }
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int VoidGetInstanceMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(VoidGetInstanceCustomMarshaler))] string str);
+
+        [Fact]
+        public void Parameter_GetInstanceMethodReturnsNull_ThrowsApplicationException()
+        {
+            Assert.Throws<ApplicationException>(() => NullGetInstanceMethod(""));
+        }
+
+        public class NullGetInstanceCustomMarshaler : ICustomMarshaler
+        {
+            public void CleanUpManagedData(object ManagedObj) { }
+            public void CleanUpNativeData(IntPtr pNativeData) { }
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj) => IntPtr.Zero;
+            public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+            public static ICustomMarshaler GetInstance(string cookie) => null;
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int NullGetInstanceMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(NullGetInstanceCustomMarshaler))] string str);
+
+        [Fact]
+        public void Parameter_GetInstanceMethodThrows_ThrowsActualException()
+        {            
+            Assert.Throws<NotImplementedException>(() => ThrowingGetInstanceMethod(""));
+        }
+
+        public class ThrowingGetInstanceCustomMarshaler : ICustomMarshaler
+        {
+            public void CleanUpManagedData(object ManagedObj) { }
+            public void CleanUpNativeData(IntPtr pNativeData) { }
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj) => IntPtr.Zero;
+            public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+            public static ICustomMarshaler GetInstance(string cookie) => throw new NotImplementedException();
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int ThrowingGetInstanceMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ThrowingGetInstanceCustomMarshaler))] string str);
+
+        [Fact]
+        public void Parameter_MarshalManagedToNativeThrows_ThrowsActualException()
+        {
+            Assert.Throws<NotImplementedException>(() => ThrowingMarshalManagedToNativeMethod(""));
+        }
+
+        public class ThrowingMarshalManagedToNativeCustomMarshaler : ICustomMarshaler
+        {
+            public void CleanUpManagedData(object ManagedObj) { }
+            public void CleanUpNativeData(IntPtr pNativeData) { }
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj) => throw new NotImplementedException();
+            public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+            public static ICustomMarshaler GetInstance(string cookie) => new ThrowingMarshalManagedToNativeCustomMarshaler();
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int ThrowingMarshalManagedToNativeMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ThrowingMarshalManagedToNativeCustomMarshaler))] string str);
+
+        [Fact]
+        public void Parameter_CleanUpNativeDataMethodThrows_ThrowsActualException()
+        {
+            Assert.Throws<NotImplementedException>(() => ThrowingCleanUpNativeDataMethod(""));
+        }
+
+        public class ThrowingCleanUpNativeDataCustomMarshaler : ICustomMarshaler
+        {
+            public void CleanUpManagedData(object ManagedObj) { }
+            public void CleanUpNativeData(IntPtr pNativeData) => throw new NotImplementedException();
+
+            public int GetNativeDataSize() => IntPtr.Size;
+
+            public IntPtr MarshalManagedToNative(object ManagedObj) => Marshal.StringToCoTaskMemAnsi((string)ManagedObj);
+            public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+            public static ICustomMarshaler GetInstance(string cookie) => new ThrowingMarshalManagedToNativeCustomMarshaler();
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int ThrowingCleanUpNativeDataMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ThrowingCleanUpNativeDataCustomMarshaler))] string str);
+
+        [Fact]
+        public static void Field_ParentIsStruct_ThrowsTypeLoadException()
+        {
+            Assert.Throws<TypeLoadException>(() => StructWithCustomMarshalerFieldMethod(new StructWithCustomMarshalerField()));
+        }
+
+        public struct StructWithCustomMarshalerField
+        {
+            [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StringForwardingCustomMarshaler))]
+            public string Field;
+        }
+
+        [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int StructWithCustomMarshalerFieldMethod(StructWithCustomMarshalerField c);
+
+        public static int Main(String[] args)
+        {
+            return new ICustomMarshalerTests().RunTests();
+        }
+    }
+}
diff --git a/tests/src/Interop/ICustomMarshaler/ICustomMarshaler.csproj b/tests/src/Interop/ICustomMarshaler/ICustomMarshaler.csproj
new file mode 100644 (file)
index 0000000..ae3ef0e
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{95DFC527-4DC1-495E-97D7-E94EE1F7140D}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <DefineConstants Condition="'$(TargetsWindows)' == 'true'">$(DefineConstants);Windows</DefineConstants>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' " />
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="ICustomMarshaler.cs" />
+    <Compile Include="..\common\XunitBase.cs" />
+  </ItemGroup>
+  <ItemGroup>
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+  <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+</Project>
diff --git a/tests/src/Interop/common/XunitBase.cs b/tests/src/Interop/common/XunitBase.cs
new file mode 100644 (file)
index 0000000..06341e7
--- /dev/null
@@ -0,0 +1,77 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading;
+using Xunit.Runners;
+
+namespace Xunit
+{
+    public abstract class XunitBase
+    {
+        private static object consoleLock = new object();
+
+        private static ManualResetEvent finished = new ManualResetEvent(false);
+        
+        private static int result = 100;
+
+        public int RunTests()
+        {
+            var runner = AssemblyRunner.WithoutAppDomain(GetType().Assembly.Location);
+            runner.OnDiscoveryComplete = OnDiscoveryComplete;
+            runner.OnExecutionComplete = OnExecutionComplete;
+            runner.OnTestFailed = OnTestFailed;
+            runner.OnTestSkipped = OnTestSkipped;
+
+            Console.WriteLine("Discovering...");
+
+            runner.Start();
+
+            finished.WaitOne();
+            finished.Dispose();
+
+            return result;
+        }
+
+        private static void OnDiscoveryComplete(DiscoveryCompleteInfo info)
+        {
+            lock (consoleLock)
+                Console.WriteLine($"Running {info.TestCasesToRun} of {info.TestCasesDiscovered} tests...");
+        }
+
+        private static void OnExecutionComplete(ExecutionCompleteInfo info)
+        {
+            lock (consoleLock)
+                Console.WriteLine($"Finished: {info.TotalTests} tests in {Math.Round(info.ExecutionTime, 3)}s ({info.TestsFailed} failed, {info.TestsSkipped} skipped)");
+
+            finished.Set();
+        }
+
+        private static void OnTestFailed(TestFailedInfo info)
+        {
+            lock (consoleLock)
+            {
+                Console.ForegroundColor = ConsoleColor.Red;
+
+                Console.WriteLine("[FAIL] {0}: {1}", info.TestDisplayName, info.ExceptionMessage);
+                if (info.ExceptionStackTrace != null)
+                    Console.WriteLine(info.ExceptionStackTrace);
+
+                Console.ResetColor();
+            }
+
+            result = 101;
+        }
+
+        private static void OnTestSkipped(TestSkippedInfo info)
+        {
+            lock (consoleLock)
+            {
+                Console.ForegroundColor = ConsoleColor.Yellow;
+                Console.WriteLine("[SKIP] {0}: {1}", info.TestDisplayName, info.SkipReason);
+                Console.ResetColor();
+            }
+        }
+    }
+}