Add IJW native varargs tests. (#24983)
authorJeremy Koritzinsky <jekoritz@microsoft.com>
Tue, 18 Jun 2019 00:17:47 +0000 (17:17 -0700)
committerGitHub <noreply@github.com>
Tue, 18 Jun 2019 00:17:47 +0000 (17:17 -0700)
* Refactor IJW test infra. Add some tests for some  interesting simple native varargs.

* Add missing include of IjwHelper.cs

* Fix Cmake test project name.

* PR Feedback.

* Fix compilation error.

* Deploy msvcp*d.dll as part of the copy-local'd CRT.

19 files changed:
tests/src/Interop/CMakeLists.txt
tests/src/Interop/IJW/CopyConstructorMarshaler/CMakeLists.txt
tests/src/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler.cs
tests/src/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler.csproj
tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.cs
tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.csproj
tests/src/Interop/IJW/IJW.cmake [new file with mode: 0644]
tests/src/Interop/IJW/IjwHelper.cs [new file with mode: 0644]
tests/src/Interop/IJW/IjwNativeCallingManagedDll/CMakeLists.txt
tests/src/Interop/IJW/IjwNativeDll/CMakeLists.txt
tests/src/Interop/IJW/ManagedCallingNative/ManagedCallingNative.cs
tests/src/Interop/IJW/ManagedCallingNative/ManagedCallingNative.csproj
tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.cs
tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.csproj
tests/src/Interop/IJW/NativeVarargs/CMakeLists.txt [new file with mode: 0644]
tests/src/Interop/IJW/NativeVarargs/IjwNativeVarargs.cpp [new file with mode: 0644]
tests/src/Interop/IJW/NativeVarargs/NativeVarargsTest.cs [new file with mode: 0644]
tests/src/Interop/IJW/NativeVarargs/NativeVarargsTest.csproj [new file with mode: 0644]
tests/src/Interop/Interop.settings.targets

index a203a9b..117ba79 100644 (file)
@@ -92,5 +92,6 @@ if(WIN32)
         add_subdirectory(IJW/IjwNativeDll)
         add_subdirectory(IJW/IjwNativeCallingManagedDll)
         add_subdirectory(IJW/CopyConstructorMarshaler)
+        add_subdirectory(IJW/NativeVarargs)
     endif()
 endif(WIN32)
index 57726a4..807f3a9 100644 (file)
@@ -1,39 +1,10 @@
 cmake_minimum_required (VERSION 2.6)
 project (CopyConstructorMarshaler)
+include("../IJW.cmake")
+
 include_directories( ${INC_PLATFORM_DIR} )
 set(SOURCES IjwCopyConstructorMarshaler.cpp)
 
-if (WIN32)
-  # 4365 - signed/unsigned mismatch
-  add_compile_options(/wd4365)
-
-  # IJW
-  add_compile_options(/clr)
-  
-  # IJW requires the CRT as a dll, not linked in
-  add_compile_options(/MD$<$<OR:$<CONFIG:Debug>,$<CONFIG:Checked>>:d>)
-
-  # CMake enables /RTC1 and /EHsc by default, but they're not compatible with /clr, so remove them
-  if(CMAKE_CXX_FLAGS_DEBUG MATCHES "/RTC1")
-    string(REPLACE "/RTC1" " " CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
-  endif()
-  
-  if(CMAKE_CXX_FLAGS MATCHES "/EHsc")
-    string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
-  endif()
-
-  # IJW isn't compatible with CFG
-   if(CMAKE_CXX_FLAGS MATCHES "/guard:cf")
-    string(REPLACE "/guard:cf" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
-   endif()
-
-     # IJW isn't compatible with GR-
-   if(CMAKE_CXX_FLAGS MATCHES "/GR-")
-    string(REPLACE "/GR-" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
-   endif()
-
-endif()
-
 # add the shared library
 add_library (IjwCopyConstructorMarshaler SHARED ${SOURCES})
 target_link_libraries(IjwCopyConstructorMarshaler ${LINK_LIBRARIES_ADDITIONAL})
index d589cd4..c85224a 100644 (file)
@@ -21,10 +21,7 @@ namespace CopyConstructorMarshaler
 
             try
             {
-                // Load a fake mscoree.dll to avoid starting desktop
-                LoadLibraryEx(Path.Combine(Environment.CurrentDirectory, "mscoree.dll"), IntPtr.Zero, 0);
-
-                Assembly ijwNativeDll = Assembly.Load("IjwCopyConstructorMarshaler");
+                Assembly ijwNativeDll = IjwHelper.LoadIjwAssembly("IjwCopyConstructorMarshaler");
                 Type testType = ijwNativeDll.GetType("TestClass");
                 object testInstance = Activator.CreateInstance(testType);
                 MethodInfo testMethod = testType.GetMethod("PInvokeNumCopies");
index e1bf4bf..f52a8dc 100644 (file)
@@ -38,6 +38,7 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="CopyConstructorMarshaler.cs" />
+    <Compile Include="../IjwHelper.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="CMakeLists.txt" />
index cfc5a9a..3d310d4 100644 (file)
@@ -23,7 +23,7 @@ namespace FixupCallsHostWhenLoaded
             try
             {
                 // Load a fake mscoree.dll to avoid starting desktop
-                IntPtr ijwHost = NativeLibrary.Load(Path.Combine(Environment.CurrentDirectory, "mscoree.dll"));
+                IntPtr ijwHost = NativeLibrary.Load(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mscoree.dll"));
 
                 WasModuleVTableQueriedDelegate wasModuleVTableQueried = Marshal.GetDelegateForFunctionPointer<WasModuleVTableQueriedDelegate>(NativeLibrary.GetExport(ijwHost, "WasModuleVTableQueried"));
 
index a442a4a..6964d1d 100644 (file)
@@ -38,6 +38,7 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="FixupCallsHostWhenLoaded.cs" />
+    <Compile Include="../IjwHelper.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="../IjwNativeDll/CMakeLists.txt" />
diff --git a/tests/src/Interop/IJW/IJW.cmake b/tests/src/Interop/IJW/IJW.cmake
new file mode 100644 (file)
index 0000000..3bb61d0
--- /dev/null
@@ -0,0 +1,30 @@
+if (WIN32)
+  # 4365 - signed/unsigned mismatch
+  add_compile_options(/wd4365)
+
+  # IJW
+  add_compile_options(/clr)
+
+  # IJW requires the CRT as a dll, not linked in
+  add_compile_options(/MD$<$<OR:$<CONFIG:Debug>,$<CONFIG:Checked>>:d>)
+
+  # CMake enables /RTC1 and /EHsc by default, but they're not compatible with /clr, so remove them
+  if(CMAKE_CXX_FLAGS_DEBUG MATCHES "/RTC1")
+    string(REPLACE "/RTC1" " " CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
+  endif()
+
+  if(CMAKE_CXX_FLAGS MATCHES "/EHsc")
+    string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+  endif()
+
+  # IJW isn't compatible with CFG
+  if(CMAKE_CXX_FLAGS MATCHES "/guard:cf")
+    string(REPLACE "/guard:cf" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+  endif()
+
+  # IJW isn't compatible with GR-
+  if(CMAKE_CXX_FLAGS MATCHES "/GR-")
+    string(REPLACE "/GR-" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+  endif()
+
+endif()
diff --git a/tests/src/Interop/IJW/IjwHelper.cs b/tests/src/Interop/IJW/IjwHelper.cs
new file mode 100644 (file)
index 0000000..ad0b4dd
--- /dev/null
@@ -0,0 +1,22 @@
+// 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.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using TestLibrary;
+
+class IjwHelper
+{
+    private const string ijwHostName = "mscoree.dll";
+
+    public static Assembly LoadIjwAssembly(string name)
+    {
+        // Load our mock ijwhost before we load the IJW assembly.
+        NativeLibrary.Load(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), ijwHostName));
+
+        return Assembly.Load(name);
+    }
+}
index c8b0edc..cc62aa1 100644 (file)
@@ -1,39 +1,10 @@
 cmake_minimum_required (VERSION 2.6)
 project (IjwNativeCallingManagedDll)
+include("../IJW.cmake")
+
 include_directories( ${INC_PLATFORM_DIR} )
 set(SOURCES IjwNativeCallingManagedDll.cpp)
 
-if (WIN32)
-  # 4365 - signed/unsigned mismatch
-  add_compile_options(/wd4365)
-
-  # IJW
-  add_compile_options(/clr)
-  
-  # IJW requires the CRT as a dll, not linked in
-  add_compile_options(/MD$<$<OR:$<CONFIG:Debug>,$<CONFIG:Checked>>:d>)
-
-  # CMake enables /RTC1 and /EHsc by default, but they're not compatible with /clr, so remove them
-  if(CMAKE_CXX_FLAGS_DEBUG MATCHES "/RTC1")
-    string(REPLACE "/RTC1" " " CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
-  endif()
-  
-  if(CMAKE_CXX_FLAGS MATCHES "/EHsc")
-    string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
-  endif()
-
-  # IJW isn't compatible with CFG
-   if(CMAKE_CXX_FLAGS MATCHES "/guard:cf")
-    string(REPLACE "/guard:cf" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
-   endif()
-
-     # IJW isn't compatible with GR-
-   if(CMAKE_CXX_FLAGS MATCHES "/GR-")
-    string(REPLACE "/GR-" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
-   endif()
-
-endif()
-
 # add the shared library
 add_library (IjwNativeCallingManagedDll SHARED ${SOURCES})
 target_link_libraries(IjwNativeCallingManagedDll ${LINK_LIBRARIES_ADDITIONAL})
index 612e6aa..ef72059 100644 (file)
@@ -1,39 +1,10 @@
 cmake_minimum_required (VERSION 2.6)
 project (IjwNativeDll)
+include("../IJW.cmake")
+
 include_directories( ${INC_PLATFORM_DIR} )
 set(SOURCES IjwNativeDll.cpp)
 
-if (WIN32)
-  # 4365 - signed/unsigned mismatch
-  add_compile_options(/wd4365)
-
-  # IJW
-  add_compile_options(/clr)
-  
-  # IJW requires the CRT as a dll, not linked in
-  add_compile_options(/MD$<$<OR:$<CONFIG:Debug>,$<CONFIG:Checked>>:d>)
-
-  # CMake enables /RTC1 and /EHsc by default, but they're not compatible with /clr, so remove them
-  if(CMAKE_CXX_FLAGS_DEBUG MATCHES "/RTC1")
-    string(REPLACE "/RTC1" " " CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
-  endif()
-  
-  if(CMAKE_CXX_FLAGS MATCHES "/EHsc")
-    string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
-  endif()
-
-  # IJW isn't compatible with CFG
-   if(CMAKE_CXX_FLAGS MATCHES "/guard:cf")
-    string(REPLACE "/guard:cf" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
-   endif()
-
-     # IJW isn't compatible with GR-
-   if(CMAKE_CXX_FLAGS MATCHES "/GR-")
-    string(REPLACE "/GR-" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
-   endif()
-
-endif()
-
 # add the shared library
 add_library (IjwNativeDll SHARED ${SOURCES})
 target_link_libraries(IjwNativeDll ${LINK_LIBRARIES_ADDITIONAL})
index 4e18469..c1c0cd6 100644 (file)
@@ -21,15 +21,7 @@ namespace ManagedCallingNative
             }
 
             bool success = true;
-            // Load a fake mscoree.dll to avoid starting desktop
-            LoadLibraryEx(Path.Combine(Environment.CurrentDirectory, "mscoree.dll"), IntPtr.Zero, 0);
-
-            TestFramework.BeginScenario("Calling from managed to native IJW code");
-
-            // Building with a reference to the IJW dll is difficult, so load via reflection instead
-            TestFramework.BeginTestCase("Load IJW dll via reflection");
-            Assembly ijwNativeDll = Assembly.Load("IjwNativeDll");
-            TestFramework.EndTestCase();
+            Assembly ijwNativeDll = IjwHelper.LoadIjwAssembly("IjwNativeDll");
 
             TestFramework.BeginTestCase("Call native method returning int");
             Type testType = ijwNativeDll.GetType("TestClass");
@@ -67,9 +59,6 @@ namespace ManagedCallingNative
         }
 
         [DllImport("kernel32.dll")]
-        static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, int dwFlags);
-
-        [DllImport("kernel32.dll")]
         static extern IntPtr GetModuleHandle(string lpModuleName);
     }
 }
index c0dcb87..6b4d43e 100644 (file)
@@ -37,6 +37,7 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="ManagedCallingNative.cs" />
+    <Compile Include="../IjwHelper.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="../IjwNativeDll/CMakeLists.txt" />
index ec557a9..189ac17 100644 (file)
@@ -21,15 +21,7 @@ namespace NativeCallingManaged
             }
 
             bool success = true;
-            // Load a fake mscoree.dll to avoid starting desktop
-            LoadLibraryEx(Path.Combine(Environment.CurrentDirectory, "mscoree.dll"), IntPtr.Zero, 0);
-
-            TestFramework.BeginScenario("Calling from managed to native IJW code");
-
-            // Building with a reference to the IJW dll is difficult, so load via reflection instead
-            TestFramework.BeginTestCase("Load IJW dll via reflection");
-            Assembly ijwNativeDll = Assembly.Load("IjwNativeCallingManagedDll");
-            TestFramework.EndTestCase();
+            Assembly ijwNativeDll = IjwHelper.LoadIjwAssembly("IjwNativeCallingManagedDll");
 
             TestFramework.BeginTestCase("Call native method returning int");
             Type testType = ijwNativeDll.GetType("TestClass");
@@ -56,9 +48,6 @@ namespace NativeCallingManaged
         }
 
         [DllImport("kernel32.dll")]
-        static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, int dwFlags);
-
-        [DllImport("kernel32.dll")]
         static extern IntPtr GetModuleHandle(string lpModuleName);
     }
 }
index 8b01960..bee62a5 100644 (file)
@@ -37,6 +37,7 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="NativeCallingManaged.cs" />
+    <Compile Include="../IjwHelper.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="../IjwNativeCallingManagedDll/CMakeLists.txt" />
diff --git a/tests/src/Interop/IJW/NativeVarargs/CMakeLists.txt b/tests/src/Interop/IJW/NativeVarargs/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c7cb1e3
--- /dev/null
@@ -0,0 +1,13 @@
+cmake_minimum_required (VERSION 2.6)
+project (IjwNativeVarargs)
+include("../IJW.cmake")
+
+include_directories( ${INC_PLATFORM_DIR} )
+set(SOURCES IjwNativeVarargs.cpp)
+
+# add the shared library
+add_library (IjwNativeVarargs SHARED ${SOURCES})
+target_link_libraries(IjwNativeVarargs ${LINK_LIBRARIES_ADDITIONAL})
+
+# add the install targets
+install (TARGETS IjwNativeVarargs DESTINATION bin)
diff --git a/tests/src/Interop/IJW/NativeVarargs/IjwNativeVarargs.cpp b/tests/src/Interop/IJW/NativeVarargs/IjwNativeVarargs.cpp
new file mode 100644 (file)
index 0000000..9005f6b
--- /dev/null
@@ -0,0 +1,495 @@
+// 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.
+#include <cstddef>
+#include <vector>
+#include <cstdarg>
+#include <cstdint>
+#include <numeric>
+#include <array>
+#include <functional>
+#include <iostream>
+#using <mscorlib.dll>
+using namespace System::Collections::Generic;
+
+public enum class TestCases
+{
+    SumInts,
+    SumFloats,
+    SumSumInt64s,
+    SumWidenedShorts,
+    SumDoubles,
+    SumWidenedFloats,
+    SumHFAs,
+    DoublesInIntegerRegisters
+};
+
+struct HFA
+{
+    float f1;
+    float f2;
+    float f3;
+    float f4;
+};
+
+#pragma unmanaged
+
+int SumInts(std::size_t numElements, ...)
+{
+    va_list args;
+    va_start(args, numElements);
+    int sum = 0;
+    for (std::size_t i = 0; i < numElements; ++i)
+    {
+        sum += va_arg(args, int);
+    }
+    va_end(args);
+    return sum;
+}
+
+std::int64_t SumSumInt64s(std::size_t numElements, ...)
+{
+    va_list args;
+    va_start(args, numElements);
+    std::int64_t sum = 0;
+    for (std::size_t i = 0; i < numElements; ++i)
+    {
+        sum += va_arg(args, std::int64_t);
+    }
+    va_end(args);
+    return sum;
+}
+
+double SumDoubles(std::size_t numElements, ...)
+{
+    va_list args;
+    va_start(args, numElements);
+    double sum = 0;
+    for (std::size_t i = 0; i < numElements; ++i)
+    {
+        sum += va_arg(args, double);
+    }
+    va_end(args);
+    return sum;
+}
+
+float SumHFAs(std::size_t numElements, ...)
+{
+    va_list args;
+    va_start(args, numElements);
+    float sum = 0;
+    for (std::size_t i = 0; i < numElements; ++i)
+    {
+        HFA hfa = va_arg(args, HFA);
+        sum += hfa.f1 + hfa.f2 + hfa.f3 + hfa.f4;
+    }
+    va_end(args);
+    return sum;
+}
+
+constexpr std::size_t NumArgsPerCall = 41; // Match number of arguments used in JIT/Directed/arglist/vararg test.
+
+#pragma managed
+
+public ref class TestClass
+{
+public:
+    List<TestCases>^ RunTests(int seed)
+    {
+        System::Random^ rng = gcnew System::Random(seed);
+        List<TestCases>^ failedTests = gcnew List<TestCases>();
+
+        if (!RunIntsTest(rng))
+        {
+            failedTests->Add(TestCases::SumInts);
+        }
+        if (!RunDoublesTest(rng))
+        {
+            failedTests->Add(TestCases::SumDoubles);
+        }
+        if (!RunSumInt64sTest(rng))
+        {
+            failedTests->Add(TestCases::SumSumInt64s);
+        }
+        if (!RunWidenedShortsTest(rng))
+        {
+            failedTests->Add(TestCases::SumWidenedShorts);
+        }
+        if (!RunWidenedFloatsTest(rng))
+        {
+            failedTests->Add(TestCases::SumWidenedFloats);
+        }
+        if (!RunHFAsTest(rng))
+        {
+            failedTests->Add(TestCases::SumHFAs);
+        }
+#if _WIN64
+        if (!RunDoublesInIntegerRegistersTest())
+        {
+            failedTests->Add(TestCases::DoublesInIntegerRegisters);
+        }
+#endif
+
+        return failedTests;
+    }
+private:
+    bool RunIntsTest(System::Random^ rng)
+    {
+        std::array<int, NumArgsPerCall> values;
+        for(std::size_t i = 0; i < NumArgsPerCall; ++i)
+        {
+            values[i] = rng->Next(System::Int32::MinValue, System::Int32::MaxValue);
+        }
+
+        auto expected = std::accumulate(values.begin(), values.end(), 0, std::plus<>{});
+        auto actual = SumInts(NumArgsPerCall,
+            values[0],
+            values[1],
+            values[2],
+            values[3],
+            values[4],
+            values[5],
+            values[6],
+            values[7],
+            values[8],
+            values[9],
+            values[10],
+            values[11],
+            values[12],
+            values[13],
+            values[14],
+            values[15],
+            values[16],
+            values[17],
+            values[18],
+            values[19],
+            values[20],
+            values[21],
+            values[22],
+            values[23],
+            values[24],
+            values[25],
+            values[26],
+            values[27],
+            values[28],
+            values[29],
+            values[30],
+            values[31],
+            values[32],
+            values[33],
+            values[34],
+            values[35],
+            values[36],
+            values[37],
+            values[38],
+            values[39],
+            values[40]
+        );
+        bool result = expected == actual;
+        if (!result)
+        {
+            std::cout << "RunIntsTest Failed:" << "Expected:" << expected << '\t' << "Actual:" << actual << std::endl;
+        }
+        return result;
+    }
+
+    bool RunDoublesTest(System::Random^ rng)
+    {
+        std::array<double, NumArgsPerCall> values;
+        for (std::size_t i = 0; i < NumArgsPerCall; ++i)
+        {
+            values[i] = (double)rng->Next(System::Int32::MinValue, System::Int32::MaxValue);
+        }
+
+        auto expected = std::accumulate(values.begin(), values.end(), 0.0, std::plus<>{});
+        auto actual = SumDoubles(NumArgsPerCall,
+            values[0],
+            values[1],
+            values[2],
+            values[3],
+            values[4],
+            values[5],
+            values[6],
+            values[7],
+            values[8],
+            values[9],
+            values[10],
+            values[11],
+            values[12],
+            values[13],
+            values[14],
+            values[15],
+            values[16],
+            values[17],
+            values[18],
+            values[19],
+            values[20],
+            values[21],
+            values[22],
+            values[23],
+            values[24],
+            values[25],
+            values[26],
+            values[27],
+            values[28],
+            values[29],
+            values[30],
+            values[31],
+            values[32],
+            values[33],
+            values[34],
+            values[35],
+            values[36],
+            values[37],
+            values[38],
+            values[39],
+            values[40]
+        );
+        bool result = expected == actual;
+        if (!result)
+        {
+            std::cout << "RunDoublesTest Failed:" << "Expected:" << expected << '\t' << "Actual:" << actual << std::endl;
+        }
+        return result;
+    }
+
+    bool RunHFAsTest(System::Random^ rng)
+    {
+        std::array<HFA, NumArgsPerCall> values;
+        for (std::size_t i = 0; i < NumArgsPerCall; ++i)
+        {
+            values[i].f1 = (float)rng->Next(System::Int32::MinValue, System::Int32::MaxValue);
+            values[i].f2 = (float)rng->Next(System::Int32::MinValue, System::Int32::MaxValue);
+            values[i].f3 = (float)rng->Next(System::Int32::MinValue, System::Int32::MaxValue);
+            values[i].f4 = (float)rng->Next(System::Int32::MinValue, System::Int32::MaxValue);
+        }
+
+        auto expected = std::accumulate(values.begin(), values.end(), 0.0f, AggregateHFAs);
+        auto actual = SumHFAs(NumArgsPerCall,
+            values[0],
+            values[1],
+            values[2],
+            values[3],
+            values[4],
+            values[5],
+            values[6],
+            values[7],
+            values[8],
+            values[9],
+            values[10],
+            values[11],
+            values[12],
+            values[13],
+            values[14],
+            values[15],
+            values[16],
+            values[17],
+            values[18],
+            values[19],
+            values[20],
+            values[21],
+            values[22],
+            values[23],
+            values[24],
+            values[25],
+            values[26],
+            values[27],
+            values[28],
+            values[29],
+            values[30],
+            values[31],
+            values[32],
+            values[33],
+            values[34],
+            values[35],
+            values[36],
+            values[37],
+            values[38],
+            values[39],
+            values[40]
+        );
+        bool result = expected == actual;
+        if (!result)
+        {
+            std::cout << "RunHFAsTest Failed:" << "Expected:" << expected << '\t' << "Actual:" << actual << std::endl;
+        }
+        return result;
+    }
+
+    static float AggregateHFAs(float current, HFA hfa)
+    {
+        return current + hfa.f1 + hfa.f2 + hfa.f3 + hfa.f4;
+    }
+
+    bool RunSumInt64sTest(System::Random^ rng)
+    {
+        std::array<std::int64_t, NumArgsPerCall> values;
+        for (std::size_t i = 0; i < NumArgsPerCall; ++i)
+        {
+            values[i] = rng->Next(System::Int32::MinValue, System::Int32::MaxValue);
+        }
+
+        auto expected = std::accumulate(values.begin(), values.end(), 0LL, std::plus<>{});
+        auto actual = SumSumInt64s(NumArgsPerCall,
+            values[0],
+            values[1],
+            values[2],
+            values[3],
+            values[4],
+            values[5],
+            values[6],
+            values[7],
+            values[8],
+            values[9],
+            values[10],
+            values[11],
+            values[12],
+            values[13],
+            values[14],
+            values[15],
+            values[16],
+            values[17],
+            values[18],
+            values[19],
+            values[20],
+            values[21],
+            values[22],
+            values[23],
+            values[24],
+            values[25],
+            values[26],
+            values[27],
+            values[28],
+            values[29],
+            values[30],
+            values[31],
+            values[32],
+            values[33],
+            values[34],
+            values[35],
+            values[36],
+            values[37],
+            values[38],
+            values[39],
+            values[40]
+        );
+        bool result = expected == actual;
+        if (!result)
+        {
+            std::cout << "RunSumInt64sTest Failed:" << "Expected:" << expected << '\t' << "Actual:" << actual << std::endl;
+        }
+        return result;
+    }
+
+    bool RunWidenedShortsTest(System::Random^ rng)
+    {
+        std::array<int, NumArgsPerCall / 2> intValues;
+        std::array<short, NumArgsPerCall - (NumArgsPerCall / 2)> shortValues;
+        for (std::size_t i = 0; i < intValues.size(); ++i)
+        {
+            intValues[i] = rng->Next(System::Int32::MinValue, System::Int32::MaxValue);
+        }
+        for (std::size_t i = 0; i < shortValues.size(); ++i)
+        {
+            shortValues[i] = (short)rng->Next(System::Int16::MinValue, System::Int16::MaxValue);
+        }
+
+        auto expected = std::accumulate(intValues.begin(), intValues.end(), 0, std::plus<>{}) + std::accumulate(shortValues.begin(), shortValues.end(), 0LL, std::plus<>{});
+        auto actual = SumInts(NumArgsPerCall,
+            shortValues[0],
+            intValues[0], shortValues[1],
+            intValues[1], shortValues[2],
+            intValues[2], shortValues[3],
+            intValues[3], shortValues[4],
+            intValues[4], shortValues[5],
+            intValues[5], shortValues[6],
+            intValues[6], shortValues[7],
+            intValues[7], shortValues[8],
+            intValues[8], shortValues[9],
+            intValues[9], shortValues[10],
+            intValues[10], shortValues[11],
+            intValues[11], shortValues[12],
+            intValues[12], shortValues[13],
+            intValues[13], shortValues[14],
+            intValues[14], shortValues[15],
+            intValues[15], shortValues[16],
+            intValues[16], shortValues[17],
+            intValues[17], shortValues[18],
+            intValues[18], shortValues[19],
+            intValues[19], shortValues[20]
+        );
+        bool result = expected == actual;
+        if (!result)
+        {
+            std::cout << "RunWidenedShortsTest Failed:" << "Expected:" << expected << '\t' << "Actual:" << actual << std::endl;
+        }
+        return result;
+    }
+
+    bool RunWidenedFloatsTest(System::Random^ rng)
+    {
+        std::array<float, NumArgsPerCall / 2> floatValues;
+        std::array<double, NumArgsPerCall - (NumArgsPerCall / 2)> doubleValues;
+        for (std::size_t i = 0; i < floatValues.size(); ++i)
+        {
+            floatValues[i] = (float)rng->Next(System::Int32::MinValue, System::Int32::MaxValue);
+        }
+        for (std::size_t i = 0; i < doubleValues.size(); ++i)
+        {
+            doubleValues[i] = (double)rng->Next(System::Int32::MinValue, System::Int32::MaxValue);
+        }
+
+        double expected = 0.0;
+        for (std::size_t i = 0; i < floatValues.size(); ++i)
+        {
+            expected += floatValues[i];
+        }
+        for (std::size_t i = 0; i < doubleValues.size(); ++i)
+        {
+            expected += doubleValues[i];
+        }
+
+        auto actual = SumDoubles(NumArgsPerCall,
+            doubleValues[0],
+            floatValues[0], doubleValues[1],
+            floatValues[1], doubleValues[2],
+            floatValues[2], doubleValues[3],
+            floatValues[3], doubleValues[4],
+            floatValues[4], doubleValues[5],
+            floatValues[5], doubleValues[6],
+            floatValues[6], doubleValues[7],
+            floatValues[7], doubleValues[8],
+            floatValues[8], doubleValues[9],
+            floatValues[9], doubleValues[10],
+            floatValues[10], doubleValues[11],
+            floatValues[11], doubleValues[12],
+            floatValues[12], doubleValues[13],
+            floatValues[13], doubleValues[14],
+            floatValues[14], doubleValues[15],
+            floatValues[15], doubleValues[16],
+            floatValues[16], doubleValues[17],
+            floatValues[17], doubleValues[18],
+            floatValues[18], doubleValues[19],
+            floatValues[19], doubleValues[20]
+        );
+        bool result = expected == actual;
+        if (!result)
+        {
+            std::cout << "RunWidenedFloatsTest Failed:" << "Expected:" << expected << '\t' << "Actual:" << actual << std::endl;
+        }
+        return result;
+    }
+
+    bool RunDoublesInIntegerRegistersTest()
+    {
+        double a = 123.456;
+        std::int64_t expected = *reinterpret_cast<std::int64_t*>(&a);
+        std::int64_t actual = SumSumInt64s(1, a);
+        bool result = expected == actual;
+        if (!result)
+        {
+            std::cout << "RunDoublesInIntegerRegistersTest Failed:" << "Expected:" << expected << '\t' << "Actual:" << actual << std::endl;
+        }
+        return result;
+    }
+};
diff --git a/tests/src/Interop/IJW/NativeVarargs/NativeVarargsTest.cs b/tests/src/Interop/IJW/NativeVarargs/NativeVarargsTest.cs
new file mode 100644 (file)
index 0000000..821e04c
--- /dev/null
@@ -0,0 +1,54 @@
+// 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.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using TestLibrary;
+
+namespace NativeVarargsTest
+{
+    class NativeVarargsTest
+    {
+        static int Main(string[] args)
+        {
+            if(Environment.OSVersion.Platform != PlatformID.Win32NT || TestLibrary.Utilities.IsWindows7)
+            {
+                return 100;
+            }
+
+            // Use the same seed for consistency between runs.
+            int seed = 42;
+
+            try
+            {
+                Assembly ijwNativeDll = IjwHelper.LoadIjwAssembly("IjwNativeVarargs");
+                Type testType = ijwNativeDll.GetType("TestClass");
+                object testInstance = Activator.CreateInstance(testType);
+                MethodInfo testMethod = testType.GetMethod("RunTests");
+                IEnumerable failedTests = (IEnumerable)testMethod.Invoke(testInstance, BindingFlags.DoNotWrapExceptions, null, new object[] {seed}, null);
+
+                if (failedTests.OfType<object>().Any())
+                {
+                    Console.WriteLine("Failed Varargs tests:");
+                    foreach (var failedTest in failedTests)
+                    {
+                        Console.WriteLine($"\t{failedTest}");
+                    }
+                    return 102;
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine(ex);
+                return 101;
+            }
+            return 100;
+        }
+    }
+}
diff --git a/tests/src/Interop/IJW/NativeVarargs/NativeVarargsTest.csproj b/tests/src/Interop/IJW/NativeVarargs/NativeVarargsTest.csproj
new file mode 100644 (file)
index 0000000..176a38f
--- /dev/null
@@ -0,0 +1,50 @@
+<?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" />
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), Interop.settings.targets))\Interop.settings.targets" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <AssemblyName>NativeVarargsTest</AssemblyName>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{49D1D482-E783-4CA9-B6BA-A9714BF81036}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    
+    <!-- IJW is Windows-only -->
+    <!-- Test unsupported outside of windows -->
+    <TestUnsupportedOutsideWindows>true</TestUnsupportedOutsideWindows>
+    <DisableProjectBuild Condition="'$(TargetsUnix)' == 'true'">true</DisableProjectBuild>
+
+    <!-- IJW is not supported on ARM64 -->
+    <DisableProjectBuild Condition="'$(Platform)' == 'arm64'">true</DisableProjectBuild>
+    <!-- Native varargs not supported on ARM -->
+    <DisableProjectBuild Condition="'$(Platform)' == 'arm'">true</DisableProjectBuild>
+  
+    <!-- Loading IJW assemblies into an unloadable context is not allowed -->
+    <UnloadabilityIncompatible>true</UnloadabilityIncompatible>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+  </PropertyGroup>
+  <PropertyGroup>
+    <CopyDebugCRTDllsToOutputDirectory>true</CopyDebugCRTDllsToOutputDirectory>
+  </PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="NativeVarargsTest.cs" />
+    <Compile Include="../IjwHelper.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="CMakeLists.txt" />
+    <ProjectReference Include="../ijwhostmock/CMakeLists.txt" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>
index f67ae45..117a54f 100644 (file)
@@ -24,6 +24,7 @@
     Condition="'$(TargetsWindows)' == 'true' And ('$(Configuration)' == 'Debug' Or '$(Configuration)' == 'Checked') And '$(CopyDebugCRTDllsToOutputDirectory)' == 'true'" >
 
     <None Include="$(VCToolsRedistDir)onecore/debug_nonredist/$(Platform)/Microsoft.VC*.DebugCRT/vcruntime*d.dll" CopyToOutputDirectory="Always" />
+    <None Include="$(VCToolsRedistDir)onecore/debug_nonredist/$(Platform)/Microsoft.VC*.DebugCRT/msvcp*d.dll" CopyToOutputDirectory="Always" />
     <None Include="$(ExtensionSdkDir)/Microsoft.UniversalCRT.Debug/$(UCRTVersion)/Redist/Debug/$(Platform)/ucrtbased.dll" CopyToOutputDirectory="Always" />
   </ItemGroup>
 </Project>