From f6e4b6c246f5e94ae7adf9d934881130f65fec41 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 26 Jan 2024 14:24:27 -0800 Subject: [PATCH] Convert our unit tests and perf harness to use native entry-points (#50) --- .github/workflows/main.yml | 12 +- CMakeLists.txt | 3 + Directory.Build.props | 16 +- src/inc/internal/dnmd_platform.hpp | 8 + src/inc/internal/dnmd_tools_platform.hpp | 40 +- src/interfaces/metadataimport.cpp | 2 +- test/CMakeLists.txt | 38 +- test/DNMD.Tests.sln | 12 - test/FindNetHost.cmake | 22 + test/FindNetHostDir.proj | 5 + test/Regression.Performance/Program.cs | 112 --- .../Regression.Performance.csproj | 21 - .../Regression.TargetAssembly.ilproj | 1 - test/Regression.UnitTests/ImportTests.cs | 324 -------- .../Properties/launchSettings.json | 8 - .../Regression.UnitTests.csproj | 41 - test/Regression.UnitTests/SymReaderTests.cs | 25 - test/regnative/CMakeLists.txt | 24 - test/regnative/perf.cpp | 220 ------ test/regnative/regnative.cpp | 26 - test/regnative/regnative.hpp | 136 ---- test/regnative/unit_corsym.cpp | 10 - test/regpal/CMakeLists.txt | 17 + test/regpal/pal.cpp | 186 +++++ test/regpal/pal.hpp | 18 + test/regperf/CMakeLists.txt | 22 + test/regperf/perf.cpp | 218 +++++ test/regtest/CMakeLists.txt | 55 ++ test/regtest/asserts.h | 25 + test/regtest/baseline.h | 14 + test/regtest/discovery.cpp | 337 ++++++++ test/regtest/fixtures.h | 58 ++ test/regtest/main.cpp | 56 ++ .../unit_cor.cpp => regtest/metadata.cpp} | 743 +++++++++--------- 34 files changed, 1489 insertions(+), 1366 deletions(-) create mode 100644 test/FindNetHost.cmake create mode 100644 test/FindNetHostDir.proj delete mode 100644 test/Regression.Performance/Program.cs delete mode 100644 test/Regression.Performance/Regression.Performance.csproj delete mode 100644 test/Regression.UnitTests/ImportTests.cs delete mode 100644 test/Regression.UnitTests/Properties/launchSettings.json delete mode 100644 test/Regression.UnitTests/Regression.UnitTests.csproj delete mode 100644 test/Regression.UnitTests/SymReaderTests.cs delete mode 100644 test/regnative/CMakeLists.txt delete mode 100644 test/regnative/perf.cpp delete mode 100644 test/regnative/regnative.cpp delete mode 100644 test/regnative/regnative.hpp delete mode 100644 test/regnative/unit_corsym.cpp create mode 100644 test/regpal/CMakeLists.txt create mode 100644 test/regpal/pal.cpp create mode 100644 test/regpal/pal.hpp create mode 100644 test/regperf/CMakeLists.txt create mode 100644 test/regperf/perf.cpp create mode 100644 test/regtest/CMakeLists.txt create mode 100644 test/regtest/asserts.h create mode 100644 test/regtest/baseline.h create mode 100644 test/regtest/discovery.cpp create mode 100644 test/regtest/fixtures.h create mode 100644 test/regtest/main.cpp rename test/{regnative/unit_cor.cpp => regtest/metadata.cpp} (74%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1a4d9231..77b96dac 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,6 +29,9 @@ jobs: with: dotnet-version: '8.0.x' dotnet-quality: 'preview' + + - name: List dotnet information + run: dotnet --info - name: Build Vendored Dependencies if: ${{ !matrix.use-vendored-libs }} @@ -46,13 +49,8 @@ jobs: cmake -S . -B artifacts -DCMAKE_BUILD_TYPE=${{ matrix.flavor }} -DINCLUDE_VENDORED_LIBS=${{ matrix.use-vendored-libs }} cmake --build artifacts --config ${{ matrix.flavor }} --target install - - name: Build Managed Test Components - run: dotnet build --configuration ${{ matrix.flavor }} - working-directory: ./test - - - name: Run Unit Tests - run: dotnet test --configuration ${{ matrix.flavor }} --logger trx --results-directory "TestResults-${{ matrix.os }}-${{ matrix.flavor }}" - working-directory: ./test/Regression.UnitTests + - name: Run Tests + run: ctest --test-dir artifacts --output-on-failure -C ${{ matrix.flavor }} # - name: Upload Test Results # if: always() diff --git a/CMakeLists.txt b/CMakeLists.txt index ea98b419..ad428c6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,5 +16,8 @@ include_directories(src/inc) include_directories(src/inc/external) # Hiding the "external" subdirectory due to uses of <...> in cor.h. add_subdirectory(src/) + +enable_testing() + add_subdirectory(test/) diff --git a/Directory.Build.props b/Directory.Build.props index c1a9124c..c8149211 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,22 +1,12 @@ - Debug - - - - $(MSBuildThisFileDirectory) - $(RepoDir)artifacts - $(BaseArtifactsPath)/$(Configuration)/ - - $(ArtifactsDir)obj/$(MSBuildProjectName) - $(ArtifactsDir)bin/$(MSBuildProjectName) + true + $(MSBuildThisFileDirectory)/artifacts/managed - $(BaseIntermediateOutputPath) - $(BaseOutputPath) $(BaseArtifactsPath)/bin $(BaseArtifactsPath)/lib - net8.0 + net8.0 \ No newline at end of file diff --git a/src/inc/internal/dnmd_platform.hpp b/src/inc/internal/dnmd_platform.hpp index 767fc9ef..215a35e9 100644 --- a/src/inc/internal/dnmd_platform.hpp +++ b/src/inc/internal/dnmd_platform.hpp @@ -13,6 +13,14 @@ #endif // !BUILD_WINDOWS +// Machine code masks for native (R2R) images +// See pedecoder.h in CoreCLR +#define IMAGE_FILE_MACHINE_OS_MASK_APPLE 0x4644 +#define IMAGE_FILE_MACHINE_OS_MASK_FREEBSD 0xADC4 +#define IMAGE_FILE_MACHINE_OS_MASK_LINUX 0x7B79 +#define IMAGE_FILE_MACHINE_OS_MASK_NETBSD 0x1993 +#define IMAGE_FILE_MACHINE_OS_MASK_SUN 0x1992 + #include #include diff --git a/src/inc/internal/dnmd_tools_platform.hpp b/src/inc/internal/dnmd_tools_platform.hpp index efb69f18..d7c0de13 100644 --- a/src/inc/internal/dnmd_tools_platform.hpp +++ b/src/inc/internal/dnmd_tools_platform.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "dnmd_platform.hpp" #include "span.hpp" @@ -85,6 +86,33 @@ inline bool write_out_file(char const* file, malloc_span b) return true; } +inline bool find_pe_image_bitness(uint16_t machine, uint8_t& bitness) +{ +#define MAKE_MACHINE_CASE(x) \ + case ((x) ^ IMAGE_FILE_MACHINE_OS_MASK_APPLE): \ + case ((x) ^ IMAGE_FILE_MACHINE_OS_MASK_FREEBSD): \ + case ((x) ^ IMAGE_FILE_MACHINE_OS_MASK_LINUX): \ + case ((x) ^ IMAGE_FILE_MACHINE_OS_MASK_NETBSD): \ + case ((x) ^ IMAGE_FILE_MACHINE_OS_MASK_SUN): \ + case (x) + + switch (machine) + { + MAKE_MACHINE_CASE(IMAGE_FILE_MACHINE_I386): + MAKE_MACHINE_CASE(IMAGE_FILE_MACHINE_ARM): + bitness = 32; + return true; + MAKE_MACHINE_CASE(IMAGE_FILE_MACHINE_AMD64): + MAKE_MACHINE_CASE(IMAGE_FILE_MACHINE_ARM64): + bitness = 64; + return true; + default: + return false; + } + +#undef MAKE_MACHINE_CASE +} + inline bool get_metadata_from_pe(malloc_span& b) { if (b.size() < sizeof(IMAGE_DOS_HEADER)) @@ -111,8 +139,13 @@ inline bool get_metadata_from_pe(malloc_span& b) uint16_t section_header_count; uint8_t* section_header_begin; auto nt_header_any = (PIMAGE_NT_HEADERS)(b + dos_header->e_lfanew); - if (nt_header_any->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64 - || nt_header_any->FileHeader.Machine == IMAGE_FILE_MACHINE_ARM64) + uint16_t machine = nt_header_any->FileHeader.Machine; + + uint8_t bitness; + if (!find_pe_image_bitness(machine, bitness)) + return false; + + if (bitness == 64) { auto nt_header64 = (PIMAGE_NT_HEADERS64)nt_header_any; if (remaining_pe_size < sizeof(*nt_header64)) @@ -122,8 +155,7 @@ inline bool get_metadata_from_pe(malloc_span& b) section_header_begin = (uint8_t*)&nt_header64[1]; dotnet_dir = &nt_header64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR]; } - else if (nt_header_any->FileHeader.Machine == IMAGE_FILE_MACHINE_I386 - || nt_header_any->FileHeader.Machine == IMAGE_FILE_MACHINE_ARM) + else if (bitness == 32) { auto nt_header32 = (PIMAGE_NT_HEADERS32)nt_header_any; if (remaining_pe_size < sizeof(*nt_header32)) diff --git a/src/interfaces/metadataimport.cpp b/src/interfaces/metadataimport.cpp index 0c60f1ea..de232412 100644 --- a/src/interfaces/metadataimport.cpp +++ b/src/interfaces/metadataimport.cpp @@ -2727,7 +2727,7 @@ HRESULT STDMETHODCALLTYPE MetadataImportRO::GetCustomAttributeByName( mdcursor_t cursor; uint32_t count; if (!md_create_cursor(_md_ptr.get(), mdtid_CustomAttribute, &cursor, &count)) - return CLDB_E_RECORD_NOTFOUND; + return S_FALSE; // If no custom attributes are defined, treat it the same as if the attribute is not found. char buffer[1024]; pal::StringConvert cvt{ szName, buffer }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1c4112a8..fb59f89f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,4 +1,40 @@ # Configure the compiler include(../configure.cmake) +include(FindNetHost.cmake) -add_subdirectory(regnative/) \ No newline at end of file +if (POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) # Set timestamps in downloaded archives to the time of download. +endif() + +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY + https://github.com/google/googletest.git + GIT_TAG + v1.14.0 +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) + +FetchContent_Declare( + benchmark + GIT_REPOSITORY + https://github.com/google/benchmark.git + GIT_TAG + v1.8.3 +) + +# Don't build the tests for the benchmark library. +set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) +set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest benchmark) + +include(GoogleTest) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +add_subdirectory(regpal) +add_subdirectory(regperf) +add_subdirectory(regtest) diff --git a/test/DNMD.Tests.sln b/test/DNMD.Tests.sln index 66062f70..1c895967 100644 --- a/test/DNMD.Tests.sln +++ b/test/DNMD.Tests.sln @@ -3,10 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.33026.144 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Regression.UnitTests", "Regression.UnitTests\Regression.UnitTests.csproj", "{D5105B2A-6016-4B1C-A46B-5A841AE5AD26}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Regression.Performance", "Regression.Performance\Regression.Performance.csproj", "{3ED137D0-B5A2-4309-BDA5-BD9DE3FFC752}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Regression.TargetAssembly", "Regression.TargetAssembly\Regression.TargetAssembly.ilproj", "{D3BEEF3D-C137-49AC-96DD-E5B93E2F4C21}" EndProject Global @@ -15,14 +11,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D5105B2A-6016-4B1C-A46B-5A841AE5AD26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5105B2A-6016-4B1C-A46B-5A841AE5AD26}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5105B2A-6016-4B1C-A46B-5A841AE5AD26}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5105B2A-6016-4B1C-A46B-5A841AE5AD26}.Release|Any CPU.Build.0 = Release|Any CPU - {3ED137D0-B5A2-4309-BDA5-BD9DE3FFC752}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3ED137D0-B5A2-4309-BDA5-BD9DE3FFC752}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3ED137D0-B5A2-4309-BDA5-BD9DE3FFC752}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3ED137D0-B5A2-4309-BDA5-BD9DE3FFC752}.Release|Any CPU.Build.0 = Release|Any CPU {D3BEEF3D-C137-49AC-96DD-E5B93E2F4C21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D3BEEF3D-C137-49AC-96DD-E5B93E2F4C21}.Debug|Any CPU.Build.0 = Debug|Any CPU {D3BEEF3D-C137-49AC-96DD-E5B93E2F4C21}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/test/FindNetHost.cmake b/test/FindNetHost.cmake new file mode 100644 index 00000000..c141d7a4 --- /dev/null +++ b/test/FindNetHost.cmake @@ -0,0 +1,22 @@ +execute_process( + COMMAND ${CMAKE_COMMAND} -E env DOTNET_NOLOGO=1 dotnet msbuild FindNetHostDir.proj -t:OutputNetHostDir -nologo + OUTPUT_VARIABLE NET_HOST_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND_ERROR_IS_FATAL ANY + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + +string(STRIP ${NET_HOST_DIR} NET_HOST_DIR) + +if (WIN32) + add_library(nethost IMPORTED SHARED) + set_target_properties(nethost PROPERTIES + IMPORTED_LOCATION ${NET_HOST_DIR}/nethost.dll + IMPORTED_IMPLIB ${NET_HOST_DIR}/nethost.lib) +else() + add_library(nethost IMPORTED STATIC) + target_compile_definitions(nethost INTERFACE NETHOST_USE_AS_STATIC) + set_target_properties(nethost PROPERTIES + IMPORTED_LOCATION ${NET_HOST_DIR}/libnethost.a) +endif() + +target_include_directories(nethost INTERFACE ${NET_HOST_DIR}) \ No newline at end of file diff --git a/test/FindNetHostDir.proj b/test/FindNetHostDir.proj new file mode 100644 index 00000000..c2130191 --- /dev/null +++ b/test/FindNetHostDir.proj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/test/Regression.Performance/Program.cs b/test/Regression.Performance/Program.cs deleted file mode 100644 index 411e716e..00000000 --- a/test/Regression.Performance/Program.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System.Diagnostics; -using System.Reflection.PortableExecutable; -using System.Runtime.InteropServices; - -using Common; - -namespace Regression.Performance -{ - public unsafe class Compare - { - private PEReader _peReader; - private PEMemoryBlock _metadataBlock; - - delegate* unmanaged _Initialize; - - private readonly struct Scenario - { - public string Name { get; init; } - public delegate* unmanaged Baseline { get; init; } - public delegate* unmanaged Current { get; init; } - } - - List _scenarios = new(); - - public Compare(string currentPath) - { - // Load System.Private.CoreLib - var spcl = typeof(object).Assembly.Location; - _peReader = new(File.OpenRead(spcl)); - _metadataBlock = _peReader.GetMetadata(); - - // Acquire native functions - nint mod = NativeLibrary.Load(currentPath); - _Initialize = (delegate* unmanaged)NativeLibrary.GetExport(mod, "PerfInitialize"); - - string[] scenarioNames = new[] - { - "CreateImport", - "EnumTypeDefs", - "GetScopeProps", - "EnumUserStrings", - "GetCustomAttributeByName", - }; - - // Look up each scenario test export. - foreach (var name in scenarioNames) - { - _scenarios.Add(new Scenario() - { - Name = name, - Baseline = (delegate* unmanaged)NativeLibrary.GetExport(mod, $"PerfBaseline{name}"), - Current = (delegate* unmanaged)NativeLibrary.GetExport(mod, $"PerfCurrent{name}"), - }); - } - - int hr = _Initialize(_metadataBlock.Pointer, _metadataBlock.Length, Dispensers.Baseline); - if (hr < 0) - { - throw new Exception($"Initialization failed: 0x{hr:x}"); - } - } - - public void Run(int iter) - { - int hr; - const int width = 12; - var sw = new Stopwatch(); - - foreach (var scenario in _scenarios) - { - Console.WriteLine(scenario.Name); - sw.Restart(); - hr = scenario.Baseline(iter); - Console.WriteLine($" Baseline: {sw.ElapsedMilliseconds,width}"); - if (hr < 0) throw new Exception($"Failure 0x{hr:x}"); - sw.Restart(); - hr = scenario.Current(iter); - Console.WriteLine($" Current: {sw.ElapsedMilliseconds,width}"); - if (hr < 0) throw new Exception($"Failure 0x{hr:x}"); - } - } - } - - public class Program - { - public static void Main(string[] args) - { - string regnativePath; - if (args.Length > 0) - { - regnativePath = args[0]; - } - else - { - regnativePath = - OperatingSystem.IsWindows() ? "regnative.dll" - : OperatingSystem.IsMacOS() ? "libregnative.dylib" - : "libregnative.so"; - regnativePath = Path.Combine(AppContext.BaseDirectory, regnativePath); - } - - var test = new Compare(regnativePath); - - Console.WriteLine("Warm-up"); - test.Run(100); - - const int iter = 100_000; - Console.WriteLine($"\nRun iterations - {iter}"); - test.Run(iter); - } - } -} diff --git a/test/Regression.Performance/Regression.Performance.csproj b/test/Regression.Performance/Regression.Performance.csproj deleted file mode 100644 index 8e616900..00000000 --- a/test/Regression.Performance/Regression.Performance.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - $(TargetFrameworkLatest) - Exe - enable - enable - true - - false - - - - - - - - - - - diff --git a/test/Regression.TargetAssembly/Regression.TargetAssembly.ilproj b/test/Regression.TargetAssembly/Regression.TargetAssembly.ilproj index ed558a53..5f92f5ae 100644 --- a/test/Regression.TargetAssembly/Regression.TargetAssembly.ilproj +++ b/test/Regression.TargetAssembly/Regression.TargetAssembly.ilproj @@ -1,6 +1,5 @@ - $(TargetFrameworkLatest) Library false diff --git a/test/Regression.UnitTests/ImportTests.cs b/test/Regression.UnitTests/ImportTests.cs deleted file mode 100644 index 6eb6a8f1..00000000 --- a/test/Regression.UnitTests/ImportTests.cs +++ /dev/null @@ -1,324 +0,0 @@ -using System.Buffers; -using System.Diagnostics; -using System.Text; -using System.Reflection.Metadata; -using System.Runtime.CompilerServices; -using System.Reflection.PortableExecutable; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; - -using Common; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Emit; -using Xunit; -using Xunit.Abstractions; - -namespace Regression.UnitTests -{ - public unsafe class ImportTests - { - private delegate* unmanaged _importAPIs; - private delegate* unmanaged _longRunningAPIs; - private delegate* unmanaged _findAPIs; - private delegate* unmanaged _importAPIsIndirectionTables; - - public ImportTests(ITestOutputHelper outputHelper) - { - Log = outputHelper; - - nint mod = NativeLibrary.Load(Path.Combine(AppContext.BaseDirectory, Native.Path)); - var initialize = (delegate* unmanaged)NativeLibrary.GetExport(mod, "UnitInitialize"); - int hr = initialize((void*)Dispensers.Baseline, (void*)Dispensers.DeltaImageBuilder); - if (hr < 0) - { - throw new Exception($"Initialization failed: 0x{hr:x}"); - } - - _importAPIs = (delegate* unmanaged)NativeLibrary.GetExport(mod, "UnitImportAPIs"); - _longRunningAPIs = (delegate* unmanaged)NativeLibrary.GetExport(mod, "UnitLongRunningAPIs"); - _findAPIs = (delegate* unmanaged)NativeLibrary.GetExport(mod, "UnitFindAPIs"); - _importAPIsIndirectionTables = (delegate* unmanaged)NativeLibrary.GetExport(mod, "UnitImportAPIsIndirectionTables"); - } - - private ITestOutputHelper Log { get; } - - public static IEnumerable CoreFrameworkLibraries() - { - var spcl = typeof(object).Assembly.Location; - var frameworkDir = Path.GetDirectoryName(spcl)!; - foreach (var managedMaybe in Directory.EnumerateFiles(frameworkDir, "*.dll")) - { - PEReader pe = new(File.OpenRead(managedMaybe)); - if (!pe.HasMetadata) - { - pe.Dispose(); - continue; - } - - yield return new object[] { Path.GetFileName(managedMaybe), pe }; - } - } - - [SupportedOSPlatform("windows")] - public static IEnumerable Net20FrameworkLibraries() - { - foreach (var managedMaybe in Directory.EnumerateFiles(Dispensers.NetFx20Dir, "*.dll")) - { - PEReader pe = new(File.OpenRead(managedMaybe)); - if (!pe.HasMetadata) - { - pe.Dispose(); - continue; - } - - yield return new object[] { Path.GetFileName(managedMaybe), pe }; - } - } - - [SupportedOSPlatform("windows")] - public static IEnumerable Net40FrameworkLibraries() - { - foreach (var managedMaybe in Directory.EnumerateFiles(Dispensers.NetFx40Dir, "*.dll")) - { - PEReader pe = new(File.OpenRead(managedMaybe)); - if (!pe.HasMetadata) - { - pe.Dispose(); - continue; - } - - yield return new object[] { Path.GetFileName(managedMaybe), pe }; - } - } - - public static IEnumerable AllCoreLibs() - { - List corelibs = new() { typeof(object).Assembly.Location }; - - if (OperatingSystem.IsWindows()) - { - corelibs.Add(Path.Combine(Dispensers.NetFx20Dir, "mscorlib.dll")); - corelibs.Add(Path.Combine(Dispensers.NetFx40Dir, "mscorlib.dll")); - } - - foreach (var corelibMaybe in corelibs) - { - if (!File.Exists(corelibMaybe)) - { - continue; - } - - PEReader pe = new(File.OpenRead(corelibMaybe)); - yield return new object[] { Path.GetFileName(corelibMaybe), pe }; - } - } - - public static IEnumerable AssembliesWithDelta() - { - yield return DeltaAssembly1(); - static unsafe object[] DeltaAssembly1() - { - Compilation baselineCompilation = CSharpCompilation.Create("DeltaAssembly1") - .WithReferences(Basic.Reference.Assemblies.NetStandard20.ReferenceInfos.netstandard.Reference) - .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - SyntaxTree sourceBase = CSharpSyntaxTree.ParseText(""" - using System; - public class Class1 - { - private int field; - public void Method(int x) - { - } - - public int Property { get; set; } - - public event EventHandler? Event; - } - """); - baselineCompilation = baselineCompilation.AddSyntaxTrees( - sourceBase, - CSharpSyntaxTree.ParseText(""" - using System; - public class Class2 - { - private int field; - public void Method(int x) - { - } - - public int Property { get; set; } - - public event EventHandler? Event; - } - """)); - - Compilation diffCompilation = baselineCompilation.ReplaceSyntaxTree( - sourceBase, - CSharpSyntaxTree.ParseText(""" - using System; - public class Class1 - { - private class Attr : Attribute { } - - private short field2; - private int field; - - [return:Attr] - public void Method(int x) - { - } - - public int Property { get; set; } - - public short Property2 { get; set; } - - public event EventHandler? Event; - - public event EventHandler? Event2; - } - """)); - - var diagnostics = baselineCompilation.GetDiagnostics(); - MemoryStream baselineImage = new(); - baselineCompilation.Emit(baselineImage, options: new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb)); - baselineImage.Seek(0, SeekOrigin.Begin); - - ModuleMetadata metadata = ModuleMetadata.CreateFromStream(baselineImage); - EmitBaseline baseline = EmitBaseline.CreateInitialBaseline(metadata, _ => default, _ => default, true); - - MemoryStream mddiffStream = new(); - - diffCompilation.EmitDifference( - baseline, - new[] - { - CreateSemanticEdit(SemanticEditKind.Update, baselineCompilation, diffCompilation, c => c.GetTypeByMetadataName("Class1")), - CreateSemanticEdit(SemanticEditKind.Insert, baselineCompilation, diffCompilation, c => c.GetTypeByMetadataName("Class1")!.GetMembers("field2").FirstOrDefault()), - CreateSemanticEdit(SemanticEditKind.Insert, baselineCompilation, diffCompilation, c => c.GetTypeByMetadataName("Class1")!.GetMembers("Property2").FirstOrDefault()), - CreateSemanticEdit(SemanticEditKind.Insert, baselineCompilation, diffCompilation, c => c.GetTypeByMetadataName("Class1")!.GetMembers("Event2").FirstOrDefault()), - CreateSemanticEdit(SemanticEditKind.Update, baselineCompilation, diffCompilation, c => c.GetTypeByMetadataName("Class1")!.GetMembers("Method").FirstOrDefault()), - CreateSemanticEdit(SemanticEditKind.Insert, baselineCompilation, diffCompilation, c => c.GetTypeByMetadataName("Class1")!.GetTypeMembers("Attr").FirstOrDefault()), - }, - s => - { - return false; - }, - mddiffStream, - new MemoryStream(), // il stream - new MemoryStream() // pdb diff stream - ); - - baselineImage.Seek(0, SeekOrigin.Begin); - PEReader baselineReader = new PEReader(baselineImage); - return new object[] - { - nameof(DeltaAssembly1), - baselineReader, - new Memory[] - { - mddiffStream.ToArray() - } - }; - } - - static SemanticEdit CreateSemanticEdit(SemanticEditKind editKind, Compilation baseline, Compilation diff, Func findSymbol) - { - return new SemanticEdit(editKind, findSymbol(baseline), findSymbol(diff)); - } - } - - [Theory] - [MemberData(nameof(CoreFrameworkLibraries))] - public void ImportAPIs_Core(string filename, PEReader managedLibrary) => ImportAPIs(filename, managedLibrary); - - [WindowsOnlyTheory] - [MemberData(nameof(Net20FrameworkLibraries))] - public void ImportAPIs_Net20(string filename, PEReader managedLibrary) => ImportAPIs(filename, managedLibrary); - - [WindowsOnlyTheory] - [MemberData(nameof(Net40FrameworkLibraries))] - public void ImportAPIs_Net40(string filename, PEReader managedLibrary) => ImportAPIs(filename, managedLibrary); - - [Theory] - [MemberData(nameof(AssembliesWithDelta))] - public unsafe void ImportAPIs_AssembliesWithAppliedDeltas(string filename, PEReader deltaBaseline, IList> diffs) - { - Debug.WriteLine($"{nameof(ImportAPIs_AssembliesWithAppliedDeltas)} - {filename}"); - using var _ = deltaBaseline; - PEMemoryBlock block = deltaBaseline.GetMetadata(); - - void*[] deltaImagePointers = new void*[diffs.Count]; - int[] deltaImageLengths = new int[diffs.Count]; - MemoryHandle[] handles = new MemoryHandle[diffs.Count]; - - for (int i = 0; i < diffs.Count; i++) - { - handles[i] = diffs[i].Pin(); - deltaImagePointers[i] = handles[i].Pointer; - deltaImageLengths[i] = diffs[i].Length; - } - - try - { - fixed (void** deltaImagePointersPtr = deltaImagePointers) - fixed (int* deltaImageLengthsPtr = deltaImageLengths) - { - _importAPIsIndirectionTables( - block.Pointer, - block.Length, - deltaImagePointersPtr, - deltaImageLengthsPtr, - diffs.Count).Check(); - } - } - finally - { - for (int i = 0; i < diffs.Count; i++) - { - handles[i].Dispose(); - } - } - } - - private void ImportAPIs(string filename, PEReader managedLibrary) - { - Debug.WriteLine($"{nameof(ImportAPIs)} - {filename}"); - using var _ = managedLibrary; - PEMemoryBlock block = managedLibrary.GetMetadata(); - _importAPIs(block.Pointer, block.Length).Check(); - } - - private void ImportAPIs(string filename, MetadataReader managedLibrary) - { - Debug.WriteLine($"{nameof(ImportAPIs)} - {filename}"); - _importAPIs(managedLibrary.MetadataPointer, managedLibrary.MetadataLength).Check(); - } - - /// - /// These APIs are very expensive to run on all managed libraries. This library only runs - /// them on the system corelibs and only on a reduced selection of the tokens. - /// - [Theory] - [MemberData(nameof(AllCoreLibs))] - public void LongRunningAPIs(string filename, PEReader managedLibrary) - { - Debug.WriteLine($"{nameof(LongRunningAPIs)} - {filename}"); - using var _lib = managedLibrary; - PEMemoryBlock block = managedLibrary.GetMetadata(); - - _longRunningAPIs(block.Pointer, block.Length).Check(); - } - - [Fact] - public void FindAPIs() - { - var dir = Path.GetDirectoryName(typeof(ImportTests).Assembly.Location)!; - var tgtAssembly = Path.Combine(dir, "Regression.TargetAssembly.dll"); - using PEReader managedLibrary = new(File.OpenRead(tgtAssembly)); - PEMemoryBlock block = managedLibrary.GetMetadata(); - - _findAPIs(block.Pointer, block.Length).Check(); - } - } -} \ No newline at end of file diff --git a/test/Regression.UnitTests/Properties/launchSettings.json b/test/Regression.UnitTests/Properties/launchSettings.json deleted file mode 100644 index 7fa32111..00000000 --- a/test/Regression.UnitTests/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "Regression.UnitTests": { - "commandName": "Project", - "nativeDebugging": true - } - } -} \ No newline at end of file diff --git a/test/Regression.UnitTests/Regression.UnitTests.csproj b/test/Regression.UnitTests/Regression.UnitTests.csproj deleted file mode 100644 index 249921d9..00000000 --- a/test/Regression.UnitTests/Regression.UnitTests.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - - $(TargetFrameworkLatest) - enable - enable - true - false - - - - - - - - - - - - - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - diff --git a/test/Regression.UnitTests/SymReaderTests.cs b/test/Regression.UnitTests/SymReaderTests.cs deleted file mode 100644 index a14d8fcc..00000000 --- a/test/Regression.UnitTests/SymReaderTests.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Runtime.InteropServices; - -using Common; -using Xunit; -using Xunit.Abstractions; - -namespace Regression.UnitTests -{ - public unsafe class SymReaderTests - { - private delegate* unmanaged _symReaderAPIs; - - public SymReaderTests(ITestOutputHelper outputHelper) - { - Log = outputHelper; - nint mod = NativeLibrary.Load(Path.Combine(AppContext.BaseDirectory, Native.Path)); - _symReaderAPIs = (delegate* unmanaged)NativeLibrary.GetExport(mod, "UnitSymReaderAPIs"); - } - - private ITestOutputHelper Log { get; } - - [Fact] - public void SymReaderAPIs() => _symReaderAPIs(null, 0).Check(); - } -} \ No newline at end of file diff --git a/test/regnative/CMakeLists.txt b/test/regnative/CMakeLists.txt deleted file mode 100644 index 7a95ea43..00000000 --- a/test/regnative/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -set(SOURCES - ./perf.cpp - ./regnative.cpp - ./unit_cor.cpp - ./unit_corsym.cpp -) - -add_library(regnative - SHARED - ${SOURCES} -) - -target_link_libraries(regnative - dnmd::interfaces - dncp::dncp) - -if(NOT MSVC) - target_link_libraries(regnative dncp::winhdrs) -endif() - -install(TARGETS regnative - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib - RUNTIME DESTINATION bin) \ No newline at end of file diff --git a/test/regnative/perf.cpp b/test/regnative/perf.cpp deleted file mode 100644 index 86c49ec3..00000000 --- a/test/regnative/perf.cpp +++ /dev/null @@ -1,220 +0,0 @@ -#include "regnative.hpp" - -namespace -{ - void const* g_data; - uint32_t g_dataLen; - - IMetaDataDispenser* g_baselineDisp; - IMetaDataDispenser* g_currentDisp; - - IMetaDataImport* g_baselineImport; - IMetaDataImport* g_currentImport; - - HRESULT CreateImport(IMetaDataDispenser* disp, IMetaDataImport** import) - { - assert(disp != nullptr && import != nullptr); - return disp->OpenScopeOnMemory( - g_data, - g_dataLen, - CorOpenFlags::ofReadOnly, - IID_IMetaDataImport, - reinterpret_cast(import)); - } - - HRESULT EnumTypeDefs(IMetaDataImport* import) - { - assert(import != nullptr); - HRESULT hr; - HCORENUM hcorenum = {}; - uint32_t buffer[1]; - uint32_t count; - while (S_OK == (hr = import->EnumTypeDefs(&hcorenum, buffer, ARRAY_SIZE(buffer), (ULONG*)&count)) - && count != 0) - { - } - return hr; - } - - HRESULT GetScopeProps(IMetaDataImport* import) - { - assert(import != nullptr); - WCHAR name[512]; - ULONG nameLen; - GUID mvid; - return import->GetScopeProps(name, ARRAY_SIZE(name), &nameLen, &mvid); - } - - HRESULT EnumUserStrings(IMetaDataImport* import) - { - assert(import != nullptr); - HRESULT hr; - HCORENUM hcorenum = {}; - uint32_t buffer[1]; - uint32_t count; - while (S_OK == (hr = import->EnumUserStrings(&hcorenum, buffer, ARRAY_SIZE(buffer), (ULONG*)&count)) - && count != 0) - { - } - return hr; - } - - HRESULT GetCustomAttributeByName(IMetaDataImport* import) - { - assert(import != nullptr); - mdToken tk = TokenFromRid(2, mdtTypeDef); - void const* data; - uint32_t dataLen; - return import->GetCustomAttributeByName(tk, W("NotAnAttribute"), &data, (ULONG*)&dataLen); - } -} - -EXPORT -HRESULT PerfInitialize( - void const* data, - uint32_t dataLen, - IMetaDataDispenser* baseline) -{ - if (data == nullptr || baseline == nullptr) - return E_INVALIDARG; - - g_data = data; - g_dataLen = dataLen; - - (void)baseline->AddRef(); - g_baselineDisp = baseline; - - HRESULT hr; - if (FAILED(hr = CreateImport(g_baselineDisp, &g_baselineImport))) - return hr; - - if (FAILED(hr = GetDispenser(IID_IMetaDataDispenser, reinterpret_cast(&g_currentDisp)))) - return hr; - - if (FAILED(hr = CreateImport(g_currentDisp, &g_currentImport))) - return hr; - - return S_OK; -} - -EXPORT -HRESULT PerfBaselineCreateImport(int iter) -{ - HRESULT hr; - IMetaDataImport* import; - for (int i = 0; i < iter; ++i) - { - if (FAILED(hr = CreateImport(g_baselineDisp, &import))) - return hr; - (void)import->Release(); - } - return S_OK; -} - -EXPORT -HRESULT PerfCurrentCreateImport(int iter) -{ - HRESULT hr; - IMetaDataImport* import; - for (int i = 0; i < iter; ++i) - { - if (FAILED(hr = CreateImport(g_currentDisp, &import))) - return hr; - (void)import->Release(); - } - return S_OK; -} - -EXPORT -HRESULT PerfBaselineEnumTypeDefs(int iter) -{ - HRESULT hr; - for (int i = 0; i < iter; ++i) - { - if (FAILED(hr = EnumTypeDefs(g_baselineImport))) - return hr; - } - return S_OK; -} - -EXPORT -HRESULT PerfCurrentEnumTypeDefs(int iter) -{ - HRESULT hr; - for (int i = 0; i < iter; ++i) - { - if (FAILED(hr = EnumTypeDefs(g_currentImport))) - return hr; - } - return S_OK; -} - -EXPORT -HRESULT PerfBaselineGetScopeProps(int iter) -{ - HRESULT hr; - for (int i = 0; i < iter; ++i) - { - if (FAILED(hr = GetScopeProps(g_baselineImport))) - return hr; - } - return S_OK; -} - -EXPORT -HRESULT PerfCurrentGetScopeProps(int iter) -{ - HRESULT hr; - for (int i = 0; i < iter; ++i) - { - if (FAILED(hr = GetScopeProps(g_currentImport))) - return hr; - } - return S_OK; -} - -EXPORT -HRESULT PerfBaselineEnumUserStrings(int iter) -{ - HRESULT hr; - for (int i = 0; i < iter; ++i) - { - if (FAILED(hr = EnumUserStrings(g_baselineImport))) - return hr; - } - return S_OK; -} - -EXPORT -HRESULT PerfCurrentEnumUserStrings(int iter) -{ - HRESULT hr; - for (int i = 0; i < iter; ++i) - { - if (FAILED(hr = EnumUserStrings(g_currentImport))) - return hr; - } - return S_OK; -} - -EXPORT -HRESULT PerfBaselineGetCustomAttributeByName(int iter) -{ - for (int i = 0; i < iter; ++i) - { - if (S_FALSE != GetCustomAttributeByName(g_baselineImport)) - return E_FAIL; - } - return S_OK; -} - -EXPORT -HRESULT PerfCurrentGetCustomAttributeByName(int iter) -{ - for (int i = 0; i < iter; ++i) - { - if (S_FALSE != GetCustomAttributeByName(g_currentImport)) - return E_FAIL; - } - return S_OK; -} diff --git a/test/regnative/regnative.cpp b/test/regnative/regnative.cpp deleted file mode 100644 index c6f5ad6b..00000000 --- a/test/regnative/regnative.cpp +++ /dev/null @@ -1,26 +0,0 @@ - -#include "regnative.hpp" - -using Assert::Violation; - -Violation::Violation(char const* source, size_t line, char const* funcName, std::string const& msg) -{ - std::stringstream ss; - ss << source << "(" << line << "):'" << msg << "' in " << funcName << "."; - _message = ss.str(); -} - -void Assert::_True(bool result, char const* source, size_t line, char const* funcName) -{ - if (!result) - throw Violation{ source, line, funcName, "false" }; -} - -TestResult ConvertViolation(Assert::Violation const& v) -{ - auto msg = v.message(); - char* block = (char*)malloc(msg.length() + 1); - msg.copy(block, msg.length()); - block[msg.length()] = '\0'; - return { TestState::Fail, block, &free }; -} diff --git a/test/regnative/regnative.hpp b/test/regnative/regnative.hpp deleted file mode 100644 index 32eb22e4..00000000 --- a/test/regnative/regnative.hpp +++ /dev/null @@ -1,136 +0,0 @@ -#include -#include -#include -#include - -#include -#include - -#ifdef _MSC_VER -#define EXPORT extern "C" __declspec(dllexport) -#else -#define EXPORT extern "C" __attribute__((__visibility__("default"))) -#endif // !_MSC_VER - -#include -#include -#include -#include - -namespace Assert -{ - class Violation : public std::exception - { - std::string _message; - public: - Violation(char const* source, size_t line, char const* funcName, std::string const& msg); - - std::string const& message() const noexcept - { - return _message; - } - - char const* what() const noexcept override - { - return _message.c_str(); - } - }; - - void _True(bool result, char const* source, size_t line, char const* funcName); - - template - void _Equal(T const& expected, T const& actual, char const* source, size_t line, char const* funcName) - { - if (expected != actual) - { - std::stringstream ss; - ss << std::hex << expected << " != " << actual << std::endl; - throw Violation{ source, line, funcName, ss.str() }; - } - } - - template - T _Equal(T&& expected, T const& actual, char const* source, size_t line, char const* funcName) - { - _Equal(expected, actual, source, line, funcName); - return std::move(expected); - } - - template - std::vector _Equal(std::vector&& expected, std::vector const& actual, char const* source, size_t line, char const* funcName) - { - if (expected != actual) - { - std::stringstream ss; - if (expected.size() != actual.size()) - { - ss << "Size mismatch: " << expected.size() << " != " << actual.size() << "\n"; - ss << std::hex; - char const* d = "Expect: "; - for (auto e : expected) - { - ss << d << e; - d = ", "; - } - ss << "\n"; - d = "Actual: "; - for (auto a : actual) - { - ss << d << a; - d = ", "; - } - ss << "\n"; - } - else - { - auto iters = std::mismatch(std::begin(expected), std::end(expected), std::begin(actual), std::end(actual)); - if (iters.first != std::end(expected) || iters.second != std::end(actual)) - { - ss << "Element at " << std::distance(std::begin(expected), iters.first) << " mismatch: " - << std::hex << *iters.first << " != " << *iters.second; - } - } - throw Violation{ source, line, funcName, ss.str() }; - } - return std::move(expected); - } -} - -#define ASSERT_TRUE(e) Assert::_True((e), __FILE__, __LINE__, __func__) -#define ASSERT_EQUAL(e, a) Assert::_Equal((e), (a), __FILE__, __LINE__, __func__) -#define ASSERT_AND_RETURN(e, a) Assert::_Equal((e), (a), __FILE__, __LINE__, __func__) - -enum class TestState : uint32_t -{ - Fail = 0, - Pass, -}; - -struct TestResult final -{ - TestState State; - char const* FailureMessage; - void(*Free)(void*); -}; - -TestResult ConvertViolation(Assert::Violation const& v); - -#define BEGIN_TEST()\ - try \ - { \ - -#define END_TEST()\ - } \ - catch (Assert::Violation const& v) \ - { \ - return ConvertViolation(v); \ - } \ - return { TestState::Pass, nullptr, nullptr }; - -// Used when the test calls another test case. -#define END_DELEGATING_TEST()\ - } \ - catch (Assert::Violation const& v) \ - { \ - return ConvertViolation(v); \ - } diff --git a/test/regnative/unit_corsym.cpp b/test/regnative/unit_corsym.cpp deleted file mode 100644 index c90b61b5..00000000 --- a/test/regnative/unit_corsym.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "regnative.hpp" - -EXPORT -TestResult UnitSymReaderAPIs(void const* data, uint32_t dataLen) -{ - UNREFERENCED_PARAMETER(data); - UNREFERENCED_PARAMETER(dataLen); - BEGIN_TEST() - END_TEST() -} \ No newline at end of file diff --git a/test/regpal/CMakeLists.txt b/test/regpal/CMakeLists.txt new file mode 100644 index 00000000..2d56b49f --- /dev/null +++ b/test/regpal/CMakeLists.txt @@ -0,0 +1,17 @@ +set(SOURCES + ./pal.cpp +) + +add_library(regpal STATIC ${SOURCES}) + +target_link_libraries(regpal PUBLIC nethost dncp::dncp) + +if (NOT WIN32) + target_link_libraries(regpal PUBLIC dncp::winhdrs) +endif() + +if (NOT WIN32 AND NOT APPLE) + target_link_libraries(regpal PUBLIC dl) +endif() + +target_include_directories(regpal PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) \ No newline at end of file diff --git a/test/regpal/pal.cpp b/test/regpal/pal.cpp new file mode 100644 index 00000000..353c3f9c --- /dev/null +++ b/test/regpal/pal.cpp @@ -0,0 +1,186 @@ +#define DNCP_DEFINE_GUID +#include "pal.hpp" + +#include +#include + +#ifndef BUILD_WINDOWS +#include +#endif + +#include +#include +#include +#include + +using std::filesystem::path; + +#ifdef BUILD_WINDOWS +#define W_StringView(str) std::wstring_view{L##str} +#else +#define W_StringView(str) std::string_view{str} +#endif + +namespace +{ + void* LoadModule(path path) + { +#ifdef BUILD_WINDOWS + return ::LoadLibraryW(path.c_str()); +#else + return ::dlopen(path.c_str(), RTLD_LAZY); +#endif + } + + void* GetSymbol(void* module, char const* name) + { +#ifdef BUILD_WINDOWS + return ::GetProcAddress((HMODULE)module, name); +#else + return ::dlsym(module, name); +#endif + } + + using MetaDataGetDispenser = HRESULT(STDMETHODCALLTYPE*)(REFCLSID, REFIID, LPVOID*); + + using CoreCLRInitialize = int(STDMETHODCALLTYPE*)( + char const* exePath, + char const* appDomainFriendlyName, + int propertyCount, + char const** propertyKeys, + char const** propertyValues, + void** hostHandle, + uint32_t* domainId); + + MetaDataGetDispenser LoadGetDispenser() + { + auto coreClrPath = pal::GetCoreClrPath(); + if (coreClrPath.empty()) + { + std::cerr << "Failed to get coreclr path" << std::endl; + return nullptr; + } + + auto mod = LoadModule(coreClrPath); + if (mod == nullptr) + { + std::cerr << "Failed to load metadata baseline module: " << coreClrPath << std::endl; + return nullptr; + } +#ifndef BUILD_WINDOWS + // On non-Windows, the metadata APIs in CoreCLR don't work until the PAL is initialized. + // Initialize the runtime just enough to load the PAL. + auto init = (CoreCLRInitialize)GetSymbol(mod, "coreclr_initialize"); + if (init == nullptr) + { + std::cerr << "Failed to find coreclr_initialize in module: " << coreClrPath << std::endl; + return nullptr; + } + + char const* propertyKeys[] = { "TRUSTED_PLATFORM_ASSEMBLIES" }; + char const* propertyValues[] = { coreClrPath.c_str() }; + init("regpal", "regpal", 1, propertyKeys, propertyValues, nullptr, nullptr); +#endif + + auto getDispenser = (MetaDataGetDispenser)GetSymbol(mod, "MetaDataGetDispenser"); + if (getDispenser == nullptr) + { + std::cerr << "Failed to find MetaDataGetDispenser in module: " << coreClrPath << std::endl; + return nullptr; + } + + return getDispenser; + } + + MetaDataGetDispenser GetDispenser = LoadGetDispenser(); +} + +HRESULT pal::GetBaselineMetadataDispenser(IMetaDataDispenser** dispenser) +{ + if (GetDispenser == nullptr) + { + return E_FAIL; + } + + return GetDispenser(CLSID_CorMetaDataDispenser, IID_IMetaDataDispenser, (void**)dispenser); +} + +bool pal::ReadFile(path path, malloc_span& b) +{ + // Read in the entire file + std::ifstream fd{ path, std::ios::binary | std::ios::in }; + if (!fd) + return false; + + size_t size = std::filesystem::file_size(path); + if (size == 0) + return false; + + b = { (uint8_t*)std::malloc(size), size }; + + fd.read((char*)(uint8_t*)b, b.size()); + return true; +} + +path pal::GetCoreClrPath() +{ + int result = 0; + size_t bufferSize = 4096; + std::unique_ptr hostfxr_path; + do + { + hostfxr_path.reset(new char_t[bufferSize]); + result = get_hostfxr_path(hostfxr_path.get(), &bufferSize, nullptr); + } while (result != 0); + + path hostFxrPath = hostfxr_path.get(); + void* hostfxrModule = LoadModule(hostfxr_path.get()); + if (hostfxrModule == nullptr) + { + std::cerr << "Failed to load hostfxr module: " << hostFxrPath << std::endl; + return {}; + } + + + // The hostfxr path is in the form: /host/fxr//hostfxr.dll + // We need to get the dotnet root, which is 3 levels up + // We need to do this because hostfxr_get_dotnet_environment_info only returns information + // for a globally-installed dotnet if we don't pass a path to the dotnet root. + // The macOS machines on GitHub Actions don't have dotnet globally installed. + path dotnetRoot = hostFxrPath.parent_path().parent_path().parent_path().parent_path(); + + path coreClrPath = {}; + auto getDotnetEnvironmentInfo = (hostfxr_get_dotnet_environment_info_fn)GetSymbol(hostfxrModule, "hostfxr_get_dotnet_environment_info"); + if (getDotnetEnvironmentInfo( + dotnetRoot.c_str(), + nullptr, + [](const hostfxr_dotnet_environment_info* info, void* result_context) + { + path& coreClrPath = *(path*)result_context; + for (size_t i = 0; i < info->framework_count; ++i) + { + if (info->frameworks[i].name == W_StringView("Microsoft.NETCore.App")) + { + coreClrPath = info->frameworks[i].path; + coreClrPath /= info->frameworks[i].version; +#ifdef BUILD_WINDOWS + coreClrPath /= "coreclr.dll"; +#elif BUILD_MACOS + coreClrPath /= "libcoreclr.dylib"; +#elif BUILD_UNIX + coreClrPath /= "libcoreclr.so"; +#else +#error "Unknown platform, cannot determine name for CoreCLR executable" +#endif + } + } + }, + &coreClrPath + ) != 0) + { + std::cerr << "Failed to get dotnet environment info" << std::endl; + return {}; + } + + return coreClrPath; +} diff --git a/test/regpal/pal.hpp b/test/regpal/pal.hpp new file mode 100644 index 00000000..7ac13e16 --- /dev/null +++ b/test/regpal/pal.hpp @@ -0,0 +1,18 @@ +#ifndef _TEST_REGPAL_PAL_H_ +#define _TEST_REGPAL_PAL_H_ + +#include +#include + +#include +#include +#include + +namespace pal +{ + std::filesystem::path GetCoreClrPath(); + HRESULT GetBaselineMetadataDispenser(IMetaDataDispenser** dispenser); + bool ReadFile(std::filesystem::path path, malloc_span& b); +} + +#endif // !_TEST_REGPAL_PAL_H_ \ No newline at end of file diff --git a/test/regperf/CMakeLists.txt b/test/regperf/CMakeLists.txt new file mode 100644 index 00000000..46541ae8 --- /dev/null +++ b/test/regperf/CMakeLists.txt @@ -0,0 +1,22 @@ +set(SOURCES + ./perf.cpp +) + +add_executable(regperf + ${SOURCES} +) + +target_link_libraries(regperf + dnmd::interfaces + dncp::dncp + regpal) + +if(NOT MSVC) + target_link_libraries(regperf dncp::winhdrs) +endif() + + +target_link_libraries(regperf benchmark::benchmark) + +install(DIRECTORY ${RegressionLocatorDirectory} DESTINATION bin) +install(TARGETS regperf DESTINATION bin) \ No newline at end of file diff --git a/test/regperf/perf.cpp b/test/regperf/perf.cpp new file mode 100644 index 00000000..518a177b --- /dev/null +++ b/test/regperf/perf.cpp @@ -0,0 +1,218 @@ +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#define EXPORT extern "C" __declspec(dllexport) +#else +#define EXPORT extern "C" __attribute__((__visibility__("default"))) +#endif // !_MSC_VER + +#define RETURN_IF_FAILED(x) { auto hr = x; if (FAILED(hr)) return hr; } + +namespace +{ + void const* g_data; + uint32_t g_dataLen; + + IMetaDataDispenser* g_baselineDisp; + IMetaDataDispenser* g_currentDisp; + + IMetaDataImport* g_baselineImport; + IMetaDataImport* g_currentImport; + + HRESULT CreateImport(IMetaDataDispenser* disp, IMetaDataImport** import) + { + assert(disp != nullptr && import != nullptr); + return disp->OpenScopeOnMemory( + g_data, + g_dataLen, + CorOpenFlags::ofReadOnly, + IID_IMetaDataImport, + reinterpret_cast(import)); + } + + HRESULT EnumTypeDefs(IMetaDataImport* import) + { + assert(import != nullptr); + HRESULT hr; + HCORENUM hcorenum = {}; + uint32_t buffer[1]; + uint32_t count; + while (S_OK == (hr = import->EnumTypeDefs(&hcorenum, buffer, ARRAY_SIZE(buffer), (ULONG*)&count)) + && count != 0) + { + } + return hr; + } + + HRESULT GetScopeProps(IMetaDataImport* import) + { + assert(import != nullptr); + WCHAR name[512]; + ULONG nameLen; + GUID mvid; + return import->GetScopeProps(name, ARRAY_SIZE(name), &nameLen, &mvid); + } + + HRESULT EnumUserStrings(IMetaDataImport* import) + { + assert(import != nullptr); + HRESULT hr; + HCORENUM hcorenum = {}; + uint32_t buffer[1]; + uint32_t count; + while (S_OK == (hr = import->EnumUserStrings(&hcorenum, buffer, ARRAY_SIZE(buffer), (ULONG*)&count)) + && count != 0) + { + } + return hr; + } + + HRESULT GetCustomAttributeByName(IMetaDataImport* import) + { + assert(import != nullptr); + mdToken tk = TokenFromRid(2, mdtTypeDef); + void const* data; + uint32_t dataLen; + return import->GetCustomAttributeByName(tk, W("NotAnAttribute"), &data, (ULONG*)&dataLen); + } +} + +HRESULT PerfInitialize( + void const* data, + uint32_t dataLen) +{ + if (data == nullptr) + return E_INVALIDARG; + + g_data = data; + g_dataLen = dataLen; + + RETURN_IF_FAILED(CreateImport(g_baselineDisp, &g_baselineImport)); + + RETURN_IF_FAILED(CreateImport(g_currentDisp, &g_currentImport)); + + return S_OK; +} + +void CreateImport(benchmark::State& state, IMetaDataDispenser* disp) +{ + IMetaDataImport* import; + for (auto _ : state) + { + if (SUCCEEDED(CreateImport(disp, &import))) + (void)import->Release(); + } +} + +BENCHMARK_CAPTURE(CreateImport, BaselineCreateImport, g_baselineDisp); +BENCHMARK_CAPTURE(CreateImport, CurrentCreateImport, g_currentDisp); + +#define IMPORT_BENCHMARK(func) \ + BENCHMARK_CAPTURE(func, Baseline##func, g_baselineImport); \ + BENCHMARK_CAPTURE(func, Current##func, g_currentImport); + +void EnumTypeDefs(benchmark::State& state, IMetaDataImport* import) +{ + for (auto _ : state) + { + if (FAILED(EnumTypeDefs(import))) + { + state.SkipWithError("Failed to enumerate typedefs"); + } + } +} + +IMPORT_BENCHMARK(EnumTypeDefs); + +void GetScopeProps(benchmark::State& state, IMetaDataImport* import) +{ + HRESULT hr; + for (auto _ : state) + { + if (FAILED(hr = GetScopeProps(import))) + { + state.SkipWithError("Failed to get scope props"); + } + } +} + +IMPORT_BENCHMARK(GetScopeProps); + +void EnumUserStrings(benchmark::State& state, IMetaDataImport* import) +{ + HRESULT hr; + for (auto _ : state) + { + if (FAILED(hr = EnumUserStrings(import))) + { + state.SkipWithError("Failed to enumerate user strings"); + } + } +} + +IMPORT_BENCHMARK(EnumUserStrings); + +void EnumCustomAttributeByName(benchmark::State& state, IMetaDataImport* import) +{ + HRESULT hr; + for (auto _ : state) + { + if (FAILED(hr = GetCustomAttributeByName(import))) + { + state.SkipWithError("Failed to get custom attributes"); + } + } +} + +IMPORT_BENCHMARK(EnumCustomAttributeByName); + +int main(int argc, char** argv) +{ + RETURN_IF_FAILED(pal::GetBaselineMetadataDispenser(&g_baselineDisp)); + RETURN_IF_FAILED(GetDispenser(IID_IMetaDataDispenser, reinterpret_cast(&g_currentDisp))); + auto coreClrPath = pal::GetCoreClrPath(); + if (coreClrPath.empty()) + { + std::cerr << "Failed to get coreclr path" << std::endl; + return -1; + } + + std::filesystem::path dataImagePath = std::move(coreClrPath); + dataImagePath.replace_filename("System.Private.CoreLib.dll"); + + std::cerr << "Loading System.Private.CoreLib from: " << dataImagePath << std::endl; + + malloc_span dataImage; + if (!pal::ReadFile(dataImagePath, dataImage)) + { + std::cerr << "Failed to read System.Private.CoreLib" << std::endl; + return EXIT_FAILURE; + } + + if (!get_metadata_from_pe(dataImage)) + { + std::cerr << "Failed to get metadata from System.Private.CoreLib" << std::endl; + return EXIT_FAILURE; + } + + RETURN_IF_FAILED(PerfInitialize( + dataImage, + (uint32_t)dataImage.size())); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/test/regtest/CMakeLists.txt b/test/regtest/CMakeLists.txt new file mode 100644 index 00000000..5eed48dc --- /dev/null +++ b/test/regtest/CMakeLists.txt @@ -0,0 +1,55 @@ +set(HEADERS + ./baseline.h + ./fixtures.h + ./asserts.h +) +set(SOURCES + ./main.cpp + ./discovery.cpp + ./metadata.cpp +) + +add_executable(regtest ${SOURCES} ${HEADERS}) +target_link_libraries(regtest PRIVATE dnmd::interfaces gtest gmock dncp::dncp regpal) # Reference gmock for better collection assertions +set_target_properties(regtest PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON) # Require C++17 for the tests so we can use std::filesystem. + +target_compile_definitions(regtest PRIVATE COM_NO_WINDOWS_H) + +if (NOT WIN32) + target_link_libraries(regtest PRIVATE dncp::winhdrs) +endif() + +if (WIN32) + FetchContent_Declare( + wil + GIT_REPOSITORY + https://github.com/microsoft/wil.git + GIT_TAG + v1.0.231216.1 + ) + + set(WIL_BUILD_PACKAGING OFF CACHE BOOL "" FORCE) + set(WIL_BUILD_TESTS OFF CACHE BOOL "" FORCE) + + FetchContent_MakeAvailable(wil) + target_link_libraries(regtest PRIVATE WIL) +endif() + +add_custom_target(Regression.TargetAssembly + dotnet build ${CMAKE_CURRENT_SOURCE_DIR}/../Regression.TargetAssembly/Regression.TargetAssembly.ilproj -c $ + BYPRODUCTS ${CMAKE_BINARY_DIR}/managed/bin/Regression.TargetAssembly/$>/Regression.TargetAssembly.dll + COMMENT "Building Regression.TargetAssembly.dll" + ) + +add_dependencies(regtest Regression.TargetAssembly) + +add_custom_command(TARGET regtest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ $ + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/managed/bin/Regression.TargetAssembly/$>/Regression.TargetAssembly.dll $) + +if(WIN32) + add_custom_command(TARGET regtest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ $) +endif() + +gtest_discover_tests(regtest) \ No newline at end of file diff --git a/test/regtest/asserts.h b/test/regtest/asserts.h new file mode 100644 index 00000000..0cc2c4b1 --- /dev/null +++ b/test/regtest/asserts.h @@ -0,0 +1,25 @@ +#ifndef _TEST_REGTEST_ASSERTS_H_ +#define _TEST_REGTEST_ASSERTS_H_ + +#include +#include + +#define EXPECT_THAT_AND_RETURN(a, match) ([&](){ auto&& _actual = (a); EXPECT_THAT(_actual, match); return _actual; }()) + +template +void AssertEqualAndSet(T& result, T&& expected, T&& actual) +{ + ASSERT_EQ(actual, expected); + result = std::move(actual); +} + +template +void AssertEqualAndSet(std::vector& result, std::vector&& expected, std::vector&& actual) +{ + ASSERT_THAT(actual, ::testing::ElementsAreArray(expected)); + result = std::move(actual); +} + +#define ASSERT_EQUAL_AND_SET(result, expected, actual) ASSERT_NO_FATAL_FAILURE(AssertEqualAndSet(result, expected, actual)) + +#endif // !_TEST_REGTEST_ASSERTS_H_ diff --git a/test/regtest/baseline.h b/test/regtest/baseline.h new file mode 100644 index 00000000..036b3aaf --- /dev/null +++ b/test/regtest/baseline.h @@ -0,0 +1,14 @@ +#ifndef _TEST_REGTEST_BASELINE_H_ +#define _TEST_REGTEST_BASELINE_H_ + +#include +#include + +namespace TestBaseline +{ + extern dncp::com_ptr Metadata; + extern dncp::com_ptr DeltaMetadataBuilder; + extern dncp::com_ptr Symbol; +} + +#endif // !_TEST_REGTEST_BASELINE_H_ \ No newline at end of file diff --git a/test/regtest/discovery.cpp b/test/regtest/discovery.cpp new file mode 100644 index 00000000..4b30b48b --- /dev/null +++ b/test/regtest/discovery.cpp @@ -0,0 +1,337 @@ +#include "fixtures.h" +#include "baseline.h" +#include +#include +#include +#include + +#ifdef BUILD_WINDOWS +#include +#else +#define THROW_IF_FAILED(x) do { HRESULT hr = (x); if (FAILED(hr)) { throw std::runtime_error("Failed HR when running '" #x "'"); } } while (false) +#endif + +#include + +#ifdef BUILD_WINDOWS +#define DNNE_API_OVERRIDE __declspec(dllimport) +#endif + +namespace +{ + std::filesystem::path baselinePath; + std::string regressionAssemblyPath; + + template + struct OnExit + { + T callback; + ~OnExit() + { + callback(); + } + }; + + template + [[nodiscard]] OnExit on_scope_exit(T callback) + { + return { callback }; + } + + malloc_span ReadMetadataFromFile(std::filesystem::path path) + { + malloc_span b; + if (!pal::ReadFile(path, b) + || !get_metadata_from_pe(b)) + { + return {}; + } + + return b; + } + + // Create an image with indirection tables, like an image that has had a delta applied to it. + // This is used to test that the importer can handle out-of-order rows. + // This image is intentinally minimal as our other regression tests cover more full-filled metadata scenarios. + malloc_span CreateImageWithIndirectionTables() + { + std::cout << "Creating image with indirection tables" << std::endl; + dncp::com_ptr image; + THROW_IF_FAILED(TestBaseline::DeltaMetadataBuilder->DefineScope(CLSID_CorMetaDataRuntime, 0, IID_IMetaDataEmit, (IUnknown**)&image)); + + THROW_IF_FAILED(image->SetModuleProps(W("IndirectionTables.dll"))); + + dncp::com_ptr assemblyEmit; + THROW_IF_FAILED(image->QueryInterface(IID_IMetaDataAssemblyEmit, (void**)&assemblyEmit)); + + ASSEMBLYMETADATA assemblyMetadata = { 0 }; + + mdAssemblyRef systemRuntimeRef; + THROW_IF_FAILED(assemblyEmit->DefineAssemblyRef(nullptr, 0, W("System.Runtime"), &assemblyMetadata, nullptr, 0, 0, &systemRuntimeRef)); + + mdTypeRef systemObject; + THROW_IF_FAILED(image->DefineTypeRefByName(systemRuntimeRef, W("System.Object"), &systemObject)); + + // Define two types so we can define out-of-order rows. + mdTypeDef type1; + THROW_IF_FAILED(image->DefineTypeDef(W("Type1"), tdSealed, systemObject, nullptr, &type1)); + + mdTypeDef type2; + THROW_IF_FAILED(image->DefineTypeDef(W("Type2"), tdSealed, systemObject, nullptr, &type2)); + + // Define a signature that has two parameters and a return type. + // This will provide us with enough structure to define out-of-order Param rows. + std::array signature = { (uint8_t)IMAGE_CEE_CS_CALLCONV_DEFAULT, (uint8_t)0x02, (uint8_t)ELEMENT_TYPE_I4, (uint8_t)ELEMENT_TYPE_I2, (uint8_t)ELEMENT_TYPE_I8}; + + mdMethodDef method1; + THROW_IF_FAILED(image->DefineMethod(type1, W("Method1"), 0, signature.data(), (ULONG)signature.size(), 0, 0, &method1)); + + mdParamDef param1; + THROW_IF_FAILED(image->DefineParam(method1, 2, W("Param2"), 0, 0, nullptr, 0, ¶m1)); + + // Define the Param row for the first parameter after we've already defined the second parameter. + mdParamDef paramOutOfOrder; + THROW_IF_FAILED(image->DefineParam(method1, 1, W("Param1"), 0, 0, nullptr, 0, ¶mOutOfOrder)); + + mdMethodDef method2; + THROW_IF_FAILED(image->DefineMethod(type2, W("Method2"), 0, signature.data(), (ULONG)signature.size(), 0, 0, &method2)); + + // Define a method on the first type after we've already defined a method on the second type. + mdMethodDef methodOutOfOrder; + THROW_IF_FAILED(image->DefineMethod(type1, W("MethodOutOfOrder"), 0, signature.data(), (ULONG)signature.size(), 0, 0, &methodOutOfOrder)); + + std::array fieldSignature = { (uint8_t)IMAGE_CEE_CS_CALLCONV_FIELD, (uint8_t)ELEMENT_TYPE_I4 }; + + mdFieldDef field1; + THROW_IF_FAILED(image->DefineField(type1, W("Field1"), 0, fieldSignature.data(), (ULONG)fieldSignature.size(), 0, nullptr, 0, &field1)); + + mdFieldDef field2; + THROW_IF_FAILED(image->DefineField(type2, W("Field2"), 0, fieldSignature.data(), (ULONG)fieldSignature.size(), 0, nullptr, 0, &field2)); + + // Define a field on the first type after we've already defined a field on the second type. + mdFieldDef fieldOutOfOrder; + THROW_IF_FAILED(image->DefineField(type1, W("FieldOutOfOrder"), 0, fieldSignature.data(), (ULONG)fieldSignature.size(), 0, nullptr, 0, &fieldOutOfOrder)); + + std::array propertySignature = { (uint8_t)IMAGE_CEE_CS_CALLCONV_PROPERTY, (uint8_t)ELEMENT_TYPE_I4 }; + std::array getterSignature = { (uint8_t)IMAGE_CEE_CS_CALLCONV_DEFAULT, (uint8_t)0x00, (uint8_t)ELEMENT_TYPE_I4 }; + + mdMethodDef getter1; + THROW_IF_FAILED(image->DefineMethod(type1, W("get_Property1"), 0, getterSignature.data(), (ULONG)getterSignature.size(), 0, 0, &getter1)); + + mdProperty property1; + THROW_IF_FAILED(image->DefineProperty(type1, W("Property1"), 0, propertySignature.data(), (ULONG)propertySignature.size(), 0, nullptr, 0, getter1, mdMethodDefNil, nullptr, &property1)); + + mdMethodDef getter2; + THROW_IF_FAILED(image->DefineMethod(type2, W("get_Property2"), 0, getterSignature.data(), (ULONG)getterSignature.size(), 0, 0, &getter2)); + + mdProperty property2; + THROW_IF_FAILED(image->DefineProperty(type2, W("Property2"), 0, propertySignature.data(), (ULONG)propertySignature.size(), 0, nullptr, 0, getter2, mdMethodDefNil, nullptr, &property2)); + + // Define a property on the first type after we've already defined a property on the second type. + mdProperty propertyOutOfOrder; + THROW_IF_FAILED(image->DefineProperty(type1, W("PropertyOutOfOrder"), 0, propertySignature.data(), (ULONG)propertySignature.size(), 0, nullptr, 0, mdMethodDefNil, mdMethodDefNil, nullptr, &propertyOutOfOrder)); + + mdTypeRef eventHandlerRef; + THROW_IF_FAILED(image->DefineTypeRefByName(systemRuntimeRef, W("System.EventHandler"), &eventHandlerRef)); + + mdEvent event1; + THROW_IF_FAILED(image->DefineEvent(type1, W("Event1"), 0, eventHandlerRef, mdMethodDefNil, mdMethodDefNil, mdMethodDefNil, nullptr, &event1)); + + mdEvent event2; + THROW_IF_FAILED(image->DefineEvent(type2, W("Event2"), 0, eventHandlerRef, mdMethodDefNil, mdMethodDefNil, mdMethodDefNil, nullptr, &event2)); + + // Define an event on the first type after we've already defined an event on the second type. + mdEvent eventOutOfOrder; + THROW_IF_FAILED(image->DefineEvent(type1, W("EventOutOfOrder"), 0, eventHandlerRef, mdMethodDefNil, mdMethodDefNil, mdMethodDefNil, nullptr, &eventOutOfOrder)); + + ULONG size; + THROW_IF_FAILED(image->GetSaveSize(cssAccurate, &size)); + + malloc_span imageWithIndirectionTables{ (uint8_t*)malloc(size), size }; + THROW_IF_FAILED(image->SaveToMemory(imageWithIndirectionTables, size)); + + return imageWithIndirectionTables; + } + + malloc_span GetMetadataFromKey(std::string key) + { + if (key == IndirectionTablesKey) + { + return CreateImageWithIndirectionTables(); + } + return {}; + } +} + +std::vector MetadataFilesInDirectory(std::string directory) +{ + std::cout << "Discovering metadata files in directory: " << directory << std::endl; + std::vector scenarios; + + if (!std::filesystem::exists(directory)) + { + std::cout << "Directory '" << directory << "' does not exist" << std::endl; + return scenarios; + } + + for (auto& entry : std::filesystem::directory_iterator(directory)) + { + if (entry.is_regular_file()) + { + auto path = entry.path(); + auto ext = path.extension(); + if (ext == ".dll") + { + // Some of the DLLs in our search paths are native, + // so we need to filter to the managed ones. + // We could try opening them and skip them if they don't have any metadata, + // but that is slow and we don't want to do that for test discovery. + // Instead, we'll use the following heuristic to determine if the DLL is managed: + // - If the file name contains '.Native.', then it's not managed + // - If the file name contains '.Thunk.', then it's not managed + // - If the file name starts with 'System.' or 'Microsoft.', then it's managed + + auto fileName = path.filename().generic_string(); + + if (fileName.find(".Native.") != std::string::npos + || fileName.find(".Thunk.") != std::string::npos) + { + continue; + } + + if (fileName.find("System.") != 0 + && fileName.find("Microsoft.") != 0) + { + continue; + } + +#ifdef BUILD_WINDOWS + std::wcout << "Found file: " << entry.path().filename() << std::endl; +#else + std::cout << "Found file: " << entry.path().filename() << std::endl; +#endif + + scenarios.emplace_back(MetadataFile::Kind::OnDisk, path.generic_string()); + } + } + } + + return scenarios; +} + +std::vector CoreLibFiles() +{ + std::cout << "Discovering CoreLib files" << std::endl; + std::vector scenarios; + + scenarios.emplace_back(MetadataFile::Kind::OnDisk, (baselinePath.parent_path() / "System.Private.CoreLib.dll").generic_string(), "System_Private_CoreLib"); + +#ifdef BUILD_WINDOWS + scenarios.emplace_back(MetadataFile::Kind::OnDisk, (std::filesystem::path(FindFrameworkInstall("v4.0.30319")) / "mscorlib.dll").generic_string(), "4_0_mscorlib"); + + auto fx2mscorlib = std::filesystem::path(FindFrameworkInstall("v2.0.50727")) / "mscorlib.dll"; + if (std::filesystem::exists(fx2mscorlib)) + { + scenarios.emplace_back(MetadataFile::Kind::OnDisk, fx2mscorlib.generic_string(), "2_0_mscorlib"); + } +#endif + return scenarios; +} + +namespace +{ + std::mutex metadataCacheMutex; + + struct MetadataFileHash + { + size_t operator()(const MetadataFile& file) const + { + return std::hash{}(file.pathOrKey); + } + }; + + std::unordered_map, MetadataFileHash> metadataCache; +} + +span GetMetadataForFile(MetadataFile file) +{ + std::lock_guard lock{ metadataCacheMutex }; + auto it = metadataCache.find(file); + if (it != metadataCache.end()) + { + return it->second; + } + + malloc_span b; + if (file.kind == MetadataFile::Kind::OnDisk) + { + auto path = baselinePath.parent_path() / file.pathOrKey; + b = ReadMetadataFromFile(path); + } + else + { + b = GetMetadataFromKey(file.pathOrKey.c_str()); + } + + if (b.size() == 0) + { + return {}; + } + + span spanToReturn = b; + + [[maybe_unused]] auto [_, inserted] = metadataCache.emplace(std::move(file), std::move(b)); + assert(inserted); + return spanToReturn; +} + +std::string PrintName(testing::TestParamInfo info) +{ + if (info.param.testNameOverride.size() > 0) + { + return info.param.testNameOverride; + } + std::string name; + if (info.param.kind == MetadataFile::Kind::OnDisk) + { + name = std::filesystem::path(info.param.pathOrKey).stem().generic_string(); + std::replace(name.begin(), name.end(), '.', '_'); + } + else + { + name = info.param.pathOrKey + "_InMemory"; + } + return name; +} + +std::string GetBaselineDirectory() +{ + return baselinePath.parent_path().string(); +} + +void SetBaselineModulePath(std::filesystem::path path) +{ + baselinePath = std::move(path); +} + +void SetRegressionAssemblyPath(std::string path) +{ + regressionAssemblyPath = path; +} + +malloc_span GetRegressionAssemblyMetadata() +{ + return ReadMetadataFromFile(regressionAssemblyPath); +} + +std::string FindFrameworkInstall(std::string version) +{ + std::cout << "Discovering framework install for version: " << version << std::endl; +#ifdef BUILD_WINDOWS + auto key = wil::reg::create_unique_key(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\.NETFramework"); + std::filesystem::path installPath{ wil::reg::get_value_string(key.get(), L"InstallRoot") }; + return (installPath / version).generic_string(); +#else + return {}; +#endif +} \ No newline at end of file diff --git a/test/regtest/fixtures.h b/test/regtest/fixtures.h new file mode 100644 index 00000000..292f8883 --- /dev/null +++ b/test/regtest/fixtures.h @@ -0,0 +1,58 @@ +#ifndef _TEST_REGTEST_FIXTURES_H_ +#define _TEST_REGTEST_FIXTURES_H_ + +#include + +#include + +#include +#include +#include + +struct MetadataFile final +{ + enum class Kind + { + OnDisk, + Generated + } kind; + + MetadataFile(Kind kind, std::string pathOrKey, std::string testNameOverride = "") + : kind(kind), pathOrKey(std::move(pathOrKey)), testNameOverride(testNameOverride) {} + + std::string pathOrKey; + std::string testNameOverride; + + bool operator==(const MetadataFile& rhs) const noexcept + { + return kind == rhs.kind && pathOrKey == rhs.pathOrKey; + } +}; + +inline static std::string IndirectionTablesKey = "IndirectionTables"; + +std::string PrintName(testing::TestParamInfo info); + +std::vector MetadataFilesInDirectory(std::string directory); + +std::vector CoreLibFiles(); + +span GetMetadataForFile(MetadataFile file); + +malloc_span GetRegressionAssemblyMetadata(); + +std::string FindFrameworkInstall(std::string version); + +std::string GetBaselineDirectory(); + +void SetBaselineModulePath(std::filesystem::path path); + +void SetRegressionAssemblyPath(std::string path); + +class RegressionTest : public ::testing::TestWithParam +{ +protected: + using TokenList = std::vector; +}; + +#endif // !_TEST_REGTEST_FIXTURES_H_ \ No newline at end of file diff --git a/test/regtest/main.cpp b/test/regtest/main.cpp new file mode 100644 index 00000000..d07c5497 --- /dev/null +++ b/test/regtest/main.cpp @@ -0,0 +1,56 @@ +#include +#include +#include "baseline.h" +#include "fixtures.h" +#include "pal.hpp" + +namespace TestBaseline +{ + dncp::com_ptr Metadata = nullptr; + dncp::com_ptr DeltaMetadataBuilder = nullptr; + dncp::com_ptr Symbol = nullptr; +} + +#define RETURN_IF_FAILED(x) { auto hr = x; if (FAILED(hr)) return hr; } + +class ThrowListener final : public testing::EmptyTestEventListener +{ + void OnTestPartResult(testing::TestPartResult const& result) override + { + if (result.fatally_failed()) + { + throw testing::AssertionException(result); + } + } +}; + +int main(int argc, char** argv) +{ + RETURN_IF_FAILED(pal::GetBaselineMetadataDispenser(&TestBaseline::Metadata)); + + dncp::com_ptr deltaBuilder; + RETURN_IF_FAILED(pal::GetBaselineMetadataDispenser(&deltaBuilder)); + RETURN_IF_FAILED(deltaBuilder->QueryInterface(IID_IMetaDataDispenserEx, (void**)&TestBaseline::DeltaMetadataBuilder)); + + VARIANT vt; + V_VT(&vt) = VT_UI4; + V_UI4(&vt) = MDUpdateExtension; + if (HRESULT hr = TestBaseline::DeltaMetadataBuilder->SetOption(MetaDataSetENC, &vt); FAILED(hr)) + return hr; + + auto coreClrPath = pal::GetCoreClrPath(); + std::cout << "Loaded metadata baseline module: " << coreClrPath.generic_string() << std::endl; + SetBaselineModulePath(std::move(coreClrPath)); + + std::filesystem::path regressionAssemblyPath = argv[0]; + regressionAssemblyPath = regressionAssemblyPath.parent_path() / "Regression.TargetAssembly.dll"; + + SetRegressionAssemblyPath(regressionAssemblyPath.generic_string()); + + std::cout << "Regression assembly path: " << regressionAssemblyPath.generic_string() << std::endl; + + ::testing::InitGoogleTest(&argc, argv); + testing::UnitTest::GetInstance()->listeners().Append(new ThrowListener); + + return RUN_ALL_TESTS(); +} diff --git a/test/regnative/unit_cor.cpp b/test/regtest/metadata.cpp similarity index 74% rename from test/regnative/unit_cor.cpp rename to test/regtest/metadata.cpp index 222943a0..fed30745 100644 --- a/test/regnative/unit_cor.cpp +++ b/test/regtest/metadata.cpp @@ -1,19 +1,24 @@ -#include "regnative.hpp" +#include "asserts.h" +#include "fixtures.h" +#include "baseline.h" + +#include + +#include +#include #include -#include -#include -#include -#include -#include -#include +#include + +#ifndef BUILD_WINDOWS +#define EXPECT_HRESULT_SUCCEEDED(hr) EXPECT_THAT((hr), testing::Ge(S_OK)) +#define EXPECT_HRESULT_FAILED(hr) EXPECT_THAT((hr), testing::Lt(0)) +#define ASSERT_HRESULT_SUCCEEDED(hr) ASSERT_THAT((hr), testing::Ge(S_OK)) +#define ASSERT_HRESULT_FAILED(hr) ASSERT_THAT((hr), testing::Lt(0)) +#endif namespace { - IMetaDataDispenser* g_baselineDisp; - IMetaDataDispenser* g_deltaImageBuilder; - IMetaDataDispenser* g_currentDisp; - HRESULT CreateImport(IMetaDataDispenser* disp, void const* data, uint32_t dataLen, IMetaDataImport2** import) { assert(disp != nullptr && data != nullptr && dataLen > 0 && import != nullptr); @@ -25,51 +30,6 @@ namespace reinterpret_cast(import)); } - HRESULT CreateEmit(IMetaDataDispenser* disp, void const* data, uint32_t dataLen, IMetaDataEmit2** emit) - { - assert(disp != nullptr && data != nullptr && dataLen > 0 && emit != nullptr); - return disp->OpenScopeOnMemory( - data, - dataLen, - CorOpenFlags::ofWrite, - IID_IMetaDataEmit2, - reinterpret_cast(emit)); - } -} - -EXPORT -HRESULT UnitInitialize(IMetaDataDispenser* baseline, IMetaDataDispenserEx* deltaBuilder) -{ - HRESULT hr; - if (baseline == nullptr) - return E_INVALIDARG; - - if (deltaBuilder == nullptr) - return E_INVALIDARG; - - (void)baseline->AddRef(); - g_baselineDisp = baseline; - - (void)deltaBuilder->AddRef(); - - // We need to set the ENC mode on the delta builder to get it to apply EnC deltas - // and produce images with the indirection tables. - VARIANT vt; - V_VT(&vt) = VT_UI4; - V_UI4(&vt) = MDUpdateExtension; - if (FAILED(hr = deltaBuilder->SetOption(MetaDataSetENC, &vt))) - return hr; - - g_deltaImageBuilder = deltaBuilder; - - if (FAILED(hr = GetDispenser(IID_IMetaDataDispenser, reinterpret_cast(&g_currentDisp)))) - return hr; - - return S_OK; -} - -namespace -{ template using static_enum_buffer = std::array; @@ -150,8 +110,8 @@ namespace void ValidateAndCloseEnum(IMetaDataImport2* import, HCORENUM hcorenum, ULONG expectedCount) { ULONG count; - ASSERT_EQUAL(S_OK, import->CountEnum(hcorenum, &count)); - ASSERT_EQUAL(count, expectedCount); + ASSERT_HRESULT_SUCCEEDED(import->CountEnum(hcorenum, &count)); + ASSERT_EQ(count, expectedCount); import->CloseEnum(hcorenum); } @@ -584,7 +544,7 @@ namespace } dncp::com_ptr mdImport; HRESULT hr = import->QueryInterface(IID_IMetaDataImport2, (void**)&mdImport); - ASSERT_EQUAL(S_OK, hr); + EXPECT_HRESULT_SUCCEEDED(hr); ValidateAndCloseEnum(mdImport, hcorenum, (ULONG)tokens.size()); return tokens; } @@ -603,7 +563,7 @@ namespace } dncp::com_ptr mdImport; HRESULT hr = import->QueryInterface(IID_IMetaDataImport2, (void**)&mdImport); - ASSERT_EQUAL(S_OK, hr); + EXPECT_HRESULT_SUCCEEDED(hr); ValidateAndCloseEnum(mdImport, hcorenum, (ULONG)tokens.size()); return tokens; } @@ -622,7 +582,7 @@ namespace } dncp::com_ptr mdImport; HRESULT hr = import->QueryInterface(IID_IMetaDataImport2, (void**)&mdImport); - ASSERT_EQUAL(S_OK, hr); + EXPECT_HRESULT_SUCCEEDED(hr); ValidateAndCloseEnum(mdImport, hcorenum, (ULONG)tokens.size()); return tokens; } @@ -641,7 +601,7 @@ namespace } dncp::com_ptr mdImport; HRESULT hr = import->QueryInterface(IID_IMetaDataImport2, (void**)&mdImport); - ASSERT_EQUAL(S_OK, hr); + EXPECT_HRESULT_SUCCEEDED(hr); ValidateAndCloseEnum(mdImport, hcorenum, (ULONG)tokens.size()); return tokens; } @@ -1164,7 +1124,8 @@ namespace values.push_back(pulParamSeq); values.push_back(pdwParamFlags); values.push_back(ptOwner); - values.push_back(reserved); + // We don't care about reserved + // as its value is unspecified uint32_t hash = HashCharArray(name, pchName); values.push_back(hash); values.push_back(pchName); @@ -1549,7 +1510,7 @@ namespace { values.push_back(HashCharArray(name, nameLength)); values.push_back((size_t)nameLength); - values.push_back(hashLength != 0 ? (size_t)hash : 0); + values.push_back(HashByteArray(hash, hashLength)); values.push_back(hashLength); values.push_back(flags); } @@ -1638,14 +1599,14 @@ namespace // Determine how many we have and move to right before end ULONG count; - ASSERT_EQUAL(S_OK, import->CountEnum(hcorenum, &count)); + EXPECT_HRESULT_SUCCEEDED(import->CountEnum(hcorenum, &count)); if (count != 0) { - ASSERT_EQUAL(S_OK, import->ResetEnum(hcorenum, count - 1)); + EXPECT_HRESULT_SUCCEEDED(import->ResetEnum(hcorenum, count - 1)); ReadInMembers(import, hcorenum, tk, tokens); // Fully reset the enum - ASSERT_EQUAL(S_OK, import->ResetEnum(hcorenum, 0)); + EXPECT_HRESULT_SUCCEEDED(import->ResetEnum(hcorenum, 0)); ReadInMembers(import, hcorenum, tk, tokens); } } @@ -1658,260 +1619,444 @@ namespace } } -EXPORT -TestResult UnitImportAPIs(void const* data, uint32_t dataLen) +TEST(FindTest, FindAPIs) { - BEGIN_TEST(); + malloc_span metadata = GetRegressionAssemblyMetadata(); + + dncp::com_ptr baselineImport; + ASSERT_HRESULT_SUCCEEDED(CreateImport(TestBaseline::Metadata, metadata, (uint32_t)metadata.size(), &baselineImport)); + // Load metadata + dncp::com_ptr currentImport; + + dncp::com_ptr dispenser; + ASSERT_HRESULT_SUCCEEDED(GetDispenser(IID_IMetaDataDispenser, (void**)&dispenser)); + + ASSERT_HRESULT_SUCCEEDED(CreateImport(dispenser, metadata, (uint32_t)metadata.size(), ¤tImport)); + + static auto FindTokenByName = [](IMetaDataImport2* import, LPCWSTR name, mdToken enclosing = mdTokenNil) -> mdToken + { + mdTypeDef ptd; + EXPECT_HRESULT_SUCCEEDED(import->FindTypeDefByName(name, enclosing, &ptd)); + return ptd; + }; + + static auto GetTypeDefBaseToken = [](IMetaDataImport2* import, mdTypeDef tk) -> mdToken + { + static_char_buffer name{}; + ULONG pchTypeDef; + DWORD pdwTypeDefFlags; + mdToken ptkExtends; + EXPECT_HRESULT_SUCCEEDED(import->GetTypeDefProps(tk, + name.data(), + (ULONG)name.size(), + &pchTypeDef, + &pdwTypeDefFlags, + &ptkExtends)); + return ptkExtends; + }; + + static auto FindMethodDef = [](IMetaDataImport2* import, mdTypeDef type, LPCWSTR methodName) -> mdToken + { + std::vector methoddefs = EnumMembersWithName(import, type, methodName); + EXPECT_TRUE(!methoddefs.empty()); + return methoddefs[0]; + }; + + static auto FindMemberRef = [](IMetaDataImport2* import, mdTypeDef type, LPCWSTR methodName) -> mdToken + { + auto methodDef = FindMethodDef(import, type, methodName); + mdMemberRef pmr; + EXPECT_HRESULT_SUCCEEDED(import->FindMemberRef(methodDef, methodName, nullptr, 0, &pmr)); + return pmr; + }; + + static auto FindMethod = [](IMetaDataImport2* import, mdTypeDef td, LPCWSTR name, void const* pvSigBlob, ULONG cbSigBlob) -> uint32_t + { + mdMethodDef tkMethod; + mdToken tkMember; + EXPECT_HRESULT_SUCCEEDED(import->FindMethod(td, name, (PCCOR_SIGNATURE)pvSigBlob, cbSigBlob, &tkMethod)); + EXPECT_HRESULT_SUCCEEDED(import->FindMember(td, name, (PCCOR_SIGNATURE)pvSigBlob, cbSigBlob, &tkMember)); + EXPECT_EQ(tkMethod, tkMember); + return tkMethod; + }; + + static auto FindField = [](IMetaDataImport2* import, mdTypeDef td, LPCWSTR name, void const* pvSigBlob, ULONG cbSigBlob) -> uint32_t + { + mdFieldDef tkField; + mdToken tkMember; + EXPECT_HRESULT_SUCCEEDED(import->FindField(td, name, (PCCOR_SIGNATURE)pvSigBlob, cbSigBlob, &tkField)); + EXPECT_HRESULT_SUCCEEDED(import->FindMember(td, name, (PCCOR_SIGNATURE)pvSigBlob, cbSigBlob, &tkMember)); + EXPECT_EQ(tkField, tkMember); + return tkField; + }; + + auto tgt = W("C"); + + auto baseTypeDef = W("B1"); + auto tkB1 = EXPECT_THAT_AND_RETURN(FindTokenByName(baselineImport, baseTypeDef), testing::Eq(FindTokenByName(currentImport, baseTypeDef))); + auto tkB1Base = EXPECT_THAT_AND_RETURN(GetTypeDefBaseToken(baselineImport, tkB1), testing::Eq(GetTypeDefBaseToken(currentImport, tkB1))); + ASSERT_EQ(FindTypeDefByName(baselineImport, tgt, tkB1Base), FindTypeDefByName(currentImport, tgt, tkB1Base)); + + auto baseTypeRef = W("B2"); + auto tkB2 = EXPECT_THAT_AND_RETURN(FindTokenByName(baselineImport, baseTypeRef), testing::Eq(FindTokenByName(currentImport, baseTypeRef))); + auto tkB2Base = EXPECT_THAT_AND_RETURN(GetTypeDefBaseToken(baselineImport, tkB2), testing::Eq(GetTypeDefBaseToken(currentImport, tkB2))); + ASSERT_THAT(FindTypeDefByName(baselineImport, tgt, tkB2Base), testing::Eq(FindTypeDefByName(currentImport, tgt, tkB2Base))); + + auto methodDefName = W("MethodDef"); + auto tkMethodDef = EXPECT_THAT_AND_RETURN(FindMethodDef(baselineImport, tkB1Base, methodDefName), testing::Eq(FindMethodDef(currentImport, tkB1Base, methodDefName))); + + void const* defSigBlob; + ULONG defSigBlobLength; + ASSERT_EQ( + GetMethodProps(baselineImport, tkMethodDef), + GetMethodProps(currentImport, tkMethodDef, &defSigBlob, &defSigBlobLength)); + ASSERT_EQ( + FindMethod(baselineImport, tkB1Base, methodDefName, defSigBlob, defSigBlobLength), + FindMethod(currentImport, tkB1Base, methodDefName, defSigBlob, defSigBlobLength)); + + auto methodRef1Name = W("MethodRef1"); + auto tkMemberRefNoVarArgsBase = EXPECT_THAT_AND_RETURN( + FindMemberRef(baselineImport, tkB1Base, methodRef1Name), + testing::Eq(FindMemberRef(currentImport, tkB1Base, methodRef1Name))); + + PCCOR_SIGNATURE ref1Blob; + ULONG ref1BlobLength; + ASSERT_EQ( + GetMemberRefProps(baselineImport, tkMemberRefNoVarArgsBase), + GetMemberRefProps(currentImport, tkMemberRefNoVarArgsBase, &ref1Blob, &ref1BlobLength)); + ASSERT_EQ( + FindMethod(baselineImport, tkB1Base, methodRef1Name, ref1Blob, ref1BlobLength), + FindMethod(currentImport, tkB1Base, methodRef1Name, ref1Blob, ref1BlobLength)); + + auto methodRef2Name = W("MethodRef2"); + auto tkMemberRefVarArgsBase = EXPECT_THAT_AND_RETURN( + FindMemberRef(baselineImport, tkB1Base, methodRef2Name), + testing::Eq(FindMemberRef(currentImport, tkB1Base, methodRef2Name))); + + PCCOR_SIGNATURE ref2Blob; + ULONG ref2BlobLength; + ASSERT_EQ( + GetMemberRefProps(baselineImport, tkMemberRefVarArgsBase), + GetMemberRefProps(currentImport, tkMemberRefVarArgsBase, &ref2Blob, &ref2BlobLength)); + EXPECT_EQ( + FindMethod(baselineImport, tkB1Base, methodRef2Name, ref2Blob, ref2BlobLength), + FindMethod(currentImport, tkB1Base, methodRef2Name, ref2Blob, ref2BlobLength)); + + auto fieldName = W("Field1"); + auto tkFields = EXPECT_THAT_AND_RETURN( + EnumFieldsWithName(baselineImport, tkB2, fieldName), + testing::ElementsAreArray(EnumFieldsWithName(currentImport, tkB2, fieldName))); + ASSERT_FALSE(tkFields.empty()); + mdToken tkField = tkFields[0]; + + void const* sigBlob; + ULONG sigBlobLength; + ASSERT_EQ( + GetFieldProps(baselineImport, tkField), + GetFieldProps(currentImport, tkField, &sigBlob, &sigBlobLength)); + EXPECT_EQ( + FindField(baselineImport, tkB2, fieldName, sigBlob, sigBlobLength), + FindField(currentImport, tkB2, fieldName, sigBlob, sigBlobLength)); +} + +class MetadataImportTest : public RegressionTest +{ +}; + +TEST_P(MetadataImportTest, ImportAPIs) +{ + auto param = GetParam(); + span blob = GetMetadataForFile(param); + void const* data = blob; + uint32_t dataLen = (uint32_t)blob.size(); // Load metadata dncp::com_ptr baselineImport; - ASSERT_EQUAL(S_OK, CreateImport(g_baselineDisp, data, dataLen, &baselineImport)); + ASSERT_HRESULT_SUCCEEDED(CreateImport(TestBaseline::Metadata, data, dataLen, &baselineImport)); + + dncp::com_ptr dispenser; + ASSERT_HRESULT_SUCCEEDED(GetDispenser(IID_IMetaDataDispenser, (void**)&dispenser)); dncp::com_ptr currentImport; - ASSERT_EQUAL(S_OK, CreateImport(g_currentDisp, data, dataLen, ¤tImport)); + ASSERT_HRESULT_SUCCEEDED(CreateImport(dispenser, data, dataLen, ¤tImport)); // Verify APIs - ASSERT_EQUAL(ResetEnum(baselineImport), ResetEnum(currentImport)); - ASSERT_EQUAL(GetScopeProps(baselineImport), GetScopeProps(currentImport)); - ASSERT_EQUAL(GetVersionString(baselineImport), GetVersionString(currentImport)); + ASSERT_THAT(ResetEnum(currentImport), testing::ElementsAreArray(ResetEnum(baselineImport))); + ASSERT_THAT(GetScopeProps(currentImport), testing::ElementsAreArray(GetScopeProps(baselineImport))); + ASSERT_THAT(GetVersionString(currentImport), testing::ElementsAreArray(GetVersionString(baselineImport))); - auto sigs = ASSERT_AND_RETURN(EnumSignatures(baselineImport), EnumSignatures(currentImport)); + TokenList sigs; + ASSERT_EQUAL_AND_SET(sigs, EnumSignatures(baselineImport), EnumSignatures(currentImport)); for (auto sig : sigs) { - ASSERT_EQUAL(GetSigFromToken(baselineImport, sig), GetSigFromToken(currentImport, sig)); + ASSERT_THAT(GetSigFromToken(currentImport, sig), testing::ElementsAreArray(GetSigFromToken(baselineImport, sig))); } - auto userStrings = ASSERT_AND_RETURN(EnumUserStrings(baselineImport), EnumUserStrings(currentImport)); + TokenList userStrings; + ASSERT_EQUAL_AND_SET(userStrings, EnumUserStrings(baselineImport), EnumUserStrings(currentImport)); for (auto us : userStrings) { - ASSERT_EQUAL(GetUserString(baselineImport, us), GetUserString(currentImport, us)); + ASSERT_THAT(GetUserString(currentImport, us), testing::ElementsAreArray(GetUserString(baselineImport, us))); } - auto custAttrs = ASSERT_AND_RETURN(EnumCustomAttributes(baselineImport), EnumCustomAttributes(currentImport)); + TokenList custAttrs; + ASSERT_EQUAL_AND_SET(custAttrs, EnumCustomAttributes(baselineImport), EnumCustomAttributes(currentImport)); for (auto ca : custAttrs) { - ASSERT_EQUAL(GetCustomAttributeProps(baselineImport, ca), GetCustomAttributeProps(currentImport, ca)); + ASSERT_THAT(GetCustomAttributeProps(currentImport, ca), testing::ElementsAreArray(GetCustomAttributeProps(baselineImport, ca))); } - auto modulerefs = ASSERT_AND_RETURN(EnumModuleRefs(baselineImport), EnumModuleRefs(currentImport)); + TokenList modulerefs; + ASSERT_EQUAL_AND_SET(modulerefs, EnumModuleRefs(baselineImport), EnumModuleRefs(currentImport)); for (auto moduleref : modulerefs) { - ASSERT_EQUAL(GetModuleRefProps(baselineImport, moduleref), GetModuleRefProps(currentImport, moduleref)); - ASSERT_EQUAL(GetNameFromToken(baselineImport, moduleref), GetNameFromToken(currentImport, moduleref)); + ASSERT_THAT(GetModuleRefProps(currentImport, moduleref), testing::ElementsAreArray(GetModuleRefProps(baselineImport, moduleref))); + ASSERT_THAT(GetNameFromToken(currentImport, moduleref), testing::ElementsAreArray(GetNameFromToken(baselineImport, moduleref))); } - ASSERT_EQUAL(FindTypeRef(baselineImport), FindTypeRef(currentImport)); - auto typerefs = ASSERT_AND_RETURN(EnumTypeRefs(baselineImport), EnumTypeRefs(currentImport)); + ASSERT_THAT(FindTypeRef(currentImport), testing::ElementsAreArray(FindTypeRef(baselineImport))); + TokenList typerefs; + ASSERT_EQUAL_AND_SET(typerefs, EnumTypeRefs(baselineImport), EnumTypeRefs(currentImport)); for (auto typeref : typerefs) { - ASSERT_EQUAL(GetTypeRefProps(baselineImport, typeref), GetTypeRefProps(currentImport, typeref)); - ASSERT_EQUAL(GetCustomAttribute_CompilerGenerated(baselineImport, typeref), GetCustomAttribute_CompilerGenerated(currentImport, typeref)); - ASSERT_EQUAL(GetNameFromToken(baselineImport, typeref), GetNameFromToken(currentImport, typeref)); + ASSERT_THAT(GetTypeRefProps(currentImport, typeref), testing::ElementsAreArray(GetTypeRefProps(baselineImport, typeref))); + ASSERT_THAT(GetCustomAttribute_CompilerGenerated(currentImport, typeref), testing::ElementsAreArray(GetCustomAttribute_CompilerGenerated(baselineImport, typeref))); + ASSERT_THAT(GetNameFromToken(currentImport, typeref), testing::ElementsAreArray(GetNameFromToken(baselineImport, typeref))); } - auto typespecs = ASSERT_AND_RETURN(EnumTypeSpecs(baselineImport), EnumTypeSpecs(currentImport)); + TokenList typespecs; + ASSERT_EQUAL_AND_SET(typespecs, EnumTypeSpecs(baselineImport), EnumTypeSpecs(currentImport)); for (auto typespec : typespecs) { - ASSERT_EQUAL(GetTypeSpecFromToken(baselineImport, typespec), GetTypeSpecFromToken(currentImport, typespec)); - ASSERT_EQUAL(GetCustomAttribute_CompilerGenerated(baselineImport, typespec), GetCustomAttribute_CompilerGenerated(currentImport, typespec)); + ASSERT_THAT(GetTypeSpecFromToken(currentImport, typespec), testing::ElementsAreArray(GetTypeSpecFromToken(baselineImport, typespec))); + ASSERT_THAT(GetCustomAttribute_CompilerGenerated(currentImport, typespec), testing::ElementsAreArray(GetCustomAttribute_CompilerGenerated(baselineImport, typespec))); } - auto typedefs = ASSERT_AND_RETURN(EnumTypeDefs(baselineImport), EnumTypeDefs(currentImport)); + TokenList typedefs; + ASSERT_EQUAL_AND_SET(typedefs, EnumTypeDefs(baselineImport), EnumTypeDefs(currentImport)); for (auto typdef : typedefs) { - ASSERT_EQUAL(GetTypeDefProps(baselineImport, typdef), GetTypeDefProps(currentImport, typdef)); - ASSERT_EQUAL(GetNameFromToken(baselineImport, typdef), GetNameFromToken(currentImport, typdef)); - ASSERT_EQUAL(IsGlobal(baselineImport, typdef), IsGlobal(currentImport, typdef)); - ASSERT_EQUAL(EnumInterfaceImpls(baselineImport, typdef), EnumInterfaceImpls(currentImport, typdef)); - ASSERT_EQUAL(EnumPermissionSetsAndGetProps(baselineImport, typdef), EnumPermissionSetsAndGetProps(currentImport, typdef)); - ASSERT_EQUAL(EnumMembers(baselineImport, typdef), EnumMembers(currentImport, typdef)); - ASSERT_EQUAL(EnumMembersWithName(baselineImport, typdef), EnumMembersWithName(currentImport, typdef)); - ASSERT_EQUAL(EnumMethodsWithName(baselineImport, typdef), EnumMethodsWithName(currentImport, typdef)); - ASSERT_EQUAL(EnumMethodImpls(baselineImport, typdef), EnumMethodImpls(currentImport, typdef)); - ASSERT_EQUAL(GetNestedClassProps(baselineImport, typdef), GetNestedClassProps(currentImport, typdef)); - ASSERT_EQUAL(GetClassLayout(baselineImport, typdef), GetClassLayout(currentImport, typdef)); - ASSERT_EQUAL(GetCustomAttribute_CompilerGenerated(baselineImport, typdef), GetCustomAttribute_CompilerGenerated(currentImport, typdef)); - - auto methoddefs = ASSERT_AND_RETURN(EnumMethods(baselineImport, typdef), EnumMethods(currentImport, typdef)); + ASSERT_THAT(GetTypeDefProps(currentImport, typdef), testing::ElementsAreArray(GetTypeDefProps(baselineImport, typdef))); + ASSERT_THAT(GetNameFromToken(currentImport, typdef), testing::ElementsAreArray(GetNameFromToken(baselineImport, typdef))); + ASSERT_THAT(IsGlobal(currentImport, typdef), testing::Eq(IsGlobal(baselineImport, typdef))); + ASSERT_THAT(EnumInterfaceImpls(currentImport, typdef), testing::ElementsAreArray(EnumInterfaceImpls(baselineImport, typdef))); + ASSERT_THAT(EnumPermissionSetsAndGetProps(currentImport, typdef), testing::ElementsAreArray(EnumPermissionSetsAndGetProps(baselineImport, typdef))); + ASSERT_THAT(EnumMembers(currentImport, typdef), testing::ElementsAreArray(EnumMembers(baselineImport, typdef))); + ASSERT_THAT(EnumMembersWithName(currentImport, typdef), testing::ElementsAreArray(EnumMembersWithName(baselineImport, typdef))); + ASSERT_THAT(EnumMethodsWithName(currentImport, typdef), testing::ElementsAreArray(EnumMethodsWithName(baselineImport, typdef))); + ASSERT_THAT(EnumMethodImpls(currentImport, typdef), testing::ElementsAreArray(EnumMethodImpls(baselineImport, typdef))); + ASSERT_THAT(GetNestedClassProps(currentImport, typdef), testing::ElementsAreArray(GetNestedClassProps(baselineImport, typdef))); + ASSERT_THAT(GetClassLayout(currentImport, typdef), testing::ElementsAreArray(GetClassLayout(baselineImport, typdef))); + ASSERT_THAT(GetCustomAttribute_CompilerGenerated(currentImport, typdef), testing::ElementsAreArray(GetCustomAttribute_CompilerGenerated(baselineImport, typdef))); + + TokenList methoddefs; + ASSERT_EQUAL_AND_SET(methoddefs, EnumMethods(baselineImport, typdef), EnumMethods(currentImport, typdef)); for (auto methoddef : methoddefs) { void const* sig = nullptr; ULONG sigLen = 0; - ASSERT_EQUAL(GetMethodProps(baselineImport, methoddef), GetMethodProps(currentImport, methoddef, &sig, &sigLen)); - ASSERT_EQUAL(GetNativeCallConvFromSig(baselineImport, sig, sigLen), GetNativeCallConvFromSig(currentImport, sig, sigLen)); - ASSERT_EQUAL(GetNameFromToken(baselineImport, methoddef), GetNameFromToken(currentImport, methoddef)); - ASSERT_EQUAL(IsGlobal(baselineImport, methoddef), IsGlobal(currentImport, methoddef)); - ASSERT_EQUAL(GetCustomAttribute_CompilerGenerated(baselineImport, methoddef), GetCustomAttribute_CompilerGenerated(currentImport, methoddef)); - - auto paramdefs = ASSERT_AND_RETURN(EnumParams(baselineImport, methoddef), EnumParams(currentImport, methoddef)); + ASSERT_THAT(GetMethodProps(currentImport, methoddef, &sig, &sigLen), testing::ElementsAreArray(GetMethodProps(baselineImport, methoddef))); + ASSERT_THAT(GetNativeCallConvFromSig(currentImport, sig, sigLen), testing::ElementsAreArray(GetNativeCallConvFromSig(baselineImport, sig, sigLen))); + ASSERT_THAT(GetNameFromToken(currentImport, methoddef), testing::ElementsAreArray(GetNameFromToken(baselineImport, methoddef))); + ASSERT_THAT(IsGlobal(currentImport, methoddef), testing::Eq(IsGlobal(baselineImport, methoddef))); + ASSERT_THAT(GetCustomAttribute_CompilerGenerated(currentImport, methoddef), testing::ElementsAreArray(GetCustomAttribute_CompilerGenerated(baselineImport, methoddef))); + + TokenList paramdefs; + ASSERT_EQUAL_AND_SET(paramdefs, EnumParams(baselineImport, methoddef), EnumParams(currentImport, methoddef)); for (auto paramdef : paramdefs) { - ASSERT_EQUAL(GetParamProps(baselineImport, paramdef), GetParamProps(currentImport, paramdef)); - ASSERT_EQUAL(GetFieldMarshal(baselineImport, paramdef), GetFieldMarshal(currentImport, paramdef)); - ASSERT_EQUAL(GetCustomAttribute_Nullable(baselineImport, paramdef), GetCustomAttribute_Nullable(currentImport, paramdef)); - ASSERT_EQUAL(GetNameFromToken(baselineImport, paramdef), GetNameFromToken(currentImport, paramdef)); + ASSERT_THAT(GetParamProps(currentImport, paramdef), testing::ElementsAreArray(GetParamProps(baselineImport, paramdef))); + ASSERT_THAT(GetFieldMarshal(currentImport, paramdef), testing::ElementsAreArray(GetFieldMarshal(baselineImport, paramdef))); + ASSERT_THAT(GetCustomAttribute_Nullable(currentImport, paramdef), testing::ElementsAreArray(GetCustomAttribute_Nullable(baselineImport, paramdef))); + ASSERT_THAT(GetNameFromToken(currentImport, paramdef), testing::ElementsAreArray(GetNameFromToken(baselineImport, paramdef))); } - ASSERT_EQUAL(GetParamForMethodIndex(baselineImport, methoddef), GetParamForMethodIndex(currentImport, methoddef)); - ASSERT_EQUAL(EnumPermissionSetsAndGetProps(baselineImport, methoddef), EnumPermissionSetsAndGetProps(currentImport, methoddef)); - ASSERT_EQUAL(GetPinvokeMap(baselineImport, methoddef), GetPinvokeMap(currentImport, methoddef)); - ASSERT_EQUAL(GetRVA(baselineImport, methoddef), GetRVA(currentImport, methoddef)); + ASSERT_THAT(GetParamForMethodIndex(currentImport, methoddef), testing::ElementsAreArray(GetParamForMethodIndex(baselineImport, methoddef))); + ASSERT_THAT(EnumPermissionSetsAndGetProps(currentImport, methoddef), testing::ElementsAreArray(EnumPermissionSetsAndGetProps(baselineImport, methoddef))); + ASSERT_THAT(GetPinvokeMap(currentImport, methoddef), testing::ElementsAreArray(GetPinvokeMap(baselineImport, methoddef))); + ASSERT_THAT(GetRVA(currentImport, methoddef), testing::ElementsAreArray(GetRVA(baselineImport, methoddef))); - auto methodspecs = ASSERT_AND_RETURN(EnumMethodSpecs(baselineImport, methoddef), EnumMethodSpecs(currentImport, methoddef)); + TokenList methodspecs; + ASSERT_EQUAL_AND_SET(methodspecs, EnumMethodSpecs(baselineImport, methoddef), EnumMethodSpecs(currentImport, methoddef)); for (auto methodspec : methodspecs) { - ASSERT_EQUAL(GetMethodSpecProps(baselineImport, methodspec), GetMethodSpecProps(currentImport, methodspec)); + ASSERT_THAT(GetMethodSpecProps(currentImport, methodspec), testing::ElementsAreArray(GetMethodSpecProps(baselineImport, methodspec))); } } - auto eventdefs = ASSERT_AND_RETURN(EnumEvents(baselineImport, typdef), EnumEvents(currentImport, typdef)); + TokenList eventdefs; + ASSERT_EQUAL_AND_SET(eventdefs, EnumEvents(baselineImport, typdef), EnumEvents(currentImport, typdef)); for (auto eventdef : eventdefs) { std::vector mds; - ASSERT_EQUAL(GetEventProps(baselineImport, eventdef), GetEventProps(currentImport, eventdef, &mds)); + ASSERT_THAT(GetEventProps(currentImport, eventdef, &mds), testing::ElementsAreArray(GetEventProps(baselineImport, eventdef))); for (auto md : mds) { - ASSERT_EQUAL(GetMethodSemantics(baselineImport, eventdef, md), GetMethodSemantics(currentImport, eventdef, md)); + ASSERT_THAT(GetMethodSemantics(currentImport, eventdef, md), testing::ElementsAreArray(GetMethodSemantics(baselineImport, eventdef, md))); } - ASSERT_EQUAL(GetNameFromToken(baselineImport, eventdef), GetNameFromToken(currentImport, eventdef)); - ASSERT_EQUAL(IsGlobal(baselineImport, eventdef), IsGlobal(currentImport, eventdef)); + ASSERT_THAT(GetNameFromToken(currentImport, eventdef), testing::ElementsAreArray(GetNameFromToken(baselineImport, eventdef))); + ASSERT_THAT(IsGlobal(currentImport, eventdef), testing::Eq(IsGlobal(baselineImport, eventdef))); } - auto properties = ASSERT_AND_RETURN(EnumProperties(baselineImport, typdef), EnumProperties(currentImport, typdef)); + TokenList properties; + ASSERT_EQUAL_AND_SET(properties, EnumProperties(baselineImport, typdef), EnumProperties(currentImport, typdef)); for (auto props : properties) { std::vector mds; - ASSERT_EQUAL(GetPropertyProps(baselineImport, props), GetPropertyProps(currentImport, props, &mds)); + ASSERT_THAT(GetPropertyProps(currentImport, props, &mds), testing::ElementsAreArray(GetPropertyProps(baselineImport, props))); for (auto md : mds) { - ASSERT_EQUAL(GetMethodSemantics(baselineImport, props, md), GetMethodSemantics(currentImport, props, md)); + ASSERT_THAT(GetMethodSemantics(currentImport, props, md), testing::ElementsAreArray(GetMethodSemantics(baselineImport, props, md))); } - ASSERT_EQUAL(GetNameFromToken(baselineImport, props), GetNameFromToken(currentImport, props)); - ASSERT_EQUAL(IsGlobal(baselineImport, props), IsGlobal(currentImport, props)); + ASSERT_THAT(GetNameFromToken(currentImport, props), testing::ElementsAreArray(GetNameFromToken(baselineImport, props))); + ASSERT_THAT(IsGlobal(currentImport, props), testing::Eq(IsGlobal(baselineImport, props))); } - ASSERT_EQUAL(EnumFieldsWithName(baselineImport, typdef), EnumFieldsWithName(currentImport, typdef)); - auto fielddefs = ASSERT_AND_RETURN(EnumFields(baselineImport, typdef), EnumFields(currentImport, typdef)); + ASSERT_THAT(EnumFieldsWithName(baselineImport, typdef), EnumFieldsWithName(currentImport, typdef)); + TokenList fielddefs; + ASSERT_EQUAL_AND_SET(fielddefs, EnumFields(baselineImport, typdef), EnumFields(currentImport, typdef)); for (auto fielddef : fielddefs) { - ASSERT_EQUAL(GetFieldProps(baselineImport, fielddef), GetFieldProps(currentImport, fielddef)); - ASSERT_EQUAL(GetNameFromToken(baselineImport, fielddef), GetNameFromToken(currentImport, fielddef)); - ASSERT_EQUAL(IsGlobal(baselineImport, fielddef), IsGlobal(currentImport, fielddef)); - ASSERT_EQUAL(GetPinvokeMap(baselineImport, fielddef), GetPinvokeMap(currentImport, fielddef)); - ASSERT_EQUAL(GetRVA(baselineImport, fielddef), GetRVA(currentImport, fielddef)); - ASSERT_EQUAL(GetFieldMarshal(baselineImport, fielddef), GetFieldMarshal(currentImport, fielddef)); - ASSERT_EQUAL(GetCustomAttribute_Nullable(baselineImport, fielddef), GetCustomAttribute_Nullable(currentImport, fielddef)); + ASSERT_THAT(GetFieldProps(currentImport, fielddef), testing::ElementsAreArray(GetFieldProps(baselineImport, fielddef))); + ASSERT_THAT(GetNameFromToken(currentImport, fielddef), testing::ElementsAreArray(GetNameFromToken(baselineImport, fielddef))); + ASSERT_THAT(IsGlobal(currentImport, fielddef), testing::Eq(IsGlobal(baselineImport, fielddef))); + ASSERT_THAT(GetPinvokeMap(currentImport, fielddef), testing::ElementsAreArray(GetPinvokeMap(baselineImport, fielddef))); + ASSERT_THAT(GetRVA(currentImport, fielddef), testing::ElementsAreArray(GetRVA(baselineImport, fielddef))); + ASSERT_THAT(GetFieldMarshal(currentImport, fielddef), testing::ElementsAreArray(GetFieldMarshal(baselineImport, fielddef))); + ASSERT_THAT(GetCustomAttribute_Nullable(currentImport, fielddef), testing::ElementsAreArray(GetCustomAttribute_Nullable(baselineImport, fielddef))); } - auto genparams = ASSERT_AND_RETURN(EnumGenericParams(baselineImport, typdef), EnumGenericParams(currentImport, typdef)); + TokenList genparams; + ASSERT_EQUAL_AND_SET(genparams, EnumGenericParams(baselineImport, typdef), EnumGenericParams(currentImport, typdef)); for (auto genparam : genparams) { - ASSERT_EQUAL(GetGenericParamProps(baselineImport, genparam), GetGenericParamProps(currentImport, genparam)); - auto genparamconsts = ASSERT_AND_RETURN(EnumGenericParamConstraints(baselineImport, genparam), EnumGenericParamConstraints(currentImport, genparam)); + ASSERT_THAT(GetGenericParamProps(currentImport, genparam), testing::ElementsAreArray(GetGenericParamProps(baselineImport, genparam))); + TokenList genparamconsts; + ASSERT_EQUAL_AND_SET(genparamconsts, EnumGenericParamConstraints(baselineImport, genparam), EnumGenericParamConstraints(currentImport, genparam)); for (auto genparamconst : genparamconsts) { - ASSERT_EQUAL(GetGenericParamConstraintProps(baselineImport, genparamconst), GetGenericParamConstraintProps(currentImport, genparamconst)); + ASSERT_THAT(GetGenericParamConstraintProps(currentImport, genparamconst), testing::ElementsAreArray(GetGenericParamConstraintProps(baselineImport, genparamconst))); } } } dncp::com_ptr baselineAssembly; - ASSERT_EQUAL(S_OK, baselineImport->QueryInterface(IID_IMetaDataAssemblyImport, (void**)&baselineAssembly)); + ASSERT_THAT(S_OK, baselineImport->QueryInterface(IID_IMetaDataAssemblyImport, (void**)&baselineAssembly)); dncp::com_ptr currentAssembly; - ASSERT_EQUAL(S_OK, currentImport->QueryInterface(IID_IMetaDataAssemblyImport, (void**)¤tAssembly)); + ASSERT_THAT(S_OK, currentImport->QueryInterface(IID_IMetaDataAssemblyImport, (void**)¤tAssembly)); - auto assemblyTokens = ASSERT_AND_RETURN(GetAssemblyFromScope(baselineAssembly), GetAssemblyFromScope(currentAssembly)); + TokenList assemblyTokens; + ASSERT_EQUAL_AND_SET(assemblyTokens, GetAssemblyFromScope(baselineAssembly), GetAssemblyFromScope(currentAssembly)); for (auto assembly : assemblyTokens) { - ASSERT_EQUAL(GetAssemblyProps(baselineAssembly, assembly), GetAssemblyProps(currentAssembly, assembly)); + ASSERT_THAT(GetAssemblyProps(currentAssembly, assembly), testing::ElementsAreArray(GetAssemblyProps(baselineAssembly, assembly))); } - auto assemblyRefs = ASSERT_AND_RETURN(EnumAssemblyRefs(baselineAssembly), EnumAssemblyRefs(currentAssembly)); + TokenList assemblyRefs; + ASSERT_EQUAL_AND_SET(assemblyRefs, EnumAssemblyRefs(baselineAssembly), EnumAssemblyRefs(currentAssembly)); for (auto assemblyRef : assemblyRefs) { - ASSERT_EQUAL(GetAssemblyRefProps(baselineAssembly, assemblyRef), GetAssemblyRefProps(currentAssembly, assemblyRef)); + ASSERT_THAT(GetAssemblyRefProps(currentAssembly, assemblyRef), testing::ElementsAreArray(GetAssemblyRefProps(baselineAssembly, assemblyRef))); } - auto files = ASSERT_AND_RETURN(EnumFiles(baselineAssembly), EnumFiles(currentAssembly)); + TokenList files; + ASSERT_EQUAL_AND_SET(files, EnumFiles(baselineAssembly), EnumFiles(currentAssembly)); for (auto file : files) { - ASSERT_EQUAL(GetFileProps(baselineAssembly, file), GetFileProps(currentAssembly, file)); + ASSERT_THAT(GetFileProps(currentAssembly, file), testing::ElementsAreArray(GetFileProps(baselineAssembly, file))); } - auto exports = ASSERT_AND_RETURN(EnumExportedTypes(baselineAssembly), EnumExportedTypes(currentAssembly)); + TokenList exports; + ASSERT_EQUAL_AND_SET(exports, EnumExportedTypes(baselineAssembly), EnumExportedTypes(currentAssembly)); for (auto exportedType : exports) { std::vector name; uint32_t implementation = mdTokenNil; - ASSERT_EQUAL(GetExportedTypeProps(baselineAssembly, exportedType), GetExportedTypeProps(currentAssembly, exportedType, &name, &implementation)); - ASSERT_EQUAL( - FindExportedTypeByName(baselineAssembly, name.data(), implementation), - FindExportedTypeByName(currentAssembly, name.data(), implementation)); + ASSERT_THAT(GetExportedTypeProps(currentAssembly, exportedType, &name, &implementation), testing::ElementsAreArray(GetExportedTypeProps(baselineAssembly, exportedType))); + ASSERT_THAT( + FindExportedTypeByName(currentAssembly, name.data(), implementation), + testing::ElementsAreArray(FindExportedTypeByName(baselineAssembly, name.data(), implementation))); } - auto resources = ASSERT_AND_RETURN(EnumManifestResources(baselineAssembly), EnumManifestResources(currentAssembly)); + TokenList resources; + ASSERT_EQUAL_AND_SET(resources, EnumManifestResources(baselineAssembly), EnumManifestResources(currentAssembly)); for (auto resource : resources) { std::vector name; - ASSERT_EQUAL(GetManifestResourceProps(baselineAssembly, resource), GetManifestResourceProps(currentAssembly, resource, &name)); - ASSERT_EQUAL(FindManifestResourceByName(baselineAssembly, name.data()), FindManifestResourceByName(currentAssembly, name.data())); + ASSERT_THAT(GetManifestResourceProps(currentAssembly, resource, &name), testing::ElementsAreArray(GetManifestResourceProps(baselineAssembly, resource))); + ASSERT_THAT(FindManifestResourceByName(currentAssembly, name.data()), testing::ElementsAreArray(FindManifestResourceByName(baselineAssembly, name.data()))); } - - END_TEST(); } -EXPORT -TestResult UnitLongRunningAPIs(void const* data, uint32_t dataLen) +INSTANTIATE_TEST_SUITE_P(MetaDataImportTestCore, MetadataImportTest, testing::ValuesIn(MetadataFilesInDirectory(GetBaselineDirectory())), PrintName); + +INSTANTIATE_TEST_SUITE_P(MetaDataImportTestFx4_0, MetadataImportTest, testing::ValuesIn(MetadataFilesInDirectory(FindFrameworkInstall("v4.0.30319"))), PrintName); +INSTANTIATE_TEST_SUITE_P(MetaDataImportTestFx2_0, MetadataImportTest, testing::ValuesIn(MetadataFilesInDirectory(FindFrameworkInstall("v2.0.50727"))), PrintName); + +INSTANTIATE_TEST_SUITE_P(MetaDataImportTest_IndirectionTables, MetadataImportTest, testing::Values(MetadataFile{ MetadataFile::Kind::Generated, IndirectionTablesKey }), PrintName); + +class MetaDataLongRunningTest : public RegressionTest +{ +}; + +TEST_P(MetaDataLongRunningTest, ImportAPIs) { - BEGIN_TEST(); + auto param = GetParam(); + span blob = GetMetadataForFile(param); + void const* data = blob; + uint32_t dataLen = (uint32_t)blob.size(); // Load metadata dncp::com_ptr baselineImport; - ASSERT_EQUAL(S_OK, CreateImport(g_baselineDisp, data, dataLen, &baselineImport)); - dncp::com_ptr currentImport; - ASSERT_EQUAL(S_OK, CreateImport(g_currentDisp, data, dataLen, ¤tImport)); - - static auto VerifyFindMemberRef = [](IMetaDataImport2* import, mdToken memberRef) -> std::vector - { - std::vector values; + ASSERT_HRESULT_SUCCEEDED(CreateImport(TestBaseline::Metadata, data, dataLen, &baselineImport)); - mdToken ptk; - static_char_buffer name{}; - ULONG pchMember; - PCCOR_SIGNATURE ppvSigBlob; - ULONG pcbSigBlob; - HRESULT hr = import->GetMemberRefProps(memberRef, - &ptk, - name.data(), - (ULONG)name.size(), - &pchMember, - &ppvSigBlob, - &pcbSigBlob); - values.push_back(hr); - if (hr == S_OK) - { - // We were able to get the name, now try looking up a memberRef by name and by sig - mdMemberRef lookup = mdTokenNil; - hr = import->FindMemberRef(ptk, name.data(), ppvSigBlob, pcbSigBlob, &lookup); - values.push_back(hr); - values.push_back(lookup); - lookup = mdTokenNil; - hr = import->FindMemberRef(ptk, name.data(), nullptr, 0, &lookup); - values.push_back(hr); - values.push_back(lookup); - lookup = mdTokenNil; - hr = import->FindMemberRef(ptk, nullptr, ppvSigBlob, pcbSigBlob, &lookup); + dncp::com_ptr dispenser; + ASSERT_HRESULT_SUCCEEDED(GetDispenser(IID_IMetaDataDispenser, (void**)&dispenser)); + dncp::com_ptr currentImport; + ASSERT_HRESULT_SUCCEEDED(CreateImport(dispenser, data, dataLen, ¤tImport)); + + static auto VerifyFindMemberRef = [](IMetaDataImport2 * import, mdToken memberRef) -> std::vector + { + std::vector values; + + mdToken ptk; + static_char_buffer name{}; + ULONG pchMember; + PCCOR_SIGNATURE ppvSigBlob; + ULONG pcbSigBlob; + HRESULT hr = import->GetMemberRefProps(memberRef, + & ptk, + name.data(), + (ULONG)name.size(), + & pchMember, + & ppvSigBlob, + & pcbSigBlob); values.push_back(hr); - values.push_back(lookup); - } - return values; - }; + if (hr == S_OK) + { + // We were able to get the name, now try looking up a memberRef by name and by sig + mdMemberRef lookup = mdTokenNil; + hr = import->FindMemberRef(ptk, name.data(), ppvSigBlob, pcbSigBlob, & lookup); + values.push_back(hr); + values.push_back(lookup); + lookup = mdTokenNil; + hr = import->FindMemberRef(ptk, name.data(), nullptr, 0, & lookup); + values.push_back(hr); + values.push_back(lookup); + lookup = mdTokenNil; + hr = import->FindMemberRef(ptk, nullptr, ppvSigBlob, pcbSigBlob, & lookup); + values.push_back(hr); + values.push_back(lookup); + } + return values; + }; size_t stride; size_t count; - auto typedefs = ASSERT_AND_RETURN(EnumTypeDefs(baselineImport), EnumTypeDefs(currentImport)); + TokenList typedefs; + ASSERT_EQUAL_AND_SET(typedefs, EnumTypeDefs(baselineImport), EnumTypeDefs(currentImport)); count = 0; stride = std::max(typedefs.size() / 128, (size_t)16); for (auto typdef : typedefs) @@ -1919,18 +2064,20 @@ TestResult UnitLongRunningAPIs(void const* data, uint32_t dataLen) if (count++ % stride != 0) continue; - ASSERT_EQUAL(EnumMemberRefs(baselineImport, typdef), EnumMemberRefs(currentImport, typdef)); + EXPECT_THAT(EnumMemberRefs(currentImport, typdef), testing::ElementsAreArray(EnumMemberRefs(baselineImport, typdef))); - auto methoddefs = ASSERT_AND_RETURN(EnumMethods(baselineImport, typdef), EnumMethods(currentImport, typdef)); + TokenList methoddefs; + ASSERT_EQUAL_AND_SET(methoddefs, EnumMethods(baselineImport, typdef), EnumMethods(currentImport, typdef)); for (auto methoddef : methoddefs) { - ASSERT_EQUAL(EnumMethodSemantics(baselineImport, methoddef), EnumMethodSemantics(currentImport, methoddef)); + EXPECT_THAT(EnumMethodSemantics(currentImport, methoddef), testing::ElementsAreArray(EnumMethodSemantics(baselineImport, methoddef))); } - ASSERT_EQUAL(EnumCustomAttributes(baselineImport, typdef), EnumCustomAttributes(currentImport, typdef)); + EXPECT_THAT(EnumCustomAttributes(currentImport, typdef), testing::ElementsAreArray(EnumCustomAttributes(baselineImport, typdef))); } - auto typespecs = ASSERT_AND_RETURN(EnumTypeSpecs(baselineImport), EnumTypeSpecs(currentImport)); + TokenList typespecs; + ASSERT_EQUAL_AND_SET(typespecs, EnumTypeSpecs(baselineImport), EnumTypeSpecs(currentImport)); count = 0; stride = std::max(typespecs.size() / 128, (size_t)16); for (auto typespec : typespecs) @@ -1938,178 +2085,14 @@ TestResult UnitLongRunningAPIs(void const* data, uint32_t dataLen) if (count++ % stride != 0) continue; - auto memberrefs = ASSERT_AND_RETURN(EnumMemberRefs(baselineImport, typespec), EnumMemberRefs(currentImport, typespec)); + TokenList memberrefs; + ASSERT_EQUAL_AND_SET(memberrefs, EnumMemberRefs(baselineImport, typespec), EnumMemberRefs(currentImport, typespec)); for (auto memberref : memberrefs) { - ASSERT_EQUAL(GetMemberRefProps(baselineImport, memberref), GetMemberRefProps(currentImport, memberref)); - ASSERT_EQUAL(VerifyFindMemberRef(baselineImport, memberref), VerifyFindMemberRef(currentImport, memberref)); + EXPECT_THAT(GetMemberRefProps(currentImport, memberref), testing::ElementsAreArray(GetMemberRefProps(baselineImport, memberref))); + EXPECT_THAT(VerifyFindMemberRef(currentImport, memberref), testing::ElementsAreArray(VerifyFindMemberRef(baselineImport, memberref))); } } - - END_TEST(); } -EXPORT -TestResult UnitFindAPIs(void const* data, uint32_t dataLen) -{ - BEGIN_TEST(); - - // Load metadata - dncp::com_ptr baselineImport; - ASSERT_EQUAL(S_OK, CreateImport(g_baselineDisp, data, dataLen, &baselineImport)); - dncp::com_ptr currentImport; - ASSERT_EQUAL(S_OK, CreateImport(g_currentDisp, data, dataLen, ¤tImport)); - - static auto FindTokenByName = [](IMetaDataImport2* import, LPCWSTR name, mdToken enclosing = mdTokenNil) -> mdToken - { - mdTypeDef ptd; - ASSERT_EQUAL(S_OK, import->FindTypeDefByName(name, enclosing, &ptd)); - return ptd; - }; - - static auto GetTypeDefBaseToken = [](IMetaDataImport2* import, mdTypeDef tk) -> mdToken - { - static_char_buffer name{}; - ULONG pchTypeDef; - DWORD pdwTypeDefFlags; - mdToken ptkExtends; - ASSERT_EQUAL(S_OK, import->GetTypeDefProps(tk, - name.data(), - (ULONG)name.size(), - &pchTypeDef, - &pdwTypeDefFlags, - &ptkExtends)); - return ptkExtends; - }; - - static auto FindMethodDef = [](IMetaDataImport2* import, mdTypeDef type, LPCWSTR methodName) -> mdToken - { - std::vector methoddefs = EnumMembersWithName(import, type, methodName); - ASSERT_TRUE(!methoddefs.empty()); - return methoddefs[0]; - }; - - static auto FindMemberRef = [](IMetaDataImport2* import, mdTypeDef type, LPCWSTR methodName) -> mdToken - { - auto methodDef = FindMethodDef(import, type, methodName); - mdMemberRef pmr; - ASSERT_EQUAL(S_OK, import->FindMemberRef(methodDef, methodName, nullptr, 0, &pmr)); - return pmr; - }; - - static auto FindMethod = [](IMetaDataImport2* import, mdTypeDef td, LPCWSTR name, void const* pvSigBlob, ULONG cbSigBlob) -> uint32_t - { - mdMethodDef tkMethod; - mdToken tkMember; - ASSERT_EQUAL(S_OK, import->FindMethod(td, name, (PCCOR_SIGNATURE)pvSigBlob, cbSigBlob, &tkMethod)); - ASSERT_EQUAL(S_OK, import->FindMember(td, name, (PCCOR_SIGNATURE)pvSigBlob, cbSigBlob, &tkMember)); - ASSERT_EQUAL(tkMethod, tkMember); - return tkMethod; - }; - - static auto FindField = [](IMetaDataImport2* import, mdTypeDef td, LPCWSTR name, void const* pvSigBlob, ULONG cbSigBlob) -> uint32_t - { - mdFieldDef tkField; - mdToken tkMember; - ASSERT_EQUAL(S_OK, import->FindField(td, name, (PCCOR_SIGNATURE)pvSigBlob, cbSigBlob, &tkField)); - ASSERT_EQUAL(S_OK, import->FindMember(td, name, (PCCOR_SIGNATURE)pvSigBlob, cbSigBlob, &tkMember)); - ASSERT_EQUAL(tkField, tkMember); - return tkField; - }; - - auto tgt = W("C"); - - auto baseTypeDef = W("B1"); - auto tkB1 = ASSERT_AND_RETURN(FindTokenByName(baselineImport, baseTypeDef), FindTokenByName(currentImport, baseTypeDef)); - auto tkB1Base = ASSERT_AND_RETURN(GetTypeDefBaseToken(baselineImport, tkB1), GetTypeDefBaseToken(currentImport, tkB1)); - ASSERT_EQUAL(FindTypeDefByName(baselineImport, tgt, tkB1Base), FindTypeDefByName(currentImport, tgt, tkB1Base)); - - auto baseTypeRef = W("B2"); - auto tkB2 = ASSERT_AND_RETURN(FindTokenByName(baselineImport, baseTypeRef), FindTokenByName(currentImport, baseTypeRef)); - auto tkB2Base = ASSERT_AND_RETURN(GetTypeDefBaseToken(baselineImport, tkB2), GetTypeDefBaseToken(currentImport, tkB2)); - ASSERT_EQUAL(FindTypeDefByName(baselineImport, tgt, tkB2Base), FindTypeDefByName(currentImport, tgt, tkB2Base)); - - auto methodDefName = W("MethodDef"); - auto tkMethodDef = ASSERT_AND_RETURN(FindMethodDef(baselineImport, tkB1Base, methodDefName), FindMethodDef(currentImport, tkB1Base, methodDefName)); - - void const* defSigBlob; - ULONG defSigBlobLength; - ASSERT_EQUAL( - GetMethodProps(baselineImport, tkMethodDef), - GetMethodProps(currentImport, tkMethodDef, &defSigBlob, &defSigBlobLength)); - ASSERT_EQUAL( - FindMethod(baselineImport, tkB1Base, methodDefName, defSigBlob, defSigBlobLength), - FindMethod(currentImport, tkB1Base, methodDefName, defSigBlob, defSigBlobLength)); - - auto methodRef1Name = W("MethodRef1"); - auto tkMemberRefNoVarArgsBase = ASSERT_AND_RETURN( - FindMemberRef(baselineImport, tkB1Base, methodRef1Name), - FindMemberRef(currentImport, tkB1Base, methodRef1Name)); - - PCCOR_SIGNATURE ref1Blob; - ULONG ref1BlobLength; - ASSERT_EQUAL( - GetMemberRefProps(baselineImport, tkMemberRefNoVarArgsBase), - GetMemberRefProps(currentImport, tkMemberRefNoVarArgsBase, &ref1Blob, &ref1BlobLength)); - ASSERT_EQUAL( - FindMethod(baselineImport, tkB1Base, methodRef1Name, ref1Blob, ref1BlobLength), - FindMethod(currentImport, tkB1Base, methodRef1Name, ref1Blob, ref1BlobLength)); - - auto methodRef2Name = W("MethodRef2"); - auto tkMemberRefVarArgsBase = ASSERT_AND_RETURN( - FindMemberRef(baselineImport, tkB1Base, methodRef2Name), - FindMemberRef(currentImport, tkB1Base, methodRef2Name)); - - PCCOR_SIGNATURE ref2Blob; - ULONG ref2BlobLength; - ASSERT_EQUAL( - GetMemberRefProps(baselineImport, tkMemberRefVarArgsBase), - GetMemberRefProps(currentImport, tkMemberRefVarArgsBase, &ref2Blob, &ref2BlobLength)); - ASSERT_EQUAL( - FindMethod(baselineImport, tkB1Base, methodRef2Name, ref2Blob, ref2BlobLength), - FindMethod(currentImport, tkB1Base, methodRef2Name, ref2Blob, ref2BlobLength)); - - auto fieldName = W("Field1"); - auto tkFields = ASSERT_AND_RETURN( - EnumFieldsWithName(baselineImport, tkB2, fieldName), - EnumFieldsWithName(currentImport, tkB2, fieldName)); - ASSERT_TRUE(!tkFields.empty()); - mdToken tkField = tkFields[0]; - - void const* sigBlob; - ULONG sigBlobLength; - ASSERT_EQUAL( - GetFieldProps(baselineImport, tkField), - GetFieldProps(currentImport, tkField, &sigBlob, &sigBlobLength)); - ASSERT_EQUAL( - FindField(baselineImport, tkB2, fieldName, sigBlob, sigBlobLength), - FindField(currentImport, tkB2, fieldName, sigBlob, sigBlobLength)); - - END_TEST(); -} - -EXPORT -TestResult UnitImportAPIsIndirectionTables(void const* data, uint32_t dataLen, void const** deltaImages, uint32_t* deltaImageLengths, uint32_t numDeltaImages) -{ - BEGIN_TEST(); - - dncp::com_ptr baseImageEmit; - ASSERT_EQUAL(S_OK, CreateEmit(g_deltaImageBuilder, data, dataLen, &baseImageEmit)); - - for (uint32_t i = 0; i < numDeltaImages; ++i) - { - dncp::com_ptr deltaImport; - ASSERT_EQUAL(S_OK, CreateImport(g_deltaImageBuilder, deltaImages[i], deltaImageLengths[i], &deltaImport)); - ASSERT_EQUAL(S_OK, baseImageEmit->ApplyEditAndContinue(deltaImport)); - } - - DWORD compositeImageSize; - ASSERT_EQUAL(S_OK, baseImageEmit->GetSaveSize(CorSaveSize::cssAccurate, &compositeImageSize)); - - std::unique_ptr compositeImage = std::make_unique(compositeImageSize); - ASSERT_EQUAL(S_OK, baseImageEmit->SaveToMemory(compositeImage.get(), compositeImageSize)); - - return UnitImportAPIs(compositeImage.get(), compositeImageSize); - - END_DELEGATING_TEST(); -} +INSTANTIATE_TEST_SUITE_P(MetaDataLongRunningTest_CoreLibs, MetaDataLongRunningTest, testing::ValuesIn(CoreLibFiles()), PrintName);