From 69657888082794ce934823961bc61213e7ff52fd Mon Sep 17 00:00:00 2001 From: Rasmus Kuusmann Date: Tue, 20 Jul 2021 20:46:07 +0300 Subject: [PATCH] [POC] Add native tests (#201) * Add native tests * Add nuke for windows * Nuke publish linux profiler * fix scripts and add ci * fix file permissions * Add nuke MacOS steps * Add missing script for linux * Fix Linux native lib name * Add dotnet tool manifest * Fix macos native profiler lib name * update nuke schema * Update docs about build * Update docs * fix gitignore * refactor targets * remove overleft sln * Remove GitLab helpers * Cleanup unused methods * Move developer notes to docs * remove incorrect duplicated directory * fix scripts and gitignore * Update vcxproj tools version * fix example doc for LazyPathExecutableAttribute * Cleanup & formatting * fix log path script --- .config/dotnet-tools.json | 12 + .github/workflows/ci.yml | 44 ++ .github/workflows/workflow.yml | 2 +- .gitignore | 6 + .nuke/.gitkeep | 0 Datadog.Trace.proj | 7 + Datadog.Trace.sln | 86 +++- build.cmd | 7 + build.ps1 | 69 +++ build.sh | 118 ++--- build/artifacts/createLogPath.sh | 5 + build/nuke/.editorconfig | 11 + .../Attributes/LazyPathExecutableAttribute.cs | 39 ++ build/nuke/Build.Steps.Linux.cs | 62 +++ build/nuke/Build.Steps.MacOS.cs | 37 ++ build/nuke/Build.Steps.Windows.cs | 88 ++++ build/nuke/Build.Steps.cs | 156 ++++++ build/nuke/Build.cs | 88 ++++ build/nuke/Configuration.cs | 15 + build/nuke/Directory.Build.props | 8 + build/nuke/Directory.Build.targets | 8 + build/nuke/DotNetMSBuildSettings.cs | 21 + build/nuke/DotNetMSBuildTasks.cs | 32 ++ .../Extensions/DotNetSettingsExtensions.cs | 22 + build/nuke/Projects.cs | 6 + build/nuke/TargetFramework.cs | 47 ++ build/nuke/_build.csproj | 21 + build/nuke/_build.csproj.DotSettings | 26 + build_poc.sh | 62 +++ docs/DEVELOPING.md | 80 ++- poc.sh | 2 +- ...tadog.Trace.ClrProfiler.Native.DLL.vcxproj | 2 +- .../Datadog.Trace.ClrProfiler.Native.vcxproj | 2 +- ...dog.Trace.ClrProfiler.Native.Tests.vcxproj | 199 ++++++++ .../Directory.Build.props | 2 + .../clr_helper_test.cpp | 454 ++++++++++++++++++ .../clr_helper_type_check_test.cpp | 123 +++++ .../integration_loader_test.cpp | 225 +++++++++ .../integration_test.cpp | 46 ++ .../metadata_builder_test.cpp | 157 ++++++ .../packages.config | 5 + .../pch.cpp | 6 + .../pch.h | 18 + .../test_helpers.h | 77 +++ .../version_struct_test.cpp | 50 ++ .../Samples.ExampleLibrary/Class1.cs | 132 +++++ .../FakeClient/Biscuit.cs | 29 ++ .../FakeClient/DogClient.cs | 76 +++ .../FakeClient/DogTrick.cs | 14 + .../GenericTests/ComprehensiveCaller.cs | 136 ++++++ .../GenericTests/GenericTarget.cs | 25 + .../GenericTests/PointStruct.cs | 14 + .../GenericTests/StructContainer.cs | 17 + .../GlobalSuppressions.cs | 15 + .../Samples.ExampleLibrary.csproj | 7 + .../Samples.ExampleLibraryTracer/Class1.cs | 17 + .../Samples.ExampleLibraryTracer.csproj | 7 + 57 files changed, 2931 insertions(+), 111 deletions(-) create mode 100644 .config/dotnet-tools.json create mode 100644 .github/workflows/ci.yml create mode 100644 .nuke/.gitkeep create mode 100644 build.cmd create mode 100644 build.ps1 create mode 100644 build/artifacts/createLogPath.sh create mode 100644 build/nuke/.editorconfig create mode 100644 build/nuke/Attributes/LazyPathExecutableAttribute.cs create mode 100644 build/nuke/Build.Steps.Linux.cs create mode 100644 build/nuke/Build.Steps.MacOS.cs create mode 100644 build/nuke/Build.Steps.Windows.cs create mode 100644 build/nuke/Build.Steps.cs create mode 100644 build/nuke/Build.cs create mode 100644 build/nuke/Configuration.cs create mode 100644 build/nuke/Directory.Build.props create mode 100644 build/nuke/Directory.Build.targets create mode 100644 build/nuke/DotNetMSBuildSettings.cs create mode 100644 build/nuke/DotNetMSBuildTasks.cs create mode 100644 build/nuke/Extensions/DotNetSettingsExtensions.cs create mode 100644 build/nuke/Projects.cs create mode 100644 build/nuke/TargetFramework.cs create mode 100644 build/nuke/_build.csproj create mode 100644 build/nuke/_build.csproj.DotSettings create mode 100755 build_poc.sh create mode 100644 test/Datadog.Trace.ClrProfiler.Native.Tests/Datadog.Trace.ClrProfiler.Native.Tests.vcxproj create mode 100644 test/Datadog.Trace.ClrProfiler.Native.Tests/Directory.Build.props create mode 100644 test/Datadog.Trace.ClrProfiler.Native.Tests/clr_helper_test.cpp create mode 100644 test/Datadog.Trace.ClrProfiler.Native.Tests/clr_helper_type_check_test.cpp create mode 100644 test/Datadog.Trace.ClrProfiler.Native.Tests/integration_loader_test.cpp create mode 100644 test/Datadog.Trace.ClrProfiler.Native.Tests/integration_test.cpp create mode 100644 test/Datadog.Trace.ClrProfiler.Native.Tests/metadata_builder_test.cpp create mode 100644 test/Datadog.Trace.ClrProfiler.Native.Tests/packages.config create mode 100644 test/Datadog.Trace.ClrProfiler.Native.Tests/pch.cpp create mode 100644 test/Datadog.Trace.ClrProfiler.Native.Tests/pch.h create mode 100644 test/Datadog.Trace.ClrProfiler.Native.Tests/test_helpers.h create mode 100644 test/Datadog.Trace.ClrProfiler.Native.Tests/version_struct_test.cpp create mode 100644 test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/Class1.cs create mode 100644 test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/FakeClient/Biscuit.cs create mode 100644 test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/FakeClient/DogClient.cs create mode 100644 test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/FakeClient/DogTrick.cs create mode 100644 test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GenericTests/ComprehensiveCaller.cs create mode 100644 test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GenericTests/GenericTarget.cs create mode 100644 test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GenericTests/PointStruct.cs create mode 100644 test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GenericTests/StructContainer.cs create mode 100644 test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GlobalSuppressions.cs create mode 100644 test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/Samples.ExampleLibrary.csproj create mode 100644 test/test-applications/integrations/dependency-libs/Samples.ExampleLibraryTracer/Class1.cs create mode 100644 test/test-applications/integrations/dependency-libs/Samples.ExampleLibraryTracer/Samples.ExampleLibraryTracer.csproj diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000000..dc1667eb25 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "nuke.globaltool": { + "version": "5.2.1", + "commands": [ + "nuke" + ] + } + } +} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..9f5cde9978 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +# ------------------------------------------------------------------------------ +# +# +# This code was generated. +# +# - To turn off auto-generation set: +# +# [GitHubActions (AutoGenerate = false)] +# +# - To trigger manual generation invoke: +# +# nuke --generate-configuration GitHubActions_ci --host GitHubActions +# +# +# ------------------------------------------------------------------------------ + +name: ci + +on: + push: + branches: + - main + - 'refs/tags/*' + paths: + - '!docs/*' + pull_request: + branches: + - '*' + +jobs: + windows-latest: + name: windows-latest + runs-on: windows-latest + steps: + - uses: actions/checkout@v1 + - name: Cache .nuke/temp, ~/.nuget/packages + uses: actions/cache@v2 + with: + path: | + .nuke/temp + ~/.nuget/packages + key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj') }} + - name: Run './build.cmd Workflow' + run: ./build.cmd Workflow diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 17e753ef8c..b4a4d015b4 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -22,4 +22,4 @@ jobs: env: buildConfiguration: Release shell: bash - run: ./build.sh + run: ./build_poc.sh diff --git a/.gitignore b/.gitignore index 41ddc0a7df..ad6fb0e9e7 100644 --- a/.gitignore +++ b/.gitignore @@ -282,3 +282,9 @@ src/Datadog.Trace.ClrProfiler.Native/build/ # ignore blog folder from upstream blog/ + +# ignore nuke config +.nuke/* + +# exception to the ignore rule +!/**/.gitkeep \ No newline at end of file diff --git a/.nuke/.gitkeep b/.nuke/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Datadog.Trace.proj b/Datadog.Trace.proj index ce7de00865..640d59137a 100644 --- a/Datadog.Trace.proj +++ b/Datadog.Trace.proj @@ -8,6 +8,7 @@ + @@ -52,6 +53,12 @@ + + + + + + + + + + diff --git a/build/nuke/Directory.Build.targets b/build/nuke/Directory.Build.targets new file mode 100644 index 0000000000..253260956d --- /dev/null +++ b/build/nuke/Directory.Build.targets @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build/nuke/DotNetMSBuildSettings.cs b/build/nuke/DotNetMSBuildSettings.cs new file mode 100644 index 0000000000..8343942bdd --- /dev/null +++ b/build/nuke/DotNetMSBuildSettings.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.Build.Tasks; +using Nuke.Common.Tooling; +using Nuke.Common.Tools.DotNet; +using Nuke.Common.Tools.MSBuild; + +[Serializable] +public class DotNetMSBuildSettings : MSBuildSettings +{ + /// + /// Path to the DotNet executable. + /// + public override string ProcessToolPath => DotNetTasks.DotNetPath; + public override Action ProcessCustomLogger => DotNetTasks.DotNetLogger; + protected override Arguments ConfigureProcessArguments(Arguments arguments) + { + arguments + .Add("msbuild"); + return base.ConfigureProcessArguments(arguments); + } +} diff --git a/build/nuke/DotNetMSBuildTasks.cs b/build/nuke/DotNetMSBuildTasks.cs new file mode 100644 index 0000000000..48fc038524 --- /dev/null +++ b/build/nuke/DotNetMSBuildTasks.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using Nuke.Common.Tooling; + +public static class DotNetMSBuildTasks +{ + /// + ///

The dotnet msbuild command allows access to a fully functional MSBuild.

The command has the exact same capabilities as the existing MSBuild command-line client for SDK-style projects only. The options are all the same. For more information about the available options, see the [MSBuild command-line reference](/visualstudio/msbuild/msbuild-command-line-reference).

The [dotnet build](dotnet-build.md) command is equivalent to dotnet msbuild -restore. When you don't want to build the project and you have a specific target you want to run, use dotnet build or >dotnet msbuild and specify the target.

+ ///

For more details, visit the official website.

+ ///
+ /// + ///

This is a CLI wrapper with fluent API that allows to modify the following arguments:

+ ///
+ public static IReadOnlyCollection DotNetMSBuild(DotNetMSBuildSettings toolSettings = null) + { + toolSettings = toolSettings ?? new DotNetMSBuildSettings(); + using var process = ProcessTasks.StartProcess(toolSettings); + process.AssertZeroExitCode(); + return process.Output; + } + + /// + ///

The dotnet msbuild command allows access to a fully functional MSBuild.

The command has the exact same capabilities as the existing MSBuild command-line client for SDK-style projects only. The options are all the same. For more information about the available options, see the [MSBuild command-line reference](/visualstudio/msbuild/msbuild-command-line-reference).

The [dotnet build](dotnet-build.md) command is equivalent to dotnet msbuild -restore. When you don't want to build the project and you have a specific target you want to run, use dotnet build or >dotnet msbuild and specify the target.

+ ///

For more details, visit the official website.

+ ///
+ /// + ///

This is a CLI wrapper with fluent API that allows to modify the following arguments:

+ ///
+ public static IReadOnlyCollection DotNetMSBuild(Configure configurator) + { + return DotNetMSBuild(configurator(new DotNetMSBuildSettings())); + } +} diff --git a/build/nuke/Extensions/DotNetSettingsExtensions.cs b/build/nuke/Extensions/DotNetSettingsExtensions.cs new file mode 100644 index 0000000000..dfdd9f9e09 --- /dev/null +++ b/build/nuke/Extensions/DotNetSettingsExtensions.cs @@ -0,0 +1,22 @@ +using Nuke.Common.Tools.DotNet; +using Nuke.Common.Tools.MSBuild; + +internal static class DotNetSettingsExtensions +{ + public static DotNetPublishSettings SetTargetPlatformAnyCPU(this DotNetPublishSettings settings) + => settings.SetTargetPlatform(MSBuildTargetPlatform.MSIL); + + public static T SetTargetPlatformAnyCPU(this T settings) + where T : MSBuildSettings + => settings.SetTargetPlatform(MSBuildTargetPlatform.MSIL); + + public static DotNetPublishSettings SetTargetPlatform(this DotNetPublishSettings settings, MSBuildTargetPlatform platform) + { + return platform is null + ? settings + : settings.SetProperty("Platform", GetTargetPlatform(platform)); + } + + private static string GetTargetPlatform(MSBuildTargetPlatform platform) => + platform == MSBuildTargetPlatform.MSIL ? "AnyCPU" : platform.ToString(); +} diff --git a/build/nuke/Projects.cs b/build/nuke/Projects.cs new file mode 100644 index 0000000000..f126c8c7c7 --- /dev/null +++ b/build/nuke/Projects.cs @@ -0,0 +1,6 @@ +public static class Projects +{ + public const string ClrProfilerManaged = "OpenTelemetry.AutoInstrumentation.ClrProfiler.Managed"; + public const string ClrProfilerManagedCore = "OpenTelemetry.AutoInstrumentation.ClrProfiler.Managed.Core"; + public const string ClrProfilerNative = "Datadog.Trace.ClrProfiler.Native"; +} diff --git a/build/nuke/TargetFramework.cs b/build/nuke/TargetFramework.cs new file mode 100644 index 0000000000..8a4a955f64 --- /dev/null +++ b/build/nuke/TargetFramework.cs @@ -0,0 +1,47 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Reflection; +using Nuke.Common; +using Nuke.Common.Tooling; + +[TypeConverter(typeof(TargetFrameworkTypeConverter))] +public class TargetFramework : Enumeration +{ + public static TargetFramework NET452 = new TargetFramework { Value = "net452" }; + public static TargetFramework NET461 = new TargetFramework { Value = "net461" }; + public static TargetFramework NETSTANDARD2_0 = new TargetFramework { Value = "netstandard2.0" }; + public static TargetFramework NETCOREAPP2_1 = new TargetFramework { Value = "netcoreapp2.1" }; + public static TargetFramework NETCOREAPP3_0 = new TargetFramework { Value = "netcoreapp3.0" }; + public static TargetFramework NETCOREAPP3_1 = new TargetFramework { Value = "netcoreapp3.1" }; + public static TargetFramework NET5_0 = new TargetFramework { Value = "net5.0" }; + + public static implicit operator string(TargetFramework framework) + { + return framework.Value; + } + + public class TargetFrameworkTypeConverter : TypeConverter + { + private static readonly TargetFramework[] AllTargetFrameworks = typeof(TargetFramework) + .GetFields(BindingFlags.Static | BindingFlags.Public) + .Select(x => x.GetValue(null)) + .Cast() + .ToArray(); + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string stringValue) + { + var matchingFields = AllTargetFrameworks + .Where(x => string.Equals(x.Value, stringValue, StringComparison.OrdinalIgnoreCase)) + .ToList(); + ControlFlow.Assert(matchingFields.Count == 1, "matchingFields.Count == 1"); + return matchingFields.Single(); + } + + return base.ConvertFrom(context, culture, value); + } + } +} diff --git a/build/nuke/_build.csproj b/build/nuke/_build.csproj new file mode 100644 index 0000000000..e9508df42c --- /dev/null +++ b/build/nuke/_build.csproj @@ -0,0 +1,21 @@ + + + + Exe + net5.0 + + CS0649;CS0169 + ..\.. + ..\.. + 1 + + + + + + + + + + + diff --git a/build/nuke/_build.csproj.DotSettings b/build/nuke/_build.csproj.DotSettings new file mode 100644 index 0000000000..c8947fcec7 --- /dev/null +++ b/build/nuke/_build.csproj.DotSettings @@ -0,0 +1,26 @@ + + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + Implicit + Implicit + ExpressionBody + 0 + NEXT_LINE + True + False + 120 + IF_OWNER_IS_SINGLE_LINE + WRAP_IF_LONG + False + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True + True + True + True + True diff --git a/build_poc.sh b/build_poc.sh new file mode 100755 index 0000000000..658c6da51a --- /dev/null +++ b/build_poc.sh @@ -0,0 +1,62 @@ +#!/bin/bash +set -euxo pipefail + +uname_os() { + os=$(uname -s | tr '[:upper:]' '[:lower:]') + case "$os" in + cygwin_nt*) echo "windows" ;; + mingw*) echo "windows" ;; + msys_nt*) echo "windows" ;; + *) echo "$os" ;; + esac +} + +native_sufix() { + os=$(uname_os) + case "$os" in + windows*) echo "dll" ;; + linux*) echo "so" ;; + darwin*) echo "dylib" ;; + *) echo "OS: ${os} is not supported" ; exit 1 ;; + esac +} + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd $DIR + +BUILD_TYPE=${buildConfiguration:-Debug} +OUTDIR="$( pwd )/src/Datadog.Trace.ClrProfiler.Native/bin/${BUILD_TYPE}/x64" + +# build Loader +dotnet build -c $BUILD_TYPE src/Datadog.Trace.ClrProfiler.Managed.Loader/Datadog.Trace.ClrProfiler.Managed.Loader.csproj + +# build Native +os=$(uname_os) +case "$os" in + windows*) + SDK_TARGET_FRAMEWORKS="net452 net461 netstandard2.0 netcoreapp3.1" + nuget restore "src\Datadog.Trace.ClrProfiler.Native\Datadog.Trace.ClrProfiler.Native.vcxproj" -SolutionDirectory . + msbuild.exe Datadog.Trace.proj -t:BuildCpp -p:Configuration=${BUILD_TYPE} -p:Platform=x64 + ;; + + *) + SDK_TARGET_FRAMEWORKS="netstandard2.0 netcoreapp3.1" + cd src/Datadog.Trace.ClrProfiler.Native + + mkdir -p build + (cd build && cmake ../ -DCMAKE_BUILD_TYPE=${BUILD_TYPE} && make) + + cd $DIR + SUFIX=$(native_sufix) + mkdir -p ${OUTDIR} + cp -f src/Datadog.Trace.ClrProfiler.Native/build/bin/Datadog.Trace.ClrProfiler.Native.${SUFIX} ${OUTDIR}/OpenTelemetry.AutoInstrumentation.ClrProfiler.Native.${SUFIX} +esac + +# build Managed +cd $DIR + +for framework in ${SDK_TARGET_FRAMEWORKS} ; do + mkdir -p "$OUTDIR/$framework" + dotnet publish -f $framework -c ${BUILD_TYPE} src/OpenTelemetry.AutoInstrumentation.ClrProfiler.Managed/OpenTelemetry.AutoInstrumentation.ClrProfiler.Managed.csproj -o "$OUTDIR/$framework" + dotnet publish -f $framework -c ${BUILD_TYPE} samples/Vendor.Distro/Vendor.Distro.csproj -o "$OUTDIR/$framework" +done diff --git a/docs/DEVELOPING.md b/docs/DEVELOPING.md index 7070590c17..3cebd7cc3d 100644 --- a/docs/DEVELOPING.md +++ b/docs/DEVELOPING.md @@ -25,39 +25,6 @@ Microsoft provides [evaluation developer VMs](https://developer.microsoft.com/en-us/windows/downloads/virtual-machines) with Windows 10 and Visual Studio pre-installed. -### Building from a command line - -From a _Developer Command Prompt for VS 2019_: - -```cmd -rem Restore NuGet packages -rem nuget.exe is required for command line restore because msbuild doesn't support packages.config -rem (see https://github.com/NuGet/Home/issues/7386) -nuget restore Datadog.Trace.sln - -rem Build C# projects (Platform: always AnyCPU) -msbuild Datadog.Trace.proj /t:BuildCsharp /p:Configuration=Release - -rem Build NuGet packages -dotnet pack src\Datadog.Trace\Datadog.Trace.csproj -dotnet pack src\Datadog.Trace.OpenTracing\Datadog.Trace.OpenTracing.csproj - -rem Build C++ projects -rem The native profiler depends on the Datadog.Trace.ClrProfiler.Managed.Loader C# project so be sure that is built first -msbuild Datadog.Trace.proj /t:BuildCpp /p:Configuration=Release;Platform=x64 -msbuild Datadog.Trace.proj /t:BuildCpp /p:Configuration=Release;Platform=x86 - -rem Build MSI installer for Windows x64 (supports both x64 and x86 apps) -msbuild Datadog.Trace.proj /t:msi /p:Configuration=Release;Platform=x64 - -rem Build MSI installer for Windows x86 (supports x86 apps only) -msbuild Datadog.Trace.proj /t:msi /p:Configuration=Release;Platform=x86 - -rem Build tracer home directory for Windows. -rem Valid values for property `Platform` are `x64`, `x86`, and `All`. -msbuild Datadog.Trace.proj /t:CreateHomeDirectory /p:Configuration=Release;Platform=All -``` - ## Linux and MacOS ### Minimum requirements @@ -72,19 +39,6 @@ To build everything and run integration tests - [Docker](https://docs.docker.com/engine/install/) - [Docker Compose](https://docs.docker.com/compose/install/) -### Building - -```sh -./build.sh -``` - -For Windows make sure to add `msbuild` to your `PATH`. -You can do it by adding to `~/.bashrc` something more or less like bellow: - -```sh -PATH="$PATH:/c/Program Files (x86)/Microsoft Visual Studio/2019/Professional/MSBuild/Current/Bin" -``` - ## Visual Studio Code This repository contains example configuration for VS Code located under `.vscode.example`. You can copy it to `.vscode`. @@ -111,6 +65,40 @@ cp -r .devcontainer.example .devcontainer The Development Container configuration mixes [Docker in Docker](https://github.com/microsoft/vscode-dev-containers/tree/master/containers/docker-in-docker) and [C# (.NET)](https://github.com/microsoft/vscode-dev-containers/tree/master/containers/dotnet) definitions. Thanks to it you can use `docker` and `docker-compose` inside the container. +## Building from a command line + +This repository uses [Nuke](https://nuke.build/) for build automation. + +Support plugins are available for: + - JetBrains ReSharper https://nuke.build/resharper + - JetBrains Rider https://nuke.build/rider + - Microsoft VisualStudio https://nuke.build/visualstudio + - Microsoft VSCode https://nuke.build/vscode + +Restore dotnet tools to prepare build tools for solution. This will install dotnet nuke tool locally. + +```cmd +dotnet tool restore +``` + +To see a list of possible targets and configurations run: + +```cmd +dotnet nuke --help +``` + +To build using default target run: + +```cmd +dotnet nuke +``` + +To build using specific target run: + +```cmd +dotnet nuke --target TargetNameHere +``` + ## Integration tests You can use [Docker Compose](https://docs.docker.com/compose/) with Linux containers to build Linux binaries and run the test suites. This works on both Windows, Linux and MacOS hosts. diff --git a/poc.sh b/poc.sh index 520894ea7a..44e49e4d12 100755 --- a/poc.sh +++ b/poc.sh @@ -16,7 +16,7 @@ function finish { trap finish EXIT # build managed and native code -./build.sh +./build_poc.sh # start mongodb docker run -d --rm --name mongo \ diff --git a/src/Datadog.Trace.ClrProfiler.Native/Datadog.Trace.ClrProfiler.Native.DLL.vcxproj b/src/Datadog.Trace.ClrProfiler.Native/Datadog.Trace.ClrProfiler.Native.DLL.vcxproj index 38e2d46e2b..805c191d7f 100644 --- a/src/Datadog.Trace.ClrProfiler.Native/Datadog.Trace.ClrProfiler.Native.DLL.vcxproj +++ b/src/Datadog.Trace.ClrProfiler.Native/Datadog.Trace.ClrProfiler.Native.DLL.vcxproj @@ -1,5 +1,5 @@ - + Debug diff --git a/src/Datadog.Trace.ClrProfiler.Native/Datadog.Trace.ClrProfiler.Native.vcxproj b/src/Datadog.Trace.ClrProfiler.Native/Datadog.Trace.ClrProfiler.Native.vcxproj index 46b122dc4b..e7b8f604cc 100644 --- a/src/Datadog.Trace.ClrProfiler.Native/Datadog.Trace.ClrProfiler.Native.vcxproj +++ b/src/Datadog.Trace.ClrProfiler.Native/Datadog.Trace.ClrProfiler.Native.vcxproj @@ -1,5 +1,5 @@  - + Debug diff --git a/test/Datadog.Trace.ClrProfiler.Native.Tests/Datadog.Trace.ClrProfiler.Native.Tests.vcxproj b/test/Datadog.Trace.ClrProfiler.Native.Tests/Datadog.Trace.ClrProfiler.Native.Tests.vcxproj new file mode 100644 index 0000000000..2a5153d24b --- /dev/null +++ b/test/Datadog.Trace.ClrProfiler.Native.Tests/Datadog.Trace.ClrProfiler.Native.Tests.vcxproj @@ -0,0 +1,199 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {5728056a-51aa-4ff5-ad0c-e86e44e36102} + Win32Proj + 10.0 + Application + v142 + Unicode + v4.5 + ..\..\src\Datadog.Trace.ClrProfiler.Native\lib\ + x64 + x86 + $(LIB_PATH)fmt_$(LIB_PLATFORM)-windows-static\include;$(LIB_PATH)spdlog\include + $(LIB_PATH)fmt_$(LIB_PLATFORM)-windows-static\lib\fmt.lib + $(LIB_PATH)fmt_$(LIB_PLATFORM)-windows-static\debug\lib\fmtd.lib + + + + true + + + true + + + + + + + + bin\$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + bin\$(Configuration)\x86\ + obj\$(Configuration)\x86\ + + + bin\$(Configuration)\x86\ + obj\$(Configuration)\x86\ + + + bin\$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + $(OutDir) + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + + {4b243cf1-4269-45c6-a238-1a9bfa58b8cc} + + + {fdb5c8d0-018d-4ff9-9680-c6a5078f819b} + + + {91b6272f-5780-4c94-8071-dbba7b4f67f3} + + + + + + + + + + + Disabled + EnableFastChecks + MultiThreadedDebug + stdcpp17 + true + pch.h + Level3 + true + $(LIB_INCLUDES);%(AdditionalIncludeDirectories) + + + true + Console + $(LIB_INCLUDES);%(AdditionalLibraryDirectories) + $(LIB_BINARIES);%(AdditionalDependencies) + + + + + Disabled + EnableFastChecks + MultiThreadedDebug + stdcpp17 + false + true + pch.h + Level3 + true + $(LIB_INCLUDES);%(AdditionalIncludeDirectories) + + + true + Console + $(LIB_INCLUDES);%(AdditionalLibraryDirectories) + $(LIB_BINARIES);%(AdditionalDependencies) + + + + + MultiThreaded + Level3 + stdcpp17 + AnySuitable + true + Speed + true + true + true + pch.h + true + $(LIB_INCLUDES);%(AdditionalIncludeDirectories) + + + true + Console + true + true + $(LIB_INCLUDES);%(AdditionalLibraryDirectories) + $(LIB_BINARIES);%(AdditionalDependencies) + + + + + MultiThreaded + Level3 + stdcpp17 + AnySuitable + true + Speed + true + true + true + pch.h + true + $(LIB_INCLUDES);%(AdditionalIncludeDirectories) + + + true + Console + true + true + $(LIB_INCLUDES);%(AdditionalLibraryDirectories) + $(LIB_BINARIES);%(AdditionalDependencies) + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/test/Datadog.Trace.ClrProfiler.Native.Tests/Directory.Build.props b/test/Datadog.Trace.ClrProfiler.Native.Tests/Directory.Build.props new file mode 100644 index 0000000000..8c119d5413 --- /dev/null +++ b/test/Datadog.Trace.ClrProfiler.Native.Tests/Directory.Build.props @@ -0,0 +1,2 @@ + + diff --git a/test/Datadog.Trace.ClrProfiler.Native.Tests/clr_helper_test.cpp b/test/Datadog.Trace.ClrProfiler.Native.Tests/clr_helper_test.cpp new file mode 100644 index 0000000000..331bb26c22 --- /dev/null +++ b/test/Datadog.Trace.ClrProfiler.Native.Tests/clr_helper_test.cpp @@ -0,0 +1,454 @@ +#include "pch.h" + +#include "../../src/Datadog.Trace.ClrProfiler.Native/clr_helpers.h" +#include "test_helpers.h" + +using namespace trace; + +class CLRHelperTest : public ::CLRHelperTestBase {}; + +TEST_F(CLRHelperTest, EnumeratesTypeDefs) { + std::vector expected_types = { + L"Microsoft.CodeAnalysis.EmbeddedAttribute", + L"System.Runtime.CompilerServices.IsReadOnlyAttribute", + L"Samples.ExampleLibrary.Class1", + L"Samples.ExampleLibrary.GenericTests.ComprehensiveCaller`2", + L"Samples.ExampleLibrary.GenericTests.GenericTarget`2", + L"Samples.ExampleLibrary.GenericTests.PointStruct", + L"Samples.ExampleLibrary.GenericTests.StructContainer`1", + L"Samples.ExampleLibrary.FakeClient.Biscuit`1", + L"Samples.ExampleLibrary.FakeClient.Biscuit", + L"Samples.ExampleLibrary.FakeClient.DogClient`2", + L"Samples.ExampleLibrary.FakeClient.DogTrick`1", + L"Samples.ExampleLibrary.FakeClient.DogTrick", + L"<>c", + L"Cookie", + L"d__4`2", + L"Raisin"}; + + std::vector actual_types; + + for (auto& def : EnumTypeDefs(metadata_import_)) { + std::wstring name(256, 0); + DWORD name_sz = 0; + DWORD flags = 0; + mdToken extends = 0; + auto hr = metadata_import_->GetTypeDefProps( + def, name.data(), (DWORD)(name.size()), &name_sz, &flags, &extends); + ASSERT_TRUE(SUCCEEDED(hr)); + + if (name_sz > 0) { + name = name.substr(0, name_sz - 1); + actual_types.push_back(name); + } + } + + EXPECT_EQ(expected_types, actual_types); +} + +TEST_F(CLRHelperTest, EnumeratesAssemblyRefs) { + std::vector expected_assemblies = { + L"System.Runtime", + L"System.Collections", + L"System.Threading.Tasks", + L"System.Diagnostics.Debug"}; + std::vector actual_assemblies; + for (auto& ref : EnumAssemblyRefs(assembly_import_)) { + auto name = GetReferencedAssemblyMetadata(assembly_import_, ref).name; + if (!name.empty()) { + actual_assemblies.push_back(name); + } + } + EXPECT_EQ(expected_assemblies, actual_assemblies); +} + +TEST_F(CLRHelperTest, FiltersEnabledIntegrations) { + Integration i1 = {L"integration-1", + {{{}, + {L"Samples.ExampleLibrary", + L"SomeType", + L"SomeMethod", + L"ReplaceTargetMethod", + min_ver_, + max_ver_, + {}, + empty_sig_type_}, + {}}}}; + Integration i2 = { + L"integration-2", + {{{}, + {L"Assembly.Two", L"SomeType", L"SomeMethod", L"ReplaceTargetMethod", min_ver_, max_ver_, {}, empty_sig_type_}, + {}}}}; + Integration i3 = { + L"integration-3", + {{{}, {L"System.Runtime", L"", L"", L"ReplaceTargetMethod", min_ver_, max_ver_, {}, empty_sig_type_}, {}}}}; + std::vector all = {i1, i2, i3}; + std::vector expected = {i1, i3}; + std::vector disabled_integrations = {WStr("integration-2")}; + auto actual = FilterIntegrationsByName(all, disabled_integrations); + EXPECT_EQ(actual, expected); +} + +TEST_F(CLRHelperTest, FiltersIntegrationsByCaller) { + Integration i1 = { + L"integration-1", + {{{L"Assembly.One", L"SomeType", L"SomeMethod", L"ReplaceTargetMethod", min_ver_, max_ver_, {}, empty_sig_type_}, + {}, + {}}}}; + Integration i2 = { + L"integration-2", + {{{L"Assembly.Two", L"SomeType", L"SomeMethod", L"ReplaceTargetMethod", min_ver_, max_ver_, {}, empty_sig_type_}, + {}, + {}}}}; + Integration i3 = {L"integration-3", {{{}, {}, {}}}}; + auto all = FlattenIntegrations({i1, i2, i3}, false); + auto expected = FlattenIntegrations({i1, i3}, false); + ModuleID manifest_module_id{}; + AppDomainID app_domain_id{}; + trace::AssemblyInfo assembly_info = { 1, L"Assembly.One", manifest_module_id, app_domain_id, L"AppDomain1"}; + auto actual = FilterIntegrationsByCaller(all, assembly_info); + EXPECT_EQ(actual, expected); +} + +TEST_F(CLRHelperTest, FiltersIntegrationsByTarget) { + Integration i1 = {L"integration-1", + {{{}, + {L"Samples.ExampleLibrary", + L"SomeType", + L"SomeMethod", + L"ReplaceTargetMethod", + min_ver_, + max_ver_, + {}, + empty_sig_type_}, + {}}}}; + Integration i2 = { + L"integration-2", + {{{}, + {L"Assembly.Two", L"SomeType", L"SomeMethod", L"ReplaceTargetMethod", min_ver_, max_ver_, {}, empty_sig_type_}, + {}}}}; + Integration i3 = { + L"integration-3", + {{{}, {L"System.Runtime", L"", L"", L"ReplaceTargetMethod", min_ver_, max_ver_, {}, empty_sig_type_}, {}}}}; + auto all = FlattenIntegrations({i1, i2, i3}, false); + auto expected = FlattenIntegrations({i1, i3}, false); + auto actual = FilterIntegrationsByTarget(all, assembly_import_); + EXPECT_EQ(actual, expected); +} + +TEST_F(CLRHelperTest, FiltersFlattenedIntegrationMethodsByTargetAssembly) { + MethodReplacement included_method = {{}, + {L"Samples.Included", + L"SomeType", + L"SomeMethod", + L"ReplaceTargetMethod", + min_ver_, + max_ver_, + {}, + empty_sig_type_}, + {}}; + + MethodReplacement excluded_method = {{}, + {L"Samples.Excluded", + L"SomeType", + L"SomeMethod", + L"ReplaceTargetMethod", + min_ver_, + max_ver_, + {}, + empty_sig_type_}, + {}}; + + Integration mixed_integration = {L"integration-1", {included_method, excluded_method}}; + Integration included_integration = {L"integration-2", {included_method}}; + Integration excluded_integration = {L"integration-3", {excluded_method}}; + auto all = FlattenIntegrations({mixed_integration, included_integration, excluded_integration}, false); + auto expected = FlattenIntegrations({{L"integration-1", {included_method}}, included_integration}, false); + auto actual = FilterIntegrationsByTargetAssemblyName(all, {L"Samples.Excluded"}); + EXPECT_EQ(actual, expected); +} + +TEST_F(CLRHelperTest, FiltersFlattenedIntegrationMethodsByTarget) { + MethodReference included = {L"Samples.ExampleLibrary", + L"SomeType", + L"SomeMethod", + L"ReplaceTargetMethod", + min_ver_, + max_ver_, + {}, + empty_sig_type_}; + + MethodReference excluded = {L"Samples.ExampleLibrary", + L"SomeType", + L"SomeOtherMethod", + L"ReplaceTargetMethod", + Version(0, 0, 0, 0), + Version(0, 1, 0, 0), + {}, + empty_sig_type_}; + + Integration i1 = {L"integration-1", {{{}, included, {}}, {{}, excluded, {}}}}; + auto all = FlattenIntegrations({i1}, false); + auto filtered = FilterIntegrationsByTarget(all, assembly_import_); + bool foundExclusion = false; + for (auto& item : filtered) { + if (item.replacement.target_method == excluded) { + foundExclusion = true; + } + } + EXPECT_FALSE(foundExclusion) + << "Expected method within integration to be filtered by version."; +} + +TEST_F(CLRHelperTest, GetsTypeInfoFromTypeDefs) { + std::set expected = { + L"Microsoft.CodeAnalysis.EmbeddedAttribute", + L"System.Runtime.CompilerServices.IsReadOnlyAttribute", + L"<>c", + L"d__4`2", + L"Cookie", + L"Raisin", + L"Samples.ExampleLibrary.Class1", + L"Samples.ExampleLibrary.FakeClient.Biscuit", + L"Samples.ExampleLibrary.FakeClient.Biscuit`1", + L"Samples.ExampleLibrary.FakeClient.DogClient`2", + L"Samples.ExampleLibrary.FakeClient.DogTrick", + L"Samples.ExampleLibrary.FakeClient.DogTrick`1", + L"Samples.ExampleLibrary.GenericTests.ComprehensiveCaller`2", + L"Samples.ExampleLibrary.GenericTests.GenericTarget`2", + L"Samples.ExampleLibrary.GenericTests.PointStruct", + L"Samples.ExampleLibrary.GenericTests.StructContainer`1"}; + std::set actual; + for (auto& type_def : EnumTypeDefs(metadata_import_)) { + auto type_info = GetTypeInfo(metadata_import_, type_def); + if (type_info.IsValid()) { + actual.insert(type_info.name); + } + } + EXPECT_EQ(actual, expected); +} + +TEST_F(CLRHelperTest, GetsTypeInfoFromTypeRefs) { + std::set expected = { + L"DebuggingModes", + L"Enumerator", + L"System.Array", + L"System.Attribute", + L"System.Collections.DictionaryEntry", + L"System.Collections.Generic.Dictionary`2", + L"System.Collections.Generic.IList`1", + L"System.Collections.Generic.List`1", + L"System.Diagnostics.DebuggableAttribute", +#ifdef _DEBUG + L"System.Diagnostics.DebuggerBrowsableAttribute", + L"System.Diagnostics.DebuggerBrowsableState", +#endif + L"System.Diagnostics.DebuggerHiddenAttribute", +#ifdef _DEBUG + L"System.Diagnostics.DebuggerStepThroughAttribute", +#endif + L"System.Exception", + L"System.Func`3", + L"System.Guid", + L"System.Int32", + L"System.Object", + L"System.Reflection.AssemblyCompanyAttribute", + L"System.Reflection.AssemblyConfigurationAttribute", + L"System.Reflection.AssemblyFileVersionAttribute", + L"System.Reflection.AssemblyInformationalVersionAttribute", + L"System.Reflection.AssemblyProductAttribute", + L"System.Reflection.AssemblyTitleAttribute", + L"System.Runtime.CompilerServices.AsyncStateMachineAttribute", + L"System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1", + L"System.Runtime.CompilerServices.CompilationRelaxationsAttribute", + L"System.Runtime.CompilerServices.CompilerGeneratedAttribute", + L"System.Runtime.CompilerServices.IAsyncStateMachine", + L"System.Runtime.CompilerServices.RuntimeCompatibilityAttribute", + L"System.Runtime.CompilerServices.TaskAwaiter", + L"System.Runtime.CompilerServices.TaskAwaiter`1", + L"System.Runtime.Versioning.TargetFrameworkAttribute", + L"System.RuntimeTypeHandle", + L"System.String", + L"System.Threading.Tasks.Task", + L"System.Threading.Tasks.Task`1", + L"System.Tuple`2", + L"System.Tuple`7", + L"System.Type", + L"System.ValueType"}; + std::set actual; + for (auto& type_ref : EnumTypeRefs(metadata_import_)) { + auto type_info = GetTypeInfo(metadata_import_, type_ref); + if (type_info.IsValid()) { + actual.insert(type_info.name); + } + } + EXPECT_EQ(expected, actual); +} + +TEST_F(CLRHelperTest, GetsTypeInfoFromModuleRefs) { + // TODO(cbd): figure out how to create a module ref, for now its empty + std::set expected = {}; + std::set actual; + for (auto& module_ref : EnumModuleRefs(metadata_import_)) { + auto type_info = GetTypeInfo(metadata_import_, module_ref); + actual.insert(type_info.name); + } + EXPECT_EQ(actual, expected); +} + +TEST_F(CLRHelperTest, GetsTypeInfoFromMethods) { + std::set expected = { + L"Microsoft.CodeAnalysis.EmbeddedAttribute", + L"System.Runtime.CompilerServices.IsReadOnlyAttribute", + L"<>c", + L"d__4`2", + L"Cookie", + L"Raisin", + L"Samples.ExampleLibrary.Class1", + L"Samples.ExampleLibrary.FakeClient.Biscuit", + L"Samples.ExampleLibrary.FakeClient.Biscuit`1", + L"Samples.ExampleLibrary.FakeClient.DogClient`2", + L"Samples.ExampleLibrary.FakeClient.DogTrick", + L"Samples.ExampleLibrary.FakeClient.DogTrick`1", + L"Samples.ExampleLibrary.GenericTests.ComprehensiveCaller`2", + L"Samples.ExampleLibrary.GenericTests.GenericTarget`2", + L"Samples.ExampleLibrary.GenericTests.PointStruct", + L"Samples.ExampleLibrary.GenericTests.StructContainer`1"}; + std::set actual; + for (auto& type_def : EnumTypeDefs(metadata_import_)) { + for (auto& method_def : EnumMethods(metadata_import_, type_def)) { + auto type_info = GetTypeInfo(metadata_import_, method_def); + if (type_info.IsValid()) { + actual.insert(type_info.name); + } + } + } + EXPECT_EQ(actual, expected); +} + +TEST_F(CLRHelperTest, + ReturnTypeIsValueTypeOrGenericReturnsCorrectlyForMethodDefs) { + std::set> expected = { + {L".ctor", L""}, + {L"Add", L"System.Int32"}, + {L"Multiply", L"System.Int32"}, + {L"ToCustomString", L""}, + {L"ToObject", L""}, + {L"ToArray", L""}, + {L"ToCustomArray", L""}, + {L"ToMdArray", L""}, + {L"ToJaggedArray", L""}, + {L"ToList", L""}, + {L"ToEnumerator", L"Enumerator"}, + {L"ToDictionaryEntry", L"System.Collections.DictionaryEntry"}, + + // Primitive tests + {L"ToBool", L"System.Boolean"}, + {L"ToChar", L"System.Char"}, + {L"ToSByte", L"System.SByte"}, + {L"ToByte", L"System.Byte"}, + {L"ToInt16", L"System.Int16"}, + {L"ToUInt16", L"System.UInt16"}, + {L"ToInt32", L"System.Int32"}, + {L"ToUInt32", L"System.UInt32"}, + {L"ToInt64", L"System.Int64"}, + {L"ToUInt64", L"System.UInt64"}, + {L"ToSingle", L"System.Single"}, + {L"ToDouble", L"System.Double"}}; + std::set> actual; + + for (auto& type_def : EnumTypeDefs(metadata_import_)) { + for (auto& method_def : EnumMethods(metadata_import_, type_def)) { + auto type_info = GetTypeInfo(metadata_import_, method_def); + if (type_info.IsValid() && + type_info.name == L"Samples.ExampleLibrary.Class1") { + mdToken type_token = mdTokenNil; + auto target = GetFunctionInfo(metadata_import_, method_def); + bool result = ReturnTypeIsValueTypeOrGeneric(metadata_import_, + metadata_emit_, + assembly_emit_, + target.id, + target.signature, + &type_token) && + type_token != mdTokenNil; + if (result) { + auto new_type_ref_info = GetTypeInfo(metadata_import_, type_token); + actual.insert({target.name, new_type_ref_info.name}); + } else { + actual.insert({target.name, L""}); + } + } + } + } + EXPECT_EQ(actual, expected); +} + +TEST_F(CLRHelperTest, + ReturnTypeIsValueTypeOrGenericReturnsCorrectlyForMemberRefs) { + std::vector> expected = { + {L"ReturnT1", true}, + {L"ReturnT1", true}, + {L"ReturnT1", false}, + {L"ReturnT1", true}, + {L"ReturnT1", false}, + {L"ReturnT1", true}, + + {L"ReturnT2", true}, + {L"ReturnT2", true}, + {L"ReturnT2", false}, + {L"ReturnT2", true}, + {L"ReturnT2", false}, + {L"ReturnT2", true}}; + std::vector> actual; + + for (mdMemberRef current = mdtMemberRef + 1; metadata_import_->IsValidToken(current); current++) { + auto target = GetFunctionInfo(metadata_import_, current); + if (target.name == L"ReturnT1" || target.name == L"ReturnT2") { + mdToken type_token = mdTokenNil; + bool result = ReturnTypeIsValueTypeOrGeneric(metadata_import_, + metadata_emit_, + assembly_emit_, + target.id, + target.signature, + &type_token) && + type_token != mdTokenNil; + actual.push_back({target.name, result}); + } + } + EXPECT_EQ(actual, expected); +} + +TEST_F(CLRHelperTest, + ReturnTypeIsValueTypeOrGenericReturnsCorrectlyForMethodSpecs) { + std::vector> expected = { + {L"ReturnM1", true}, + {L"ReturnM1", true}, + {L"ReturnM1", false}, + {L"ReturnM1", true}, + {L"ReturnM1", false}, + {L"ReturnM1", true}, + + {L"ReturnM2", true}, + {L"ReturnM2", true}, + {L"ReturnM2", false}, + {L"ReturnM2", true}, + {L"ReturnM2", false}, + {L"ReturnM2", true}}; + std::vector> actual; + + for (mdMethodSpec current = mdtMethodSpec + 1; metadata_import_->IsValidToken(current); current++) { + mdToken type_token = mdTokenNil; + auto target = GetFunctionInfo(metadata_import_, current); + if (target.name.find(L"ReturnM") != std::string::npos) { + bool result = ReturnTypeIsValueTypeOrGeneric(metadata_import_, + metadata_emit_, + assembly_emit_, + target.id, + target.signature, + &type_token) && + type_token != mdTokenNil; + actual.push_back({target.name, result}); + } + } + EXPECT_EQ(actual, expected); +} diff --git a/test/Datadog.Trace.ClrProfiler.Native.Tests/clr_helper_type_check_test.cpp b/test/Datadog.Trace.ClrProfiler.Native.Tests/clr_helper_type_check_test.cpp new file mode 100644 index 0000000000..eca1cd944d --- /dev/null +++ b/test/Datadog.Trace.ClrProfiler.Native.Tests/clr_helper_type_check_test.cpp @@ -0,0 +1,123 @@ +#include "pch.h" + +#include "../../src/Datadog.Trace.ClrProfiler.Native/clr_helpers.h" +#include "test_helpers.h" + +using namespace trace; + +class CLRHelperTypeCheckTest : public ::CLRHelperTestBase {}; + + +TEST_F(CLRHelperTypeCheckTest, SimpleNoSignatureMethodHasOnlyVoid) { + std::vector expected = { + L"System.Void"}; + std::vector actual; + + const auto target = FunctionToTest( + WStr("Samples.ExampleLibrary.FakeClient.DogClient`2"), WStr("Silence")); + + EXPECT_TRUE(target.name.size() > 1) << "Test target method not found."; + + TryParseSignatureTypes(metadata_import_, target, actual); + + EXPECT_EQ(expected, actual); +} + +TEST_F(CLRHelperTypeCheckTest, GetsVeryComplexNestedGenericTypeStrings) { + std::vector expected = { + L"System.Void", + L"System.String", + L"System.Int32", + L"System.Byte[]", + L"System.Guid[][]", + L"T[][][]", + L"System.Collections.Generic.List`1", + L"System.Collections.Generic.List`1>", + L"System.Tuple`7, System.Int64>, System.Threading.Tasks.Task, System.Guid>", + L"System.Collections.Generic.Dictionary`2>>>"}; + std::vector actual; + + const auto target = FunctionToTest( + WStr("Samples.ExampleLibrary.FakeClient.DogClient`2"), WStr("Sit")); + + EXPECT_TRUE(target.name.size() > 1) << "Test target method not found."; + + TryParseSignatureTypes(metadata_import_, target, actual); + + EXPECT_EQ(expected, actual); +} + + +TEST_F(CLRHelperTypeCheckTest, SimpleStringReturnWithNestedTypeParamsNoGenerics) { + std::vector expected = { + L"System.String", L"Samples.ExampleLibrary.FakeClient.Biscuit+Cookie", + L"Samples.ExampleLibrary.FakeClient.Biscuit+Cookie+Raisin"}; + std::vector actual; + + const auto target = FunctionToTest( + WStr("Samples.ExampleLibrary.FakeClient.DogClient`2"), WStr("TellMeIfTheCookieIsYummy")); + + EXPECT_TRUE(target.name.size() > 1) << "Test target method not found."; + + TryParseSignatureTypes(metadata_import_, target, actual); + + EXPECT_EQ(expected, actual); +} + + +TEST_F(CLRHelperTypeCheckTest, SimpleClassReturnWithSimpleParamsNoGenerics) { + std::vector expected = {L"Samples.ExampleLibrary.FakeClient.Biscuit", + L"System.Guid", L"System.Int16", + L"Samples.ExampleLibrary.FakeClient.DogTrick"}; + std::vector actual; + + const auto target = FunctionToTest( + WStr("Samples.ExampleLibrary.FakeClient.DogClient`2"), WStr("Rollover")); + + EXPECT_TRUE(target.name.size() > 1) << "Test target method not found."; + + TryParseSignatureTypes(metadata_import_, target, actual); + + EXPECT_EQ(expected, actual); +} + +TEST_F(CLRHelperTypeCheckTest, GenericAsyncMethodWithNestedGenericTask) { + std::vector expected = { + L"System.Threading.Tasks.Task`1>", + L"System.Guid", + L"System.Int16", + L"Samples.ExampleLibrary.FakeClient.DogTrick`1", + L"T", + L"T", + }; + std::vector actual; + + const auto target = FunctionToTest( + WStr("Samples.ExampleLibrary.FakeClient.DogClient`2"), WStr("StayAndLayDown")); + + EXPECT_TRUE(target.name.size() > 1) << "Test target method not found."; + + TryParseSignatureTypes(metadata_import_, target, actual); + + EXPECT_EQ(expected, actual); +} + +TEST_F(CLRHelperTypeCheckTest, SuccessfullyParsesEverySignature) { + std::set expected_failures = { + L"Samples.ExampleLibrary.Class1.ToMdArray", + L"Samples.ExampleLibrary.Class1.ToEnumerator" + }; + std::set actual_failures; + for (auto& type_def : EnumTypeDefs(metadata_import_)) { + for (auto& method_def : EnumMethods(metadata_import_, type_def)) { + auto target = GetFunctionInfo(metadata_import_, method_def); + std::vector actual; + auto success = TryParseSignatureTypes(metadata_import_, target, actual); + if (!success) { + actual_failures.insert(target.type.name + L"." + target.name); + } + } + } + + EXPECT_EQ(expected_failures, actual_failures); +} \ No newline at end of file diff --git a/test/Datadog.Trace.ClrProfiler.Native.Tests/integration_loader_test.cpp b/test/Datadog.Trace.ClrProfiler.Native.Tests/integration_loader_test.cpp new file mode 100644 index 0000000000..07de71c28e --- /dev/null +++ b/test/Datadog.Trace.ClrProfiler.Native.Tests/integration_loader_test.cpp @@ -0,0 +1,225 @@ +#include "pch.h" + +#include +#include +#include +#include +#include +#include + +#include "../../src/Datadog.Trace.ClrProfiler.Native/integration_loader.h" +#include "../../src/Datadog.Trace.ClrProfiler.Native/environment_variables.h" + +using namespace trace; + +TEST(IntegrationLoaderTest, HandlesMissingFile) { + auto integrations = LoadIntegrationsFromFile(L"missing-file"); + EXPECT_EQ(0, integrations.size()); +} + +TEST(IntegrationLoaderTest, HandlesInvalidIntegrationNoName) { + std::stringstream str("[{}]"); + auto integrations = LoadIntegrationsFromStream(str); + // 0 because name is required + EXPECT_EQ(0, integrations.size()); +} + +TEST(IntegrationLoaderTest, HandlesInvalidIntegrationBadJson) { + std::stringstream str("["); + auto integrations = LoadIntegrationsFromStream(str); + EXPECT_EQ(0, integrations.size()); +} + +TEST(IntegrationLoaderTest, HandlesInvalidIntegrationNotAnObject) { + std::stringstream str("[1,2,3]"); + auto integrations = LoadIntegrationsFromStream(str); + EXPECT_EQ(0, integrations.size()); +} + +TEST(IntegrationLoaderTest, HandlesInvalidIntegrationNotAnArray) { + std::stringstream str(R"TEXT( + {"name": "test-integration"} + )TEXT"); + auto integrations = LoadIntegrationsFromStream(str); + EXPECT_EQ(0, integrations.size()); +} + +TEST(IntegrationLoaderTest, HandlesSingleIntegrationWithNoMethods) { + std::stringstream str(R"TEXT( + [{ "name": "test-integration" }] + )TEXT"); + + auto integrations = LoadIntegrationsFromStream(str); + EXPECT_EQ(1, integrations.size()); + EXPECT_STREQ(L"test-integration", integrations[0].integration_name.c_str()); + EXPECT_EQ(0, integrations[0].method_replacements.size()); +} + +TEST(IntegrationLoaderTest, + HandlesSingleIntegrationWithInvalidMethodReplacementType) { + std::stringstream str(R"TEXT( + [{ "name": "test-integration", "method_replacements": 1234 }] + )TEXT"); + + auto integrations = LoadIntegrationsFromStream(str); + EXPECT_EQ(1, integrations.size()); + EXPECT_STREQ(L"test-integration", integrations[0].integration_name.c_str()); + EXPECT_EQ(0, integrations[0].method_replacements.size()); +} + +TEST(IntegrationLoaderTest, HandlesSingleIntegrationWithMethodReplacements) { + std::stringstream str(R"TEXT( + [{ + "name": "test-integration", + "method_replacements": [{ + "caller": { }, + "target": { "assembly": "Assembly.One", "type": "Type.One", "method": "Method.One", "minimum_major": 0, "minimum_minor": 1, "maximum_major": 10, "maximum_minor": 0 }, + "wrapper": { "assembly": "Assembly.Two", "type": "Type.Two", "method": "Method.Two", "signature": [0, 1, 1, 28] } + }] + }] + )TEXT"); + + auto integrations = LoadIntegrationsFromStream(str); + EXPECT_EQ(1, integrations.size()); + EXPECT_STREQ(L"test-integration", integrations[0].integration_name.c_str()); +} + +TEST(IntegrationLoaderTest, DoesNotCrashWithOutOfRangeVersion) { + std::stringstream str(R"TEXT( + [{ + "name": "test-integration", + "method_replacements": [{ + "caller": { }, + "target": { "assembly": "Assembly.One", "type": "Type.One", "method": "Method.One", "minimum_major": 0, "minimum_minor": 1, "maximum_major": 75555, "maximum_minor": 0 }, + "wrapper": { "assembly": "Assembly.Two", "type": "Type.Two", "method": "Method.Two", "signature": [0, 1, 1, 28] } + }] + }] + )TEXT"); + + auto integrations = LoadIntegrationsFromStream(str); + EXPECT_EQ(1, integrations.size()); + EXPECT_STREQ(L"test-integration", integrations[0].integration_name.c_str()); + + EXPECT_EQ(1, integrations[0].method_replacements.size()); + auto mr = integrations[0].method_replacements[0]; + EXPECT_STREQ(L"", mr.caller_method.assembly.name.c_str()); + EXPECT_STREQ(L"", mr.caller_method.type_name.c_str()); + EXPECT_STREQ(L"", mr.caller_method.method_name.c_str()); + EXPECT_STREQ(L"Assembly.One", mr.target_method.assembly.name.c_str()); + EXPECT_STREQ(L"Type.One", mr.target_method.type_name.c_str()); + EXPECT_STREQ(L"Method.One", mr.target_method.method_name.c_str()); + EXPECT_STREQ(L"Assembly.Two", mr.wrapper_method.assembly.name.c_str()); + EXPECT_STREQ(L"Type.Two", mr.wrapper_method.type_name.c_str()); + EXPECT_STREQ(L"Method.Two", mr.wrapper_method.method_name.c_str()); + EXPECT_EQ(std::vector({0, 1, 1, 28}), + mr.wrapper_method.method_signature.data); +} + +TEST(IntegrationLoaderTest, HandlesSingleIntegrationWithMissingCaller) { + std::stringstream str(R"TEXT( + [{ + "name": "test-integration", + "method_replacements": [{ + "target": { "assembly": "Assembly.One", "type": "Type.One", "method": "Method.One", "minimum_major": 1, "minimum_minor": 2, "maximum_major": 10, "maximum_minor": 99 }, + "wrapper": { "assembly": "Assembly.Two", "type": "Type.Two", "method": "Method.Two", "signature": [0, 1, 1, 28] } + }] + }] + )TEXT"); + + auto integrations = LoadIntegrationsFromStream(str); + EXPECT_EQ(1, integrations.size()); + EXPECT_STREQ(L"test-integration", integrations[0].integration_name.c_str()); + + EXPECT_EQ(1, integrations[0].method_replacements.size()); + auto mr = integrations[0].method_replacements[0]; + EXPECT_STREQ(L"", mr.caller_method.assembly.name.c_str()); + EXPECT_STREQ(L"", mr.caller_method.type_name.c_str()); + EXPECT_STREQ(L"", mr.caller_method.method_name.c_str()); + EXPECT_STREQ(L"Assembly.One", mr.target_method.assembly.name.c_str()); + EXPECT_STREQ(L"Type.One", mr.target_method.type_name.c_str()); + EXPECT_STREQ(L"Method.One", mr.target_method.method_name.c_str()); + EXPECT_STREQ(L"Assembly.Two", mr.wrapper_method.assembly.name.c_str()); + EXPECT_STREQ(L"Type.Two", mr.wrapper_method.type_name.c_str()); + EXPECT_STREQ(L"Method.Two", mr.wrapper_method.method_name.c_str()); + EXPECT_STREQ(L"Method.Two", mr.wrapper_method.method_name.c_str()); + EXPECT_EQ(1, mr.target_method.min_version.major); + EXPECT_EQ(2, mr.target_method.min_version.minor); + EXPECT_EQ(0, mr.target_method.min_version.build); + EXPECT_EQ(10, mr.target_method.max_version.major); + EXPECT_EQ(99, mr.target_method.max_version.minor); + EXPECT_EQ(USHRT_MAX, mr.target_method.max_version.build); + EXPECT_EQ(std::vector({0, 1, 1, 28}), + mr.wrapper_method.method_signature.data); +} + +TEST(IntegrationLoaderTest, HandlesSingleIntegrationWithInvalidTarget) { + std::stringstream str(R"TEXT( + [{ + "name": "test-integration", + "method_replacements": [{ + "target": 1234, + "wrapper": { "assembly": "Assembly.Two", "type": "Type.Two", "method": "Method.Two" } + }] + }] + )TEXT"); + + auto integrations = LoadIntegrationsFromStream(str); + EXPECT_EQ(1, integrations.size()); + EXPECT_STREQ(L"test-integration", integrations[0].integration_name.c_str()); + + EXPECT_EQ(1, integrations[0].method_replacements.size()); + auto mr = integrations[0].method_replacements[0]; + EXPECT_STREQ(L"", mr.target_method.assembly.name.c_str()); + EXPECT_STREQ(L"", mr.target_method.type_name.c_str()); + EXPECT_STREQ(L"", mr.target_method.method_name.c_str()); +} + +TEST(IntegrationLoaderTest, LoadsFromEnvironment) { + auto tmpname1 = std::filesystem::temp_directory_path() / "test-1.json"; + auto tmpname2 = std::filesystem::temp_directory_path() / "test-2.json"; + std::ofstream f; + f.open(tmpname1); + f << R"TEXT( + [{ "name": "test-integration-1" }] + )TEXT"; + f.close(); + f.open(tmpname2); + f << R"TEXT( + [{ "name": "test-integration-2" }] + )TEXT"; + f.close(); + + auto name = tmpname1.wstring() + L"," + tmpname2.wstring(); + + SetEnvironmentVariableW(trace::environment::integrations_path.data(), name.data()); + + std::vector expected_names = {L"test-integration-1", + L"test-integration-2"}; + std::vector actual_names; + for (auto& integration : LoadIntegrationsFromEnvironment()) { + actual_names.push_back(integration.integration_name); + } + EXPECT_EQ(expected_names, actual_names); + + std::filesystem::remove(tmpname1); + std::filesystem::remove(tmpname2); +} + +TEST(IntegrationLoaderTest, DeserializesSignatureTypeArray) { + std::stringstream str(R"TEXT( + [{ + "name": "test-integration", + "method_replacements": [{ + "caller": { }, + "target": { "assembly": "Assembly.One", "type": "Type.One", "method": "Method.One", "signature_types": ["System.Void", "_", "FakeClient.Pipeline'1"] }, + "wrapper": { "assembly": "Assembly.Two", "type": "Type.Two", "method": "Method.One", "signature": [0, 1, 1, 28] } + }] + }] + )TEXT"); + + auto integrations = LoadIntegrationsFromStream(str); + const auto target = integrations[0].method_replacements[0].target_method; + EXPECT_STREQ(L"System.Void", target.signature_types[0].c_str()); + EXPECT_STREQ(L"_", target.signature_types[1].c_str()); + EXPECT_STREQ(L"FakeClient.Pipeline'1", target.signature_types[2].c_str()); +} diff --git a/test/Datadog.Trace.ClrProfiler.Native.Tests/integration_test.cpp b/test/Datadog.Trace.ClrProfiler.Native.Tests/integration_test.cpp new file mode 100644 index 0000000000..3bcd499253 --- /dev/null +++ b/test/Datadog.Trace.ClrProfiler.Native.Tests/integration_test.cpp @@ -0,0 +1,46 @@ +#include "pch.h" + +#include "../../src/Datadog.Trace.ClrProfiler.Native/integration.h" + +using namespace trace; + +TEST(IntegrationTest, AssemblyReference) { + AssemblyReference ref( + L"Some.Assembly, Version=1.2.3.4, Culture=notneutral, " + L"PublicKeyToken=0123456789abcdef"); + + EXPECT_EQ(ref.name, L"Some.Assembly"); + EXPECT_EQ(ref.version, Version(1, 2, 3, 4)); + EXPECT_EQ(ref.locale, L"notneutral"); + EXPECT_EQ(ref.public_key, + PublicKey({0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef})); +} + +TEST(IntegrationTest, AssemblyReferenceNameOnly) { + AssemblyReference ref(L"Some.Assembly"); + + EXPECT_EQ(ref.name, L"Some.Assembly"); + EXPECT_EQ(ref.version, Version(0, 0, 0, 0)); + EXPECT_EQ(ref.locale, L"neutral"); + EXPECT_EQ(ref.public_key, PublicKey({0, 0, 0, 0, 0, 0, 0, 0})); +} + +TEST(IntegrationTest, AssemblyReferenceInvalidPublicKey) { + AssemblyReference ref(L"Some.Assembly, PublicKeyToken=xyz"); + EXPECT_EQ(ref.public_key, PublicKey({0, 0, 0, 0, 0, 0, 0, 0})); +} + +TEST(IntegrationTest, AssemblyReferenceNullPublicKey) { + AssemblyReference ref(L"Some.Assembly, PublicKeyToken=null"); + EXPECT_EQ(ref.public_key, PublicKey({0, 0, 0, 0, 0, 0, 0, 0})); +} + +TEST(IntegrationTest, AssemblyReferencePartialVersion) { + AssemblyReference ref(L"Some.Assembly, Version=1.2.3"); + EXPECT_EQ(ref.version, Version(0, 0, 0, 0)); +} + +TEST(IntegrationTest, AssemblyReferenceInvalidVersion) { + AssemblyReference ref(L"Some.Assembly, Version=xyz"); + EXPECT_EQ(ref.version, Version(0, 0, 0, 0)); +} \ No newline at end of file diff --git a/test/Datadog.Trace.ClrProfiler.Native.Tests/metadata_builder_test.cpp b/test/Datadog.Trace.ClrProfiler.Native.Tests/metadata_builder_test.cpp new file mode 100644 index 0000000000..10554e1751 --- /dev/null +++ b/test/Datadog.Trace.ClrProfiler.Native.Tests/metadata_builder_test.cpp @@ -0,0 +1,157 @@ +#include "pch.h" + +#include "../../src/Datadog.Trace.ClrProfiler.Native/clr_helpers.h" +#include "../../src/Datadog.Trace.ClrProfiler.Native/metadata_builder.h" + +using namespace trace; + +class MetadataBuilderTest : public ::testing::Test { + protected: + ModuleMetadata* module_metadata_ = nullptr; + MetadataBuilder* metadata_builder_ = nullptr; + ICLRStrongName* strong_name_ = nullptr; + IMetaDataDispenser* metadata_dispenser_ = nullptr; + std::vector empty_sig_type_; + + void SetUp() override { + ICLRMetaHost* metahost = nullptr; + HRESULT hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, + (void**)&metahost); + ASSERT_TRUE(SUCCEEDED(hr)); + + IEnumUnknown* runtimes = nullptr; + hr = metahost->EnumerateInstalledRuntimes(&runtimes); + ASSERT_TRUE(SUCCEEDED(hr)); + + ICLRRuntimeInfo* latest = nullptr; + ICLRRuntimeInfo* runtime = nullptr; + ULONG fetched = 0; + while ((hr = runtimes->Next(1, (IUnknown**)&runtime, &fetched)) == S_OK && + fetched > 0) { + latest = runtime; + } + + hr = + latest->GetInterface(CLSID_CorMetaDataDispenser, IID_IMetaDataDispenser, + (void**)&metadata_dispenser_); + ASSERT_TRUE(SUCCEEDED(hr)); + + hr = latest->GetInterface(CLSID_CLRStrongName, IID_ICLRStrongName, + (void**)&strong_name_); + ASSERT_TRUE(SUCCEEDED(hr)); + + ComPtr metadataInterfaces; + hr = metadata_dispenser_->OpenScope(L"Samples.ExampleLibrary.dll", + ofReadWriteMask, IID_IMetaDataImport2, + metadataInterfaces.GetAddressOf()); + ASSERT_TRUE(SUCCEEDED(hr)) << "File not found: Samples.ExampleLibrary.dll"; + + const auto metadataImport = + metadataInterfaces.As(IID_IMetaDataImport2); + const auto metadataEmit = + metadataInterfaces.As(IID_IMetaDataEmit); + const auto assemblyImport = metadataInterfaces.As( + IID_IMetaDataAssemblyImport); + const auto assemblyEmit = + metadataInterfaces.As(IID_IMetaDataAssemblyEmit); + + const std::wstring assemblyName = L"Samples.ExampleLibrary"; + + const AppDomainID app_domain_id{}; + + GUID module_version_id; + metadataImport->GetScopeProps(NULL, 1024, nullptr, &module_version_id); + + const std::vector integrations; + module_metadata_ = + new ModuleMetadata(metadataImport, metadataEmit, assemblyImport, assemblyEmit, + assemblyName, app_domain_id, module_version_id, integrations, NULL); + + mdModule module; + hr = metadataImport->GetModuleFromScope(&module); + ASSERT_TRUE(SUCCEEDED(hr)); + + metadata_builder_ = + new MetadataBuilder(*module_metadata_, module, metadataImport, + metadataEmit, assemblyImport, assemblyEmit); + + hr = metadata_builder_->EmitAssemblyRef(trace::AssemblyReference( + L"Samples.ExampleLibraryTracer, Version=1.0.0.0")); + ASSERT_TRUE(SUCCEEDED(hr)); + } + + void TearDown() override { + delete this->module_metadata_; + delete this->metadata_builder_; + } +}; + +TEST_F(MetadataBuilderTest, StoresWrapperMemberRef) { + + const auto min_ver = Version(0, 0, 0, 0); + const auto max_ver = Version(USHRT_MAX, USHRT_MAX, USHRT_MAX, USHRT_MAX); + const MethodReference ref1(L"", L"", L"", L"", min_ver, max_ver, {}, empty_sig_type_); + const MethodReference ref2(L"Samples.ExampleLibrary", L"Class1", L"Add", L"", min_ver, max_ver, {}, empty_sig_type_); + const MethodReference ref3(L"Samples.ExampleLibrary", L"Class1", L"Add", L"ReplaceTargetMethod", min_ver, max_ver, {}, empty_sig_type_); + const MethodReplacement mr1(ref1, ref2, ref3); + auto hr = metadata_builder_->StoreWrapperMethodRef(mr1); + ASSERT_EQ(S_OK, hr); + + mdMemberRef tmp; + auto key_failed = module_metadata_->IsFailedWrapperMemberKey( + L"[Samples.ExampleLibrary]Class1.Add_vMin_0.0.0.0_vMax_65535.65535.65535.65535"); + auto ok = module_metadata_->TryGetWrapperMemberRef( + L"[Samples.ExampleLibrary]Class1.Add_vMin_0.0.0.0_vMax_65535.65535.65535.65535", tmp); + EXPECT_TRUE(ok); + EXPECT_FALSE(key_failed); + EXPECT_NE(tmp, 0); + + tmp = 0; + key_failed = module_metadata_->IsFailedWrapperMemberKey( + L"[Samples.ExampleLibrary]Class2.Add_vMin_0.0.0.0_vMax_65535.65535.65535.65535"); + ok = module_metadata_->TryGetWrapperMemberRef( + L"[Samples.ExampleLibrary]Class2.Add_vMin_0.0.0.0_vMax_65535.65535.65535.65535", tmp); + EXPECT_FALSE(ok); + EXPECT_FALSE(key_failed); + EXPECT_EQ(tmp, 0); +} + +TEST_F(MetadataBuilderTest, StoresWrapperMemberRefForSeparateAssembly) { + const auto min_ver = Version(0, 0, 0, 0); + const auto max_ver = Version(USHRT_MAX, USHRT_MAX, USHRT_MAX, USHRT_MAX); + const MethodReference ref1(L"", L"", L"", L"", min_ver, max_ver, {}, + empty_sig_type_); + const MethodReference ref2(L"Samples.ExampleLibrary", L"Class1", L"Add", L"", min_ver, max_ver, {}, empty_sig_type_); + const MethodReference ref3(L"Samples.ExampleLibraryTracer", L"Class1", L"Add", L"ReplaceTargetMethod", + min_ver, max_ver, {}, empty_sig_type_); + const MethodReplacement mr1(ref1, ref2, ref3); + auto hr = metadata_builder_->StoreWrapperMethodRef(mr1); + ASSERT_EQ(S_OK, hr); + + mdMemberRef tmp; + auto key_failed = module_metadata_->IsFailedWrapperMemberKey(L"[Samples.ExampleLibraryTracer]Class1.Add_vMin_0.0.0.0_vMax_65535.65535.65535.65535"); + auto ok = module_metadata_->TryGetWrapperMemberRef( + L"[Samples.ExampleLibraryTracer]Class1.Add_vMin_0.0.0.0_vMax_65535.65535.65535.65535", tmp); + EXPECT_TRUE(ok); + EXPECT_FALSE(key_failed); + EXPECT_NE(tmp, 0); +} + +TEST_F(MetadataBuilderTest, StoresWrapperMemberRefRecordsFailure) { + const auto min_ver = Version(0, 0, 0, 0); + const auto max_ver = Version(USHRT_MAX, USHRT_MAX, USHRT_MAX, USHRT_MAX); + const MethodReference ref1(L"", L"", L"", L"", min_ver, max_ver, {}, + empty_sig_type_); + const MethodReference ref2(L"Samples.ExampleLibrary", L"Class1", L"Add", L"", min_ver, max_ver, {}, empty_sig_type_); + const MethodReference ref3(L"Samples.ExampleLibraryTracer.AssemblyDoesNotExist", L"Class1", L"Add", L"ReplaceTargetMethod", + min_ver, max_ver, {}, empty_sig_type_); + const MethodReplacement mr1(ref1, ref2, ref3); + auto hr = metadata_builder_->StoreWrapperMethodRef(mr1); + ASSERT_NE(S_OK, hr); + + auto key_failed = module_metadata_->IsFailedWrapperMemberKey(L"[Samples.ExampleLibraryTracer.AssemblyDoesNotExist]Class1.Add_vMin_0.0.0.0_vMax_65535.65535.65535.65535"); + EXPECT_TRUE(key_failed); + + key_failed = module_metadata_->IsFailedWrapperMemberKey(L"[Samples.ExampleLibraryTracer]Class1.Add_vMin_0.0.0.0_vMax_65535.65535.65535.65535"); + EXPECT_FALSE(key_failed); +} \ No newline at end of file diff --git a/test/Datadog.Trace.ClrProfiler.Native.Tests/packages.config b/test/Datadog.Trace.ClrProfiler.Native.Tests/packages.config new file mode 100644 index 0000000000..d974a133db --- /dev/null +++ b/test/Datadog.Trace.ClrProfiler.Native.Tests/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/test/Datadog.Trace.ClrProfiler.Native.Tests/pch.cpp b/test/Datadog.Trace.ClrProfiler.Native.Tests/pch.cpp new file mode 100644 index 0000000000..97b544ec11 --- /dev/null +++ b/test/Datadog.Trace.ClrProfiler.Native.Tests/pch.cpp @@ -0,0 +1,6 @@ +// +// pch.cpp +// Include the standard header and generate the precompiled header. +// + +#include "pch.h" diff --git a/test/Datadog.Trace.ClrProfiler.Native.Tests/pch.h b/test/Datadog.Trace.ClrProfiler.Native.Tests/pch.h new file mode 100644 index 0000000000..b4b50837d6 --- /dev/null +++ b/test/Datadog.Trace.ClrProfiler.Native.Tests/pch.h @@ -0,0 +1,18 @@ +// +// pch.h +// Header for standard system include files. +// + +#ifndef DD_CLR_PROFILER_TESTS_PCH_H_ +#define DD_CLR_PROFILER_TESTS_PCH_H_ + +#define GTEST_LANG_CXX11 1 + +#include "gtest/gtest.h" + +#include +#include +#include +#pragma comment(lib, "mscoree.lib") + +#endif \ No newline at end of file diff --git a/test/Datadog.Trace.ClrProfiler.Native.Tests/test_helpers.h b/test/Datadog.Trace.ClrProfiler.Native.Tests/test_helpers.h new file mode 100644 index 0000000000..1365ffdc98 --- /dev/null +++ b/test/Datadog.Trace.ClrProfiler.Native.Tests/test_helpers.h @@ -0,0 +1,77 @@ + +#pragma once +#include "../../src/Datadog.Trace.ClrProfiler.Native/com_ptr.h" +#include "../../src/Datadog.Trace.ClrProfiler.Native/integration.h" + +namespace trace { + +class CLRHelperTestBase : public ::testing::Test { + protected: + IMetaDataDispenser* metadata_dispenser_; + ComPtr metadata_import_; + ComPtr metadata_emit_; + ComPtr assembly_import_; + ComPtr assembly_emit_; + Version min_ver_ = Version(0, 0, 0, 0); + Version max_ver_ = Version(USHRT_MAX, USHRT_MAX, USHRT_MAX, USHRT_MAX); + std::vector empty_sig_type_; + + void LoadMetadataDependencies() { + ICLRMetaHost* metahost = nullptr; + HRESULT hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, + (void**)&metahost); + ASSERT_TRUE(SUCCEEDED(hr)); + + IEnumUnknown* runtimes = nullptr; + hr = metahost->EnumerateInstalledRuntimes(&runtimes); + ASSERT_TRUE(SUCCEEDED(hr)); + + ICLRRuntimeInfo* latest = nullptr; + ICLRRuntimeInfo* runtime = nullptr; + ULONG fetched = 0; + while ((hr = runtimes->Next(1, (IUnknown**)&runtime, &fetched)) == S_OK && + fetched > 0) { + latest = runtime; + } + + hr = + latest->GetInterface(CLSID_CorMetaDataDispenser, IID_IMetaDataDispenser, + (void**)&metadata_dispenser_); + ASSERT_TRUE(SUCCEEDED(hr)); + + ComPtr metadataInterfaces; + hr = metadata_dispenser_->OpenScope(L"Samples.ExampleLibrary.dll", + ofReadWriteMask, IID_IMetaDataImport2, + metadataInterfaces.GetAddressOf()); + ASSERT_TRUE(SUCCEEDED(hr)) << "Samples.ExampleLibrary.dll was not found."; + + metadata_import_ = + metadataInterfaces.As(IID_IMetaDataImport2); + metadata_emit_ = + metadataInterfaces.As(IID_IMetaDataEmit); + assembly_import_ = + metadataInterfaces.As(IID_IMetaDataAssemblyImport); + assembly_emit_ = + metadataInterfaces.As(IID_IMetaDataAssemblyEmit); + } + + void SetUp() override { LoadMetadataDependencies(); } + + FunctionInfo FunctionToTest(const WSTRING& type_name, const WSTRING& method_name) const { + for (auto& type_def : EnumTypeDefs(metadata_import_)) { + for (auto& method_def : EnumMethods(metadata_import_, type_def)) { + auto target = GetFunctionInfo(metadata_import_, method_def); + if (target.type.name != type_name) { + continue; + } + if (target.name != method_name) { + continue; + } + return target; + } + } + + return {}; + } +}; +} // namespace trace diff --git a/test/Datadog.Trace.ClrProfiler.Native.Tests/version_struct_test.cpp b/test/Datadog.Trace.ClrProfiler.Native.Tests/version_struct_test.cpp new file mode 100644 index 0000000000..9d3bc587e0 --- /dev/null +++ b/test/Datadog.Trace.ClrProfiler.Native.Tests/version_struct_test.cpp @@ -0,0 +1,50 @@ +#include "pch.h" + +#include "../../src/Datadog.Trace.ClrProfiler.Native/integration.h" + +using namespace trace; + +TEST(VersionStructTest, Major2GreaterThanMajor1) { + const auto v1 = Version(1, 0, 0, 0); + const auto v2 = Version(2, 0, 0, 0); + ASSERT_TRUE(v2 > v1) << "Expected v2 to be greater than v1."; +} + +TEST(VersionStructTest, Minor2GreaterThanMinor1) { + const auto v0_1 = Version(0, 1, 0, 0); + const auto v0_2 = Version(0, 2, 0, 0); + ASSERT_TRUE(v0_2 > v0_1) << "Expected v0_2 to be greater than v0_1."; +} + +TEST(VersionStructTest, Build1GreaterThanBuild0) { + const auto v0_0_0 = Version(0, 0, 0, 0); + const auto v0_0_1 = Version(0, 0, 1, 0); + ASSERT_TRUE(v0_0_1 > v0_0_0) << "Expected v0_0_1 to be greater than v0_0_0."; +} + +TEST(VersionStructTest, Major1LessThanMajor2) { + const auto v1 = Version(1, 0, 0, 0); + const auto v2 = Version(2, 0, 0, 0); + ASSERT_TRUE(v1 < v2) << "Expected v1 to be less than v2."; +} + +TEST(VersionStructTest, Minor1LessThanMinor2) { + const auto v0_1 = Version(0, 1, 0, 0); + const auto v0_2 = Version(0, 2, 0, 0); + ASSERT_TRUE(v0_1 < v0_2) << "Expected v0_1 to be less than v0_2."; +} + +TEST(VersionStructTest, Build0LessThanBuild1) { + const auto v0_0_0 = Version(0, 0, 0, 0); + const auto v0_0_1 = Version(0, 0, 1, 0); + ASSERT_TRUE(v0_0_0 < v0_0_1) << "Expected v0_0_0 to be less than v0_0_1."; +} + +TEST(VersionStructTest, RevisionDoesNotAffectComparison) { + const auto v1_2_3_4 = Version(1, 2, 3, 4); + const auto v1_2_3_5 = Version(1, 2, 3, 5); + ASSERT_FALSE(v1_2_3_5 > v1_2_3_4) + << "Expected v1_2_3_5 to not be greater than v1_2_3_4."; + ASSERT_FALSE(v1_2_3_4 < v1_2_3_5) + << "Expected v1_2_3_4 to not be less than v1_2_3_5."; +} diff --git a/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/Class1.cs b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/Class1.cs new file mode 100644 index 0000000000..c8a7e6a600 --- /dev/null +++ b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/Class1.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Samples.ExampleLibrary +{ + public class Class1 + { + public int Add(int x, int y) + { + return x + y; + } + + public virtual int Multiply(int x, int y) + { + return x * y; + } + + public Func Divide = (int x, int y) => x / y; + + public string ToCustomString() + { + return "Custom"; + } + + public object ToObject() + { + return this; + } + + public Class1[] ToArray() + { + return new Class1[] { this }; + } + + public Array ToCustomArray() + { + var lengthsArray = new int[2] { 5, 10 }; + var lowerBoundsArray = new int[2] { 20, 15 }; + return Array.CreateInstance(typeof(Class1), lengthsArray, lowerBoundsArray); + } + + public Class1[, ,] ToMdArray() + { + return new Class1[4, 2, 3]; + } + + public Class1[][] ToJaggedArray() + { + return new Class1[][] + { + new Class1[] { this }, + new Class1[] { null, null } + }; + } + + public List ToList() + { + return new List() { this }; + } + + public List.Enumerator ToEnumerator() + { + return ToList().GetEnumerator(); + } + + public DictionaryEntry ToDictionaryEntry() + { + return new DictionaryEntry("Class1", this); + } + + public bool ToBool() + { + return false; + } + + public char ToChar() + { + return 'b'; + } + + public sbyte ToSByte() + { + return 0x1; + } + + public byte ToByte() + { + return 0x1; + } + + public Int16 ToInt16() + { + return 16; + } + + public UInt16 ToUInt16() + { + return 16; + } + + public Int32 ToInt32() + { + return 32; + } + + public UInt32 ToUInt32() + { + return 32; + } + + public Int64 ToInt64() + { + return 64; + } + + public UInt64 ToUInt64() + { + return 64; + } + + public float ToSingle() + { + return 0.1f; + } + + public double ToDouble() + { + return 0.1; + } + } +} diff --git a/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/FakeClient/Biscuit.cs b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/FakeClient/Biscuit.cs new file mode 100644 index 0000000000..30d3011ad9 --- /dev/null +++ b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/FakeClient/Biscuit.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace Samples.ExampleLibrary.FakeClient +{ + public class Biscuit : Biscuit + { + public T Reward { get; set; } + } + + public class Biscuit + { + public Guid Id { get; set; } + + public string Message { get; set; } + + public List Treats { get; set; } = new List(); + + public class Cookie + { + public bool IsYummy { get; set; } + + public class Raisin + { + public bool IsPurple { get; set; } + } + } + } +} diff --git a/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/FakeClient/DogClient.cs b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/FakeClient/DogClient.cs new file mode 100644 index 0000000000..87838dac2a --- /dev/null +++ b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/FakeClient/DogClient.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Samples.ExampleLibrary.FakeClient +{ + public class DogClient + { + public void Silence() + { + Task.Delay(1).Wait(); + } + + public string TellMeIfTheCookieIsYummy(Biscuit.Cookie cookie, Biscuit.Cookie.Raisin raisin) + { + if (cookie.IsYummy) + { + if (raisin.IsPurple) + { + return "Yes, it is yummy, with purple raisins."; + } + + return "Yes, it is yummy, with white raisins."; + } + + return "No, it is not yummy"; + } + + public void Sit( + string message, + int howManyTimes, + byte[] whatEvenIs = null, + Guid[][] whatEvenIsThis = null, + T1[][][] whatEvenIsThisT = null, + List evenMoreWhatIsThis = null, + List> previousTricks = null, + Tuple, long>, Task, Guid> tuple = null, + Dictionary>>> whatAmIDoing = null) + { + for (var i = 0; i < howManyTimes; i++) + { + message += + message + + whatEvenIs?.ToString() + + whatEvenIsThis?.ToString() + + whatEvenIsThisT?.ToString() + + evenMoreWhatIsThis?.GetType() + + previousTricks?.GetType() + + tuple?.GetType() + + whatAmIDoing?.GetType(); + } + } + + public Biscuit Rollover(Guid clientId, short timesToRun, DogTrick trick) + { + var biscuit = new Biscuit + { + Id = clientId, + Message = trick.Message + }; + + Sit("Sit!", timesToRun); + + return biscuit; + } + + public async Task> StayAndLayDown(Guid clientId, short timesToRun, DogTrick trick, TM1 extraTreat, TM2 extraExtraTreat) + { + await Task.Delay(5); + var biscuit = new Biscuit(); + biscuit.Treats.Add(extraTreat); + biscuit.Treats.Add(extraExtraTreat); + return await Task.FromResult(biscuit); + } + } +} diff --git a/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/FakeClient/DogTrick.cs b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/FakeClient/DogTrick.cs new file mode 100644 index 0000000000..8470ad7263 --- /dev/null +++ b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/FakeClient/DogTrick.cs @@ -0,0 +1,14 @@ +namespace Samples.ExampleLibrary.FakeClient +{ + public class DogTrick + { + public string Message { get; set; } + + public T Reward { get; set; } + } + + public class DogTrick + { + public string Message { get; set; } + } +} diff --git a/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GenericTests/ComprehensiveCaller.cs b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GenericTests/ComprehensiveCaller.cs new file mode 100644 index 0000000000..974fa8b6e7 --- /dev/null +++ b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GenericTests/ComprehensiveCaller.cs @@ -0,0 +1,136 @@ +using System; +using System.Threading.Tasks; + +namespace Samples.ExampleLibrary.GenericTests +{ + public class ComprehensiveCaller + { + #region CallReturnM1 + public void CallReturnM1WithCallerTypeArgs(GenericTarget target, CallerT1 input1, CallerT2 input2) + { + target.ReturnM1(input1, input2); + } + + public void CallReturnM1WithCallerTypeArgsReversed(GenericTarget target, CallerT1 input1, CallerT2 input2) + { + target.ReturnM1(input2, input1); + } + + public void CallReturnM1WithClass(GenericTarget target, Exception input1, CallerT2 input2) + { + target.ReturnM1(input1, input2); + } + + public void CallReturnM1WithStruct(GenericTarget target, PointStruct input1, CallerT2 input2) + { + target.ReturnM1(input1, input2); + } + + public void CallReturnM1WithReferenceTypeGenericInstantiation(GenericTarget target, Task input1, CallerT2 input2) + { + target.ReturnM1, CallerT2>(input1, input2); + } + + public void CallReturnM1WithValueTypeGenericInstantiation(GenericTarget target, StructContainer input1, CallerT2 input2) + { + target.ReturnM1, CallerT2>(input1, input2); + } + #endregion + + #region CallReturnM2 + public void CallReturnM2WithCallerTypeArgs(GenericTarget target, CallerT1 input1, CallerT2 input2) + { + target.ReturnM2(input1, input2); + } + + public void CallReturnM2WithCallerTypeArgsReversed(GenericTarget target, CallerT1 input1, CallerT2 input2) + { + target.ReturnM2(input2, input1); + } + + public void CallReturnM2WithClass(GenericTarget target, CallerT1 input1, Exception input2) + { + target.ReturnM2(input1, input2); + } + + public void CallReturnM2WithStruct(GenericTarget target, CallerT1 input1, PointStruct input2) + { + target.ReturnM2(input1, input2); + } + + public void CallReturnM2WithReferenceTypeGenericInstantiation(GenericTarget target, CallerT1 input1, Task input2) + { + target.ReturnM2>(input1, input2); + } + + public void CallReturnM2WithValueTypeGenericInstantiation(GenericTarget target, CallerT1 input1, StructContainer input2) + { + target.ReturnM2>(input1, input2); + } + #endregion + + #region CallReturnT1 + public void CallReturnT1WithCallerTypeArgs(GenericTarget target, object input) + { + target.ReturnT1(input); + } + + public void CallReturnT1WithCallerTypeArgsReversed(GenericTarget target, object input) + { + target.ReturnT1(input); + } + + public void CallReturnT1WithClass(GenericTarget target, object input) + { + target.ReturnT1(input); + } + + public void CallReturnT1WithStruct(GenericTarget target, object input) + { + target.ReturnT1(input); + } + + public void CallReturnT1WithReferenceTypeGenericInstantiation(GenericTarget, CallerT2> target, object input) + { + target.ReturnT1(input); + } + + public void CallReturnT1WithValueTypeGenericInstantiation(GenericTarget, CallerT2> target, object input) + { + target.ReturnT1(input); + } + #endregion + + #region CallReturnT2 + public void CallReturnT2WithCallerTypeArgs(GenericTarget target, object input) + { + target.ReturnT2(input); + } + + public void CallReturnT2WithCallerTypeArgsReversed(GenericTarget target, object input) + { + target.ReturnT2(input); + } + + public void CallReturnT2WithClass(GenericTarget target, object input) + { + target.ReturnT2(input); + } + + public void CallReturnT2WithStruct(GenericTarget, PointStruct> target, object input) + { + target.ReturnT2(input); + } + + public void CallReturnT2WithReferenceTypeGenericInstantiation(GenericTarget>>, Task> target, object input) + { + target.ReturnT2(input); + } + + public void CallReturnT2WithValueTypeGenericInstantiation(GenericTarget> target, object input) + { + target.ReturnT2(input); + } + #endregion + } +} diff --git a/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GenericTests/GenericTarget.cs b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GenericTests/GenericTarget.cs new file mode 100644 index 0000000000..9807e5c9df --- /dev/null +++ b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GenericTests/GenericTarget.cs @@ -0,0 +1,25 @@ +namespace Samples.ExampleLibrary.GenericTests +{ + public class GenericTarget + { + public M1 ReturnM1(M1 input1, M2 input2) + { + return input1; + } + + public M2 ReturnM2(M1 input1, M2 input2) + { + return input2; + } + + public T1 ReturnT1(object input) + { + return (T1)input; + } + + public T2 ReturnT2(object input) + { + return (T2)input; + } + } +} diff --git a/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GenericTests/PointStruct.cs b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GenericTests/PointStruct.cs new file mode 100644 index 0000000000..62e327d71d --- /dev/null +++ b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GenericTests/PointStruct.cs @@ -0,0 +1,14 @@ +namespace Samples.ExampleLibrary.GenericTests +{ + public struct PointStruct + { + public int X; + public int Y; + + public PointStruct(int x, int y) + { + X = x; + Y = y; + } + } +} diff --git a/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GenericTests/StructContainer.cs b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GenericTests/StructContainer.cs new file mode 100644 index 0000000000..fd1e62f23b --- /dev/null +++ b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GenericTests/StructContainer.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Samples.ExampleLibrary.GenericTests +{ + public struct StructContainer + { + public List Items { get; } + + public long Id { get; } + + public StructContainer(long id, List items) + { + Id = id; + Items = items; + } + } +} diff --git a/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GlobalSuppressions.cs b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GlobalSuppressions.cs new file mode 100644 index 0000000000..fa229a1f4d --- /dev/null +++ b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/GlobalSuppressions.cs @@ -0,0 +1,15 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Reviewed.")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Reviewed.")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Reviewed.")] +[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:Commas should be spaced correctly", Justification = "Reviewed.")] +[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1121:Use built-in type alias", Justification = "Reviewed.>")] +[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1314:Type parameter names should begin with T", Justification = "Reviewed.")] +[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:Do not use regions", Justification = "Reviewed.")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Reviewed.")] diff --git a/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/Samples.ExampleLibrary.csproj b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/Samples.ExampleLibrary.csproj new file mode 100644 index 0000000000..6a3e57d200 --- /dev/null +++ b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibrary/Samples.ExampleLibrary.csproj @@ -0,0 +1,7 @@ + + + + netstandard1.0 + + + diff --git a/test/test-applications/integrations/dependency-libs/Samples.ExampleLibraryTracer/Class1.cs b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibraryTracer/Class1.cs new file mode 100644 index 0000000000..b9df0afdd9 --- /dev/null +++ b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibraryTracer/Class1.cs @@ -0,0 +1,17 @@ +using System; + +namespace Samples.ExampleLibraryTracer +{ + public class Class1 + { + public int Add(int x, int y) + { + return 2 * (x + y); + } + + public virtual int Multiply(int x, int y) + { + return 2 * (x * y); + } + } +} diff --git a/test/test-applications/integrations/dependency-libs/Samples.ExampleLibraryTracer/Samples.ExampleLibraryTracer.csproj b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibraryTracer/Samples.ExampleLibraryTracer.csproj new file mode 100644 index 0000000000..6a3e57d200 --- /dev/null +++ b/test/test-applications/integrations/dependency-libs/Samples.ExampleLibraryTracer/Samples.ExampleLibraryTracer.csproj @@ -0,0 +1,7 @@ + + + + netstandard1.0 + + +