using System; using System.Collections.Generic; using System.Collections.Specialized; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Versioning; using Xunit.Abstractions; namespace IntegrationTests.Helpers { public class EnvironmentHelper { private static readonly Assembly ExecutingAssembly = Assembly.GetExecutingAssembly(); private static readonly string RuntimeFrameworkDescription = RuntimeInformation.FrameworkDescription.ToLower(); private static string _nukeOutputLocation; private readonly ITestOutputHelper _output; private readonly int _major; private readonly int _minor; private readonly string _patch = null; private readonly string _appNamePrepend; private readonly string _runtime; private readonly bool _isCoreClr; private readonly string _samplesDirectory; private readonly TargetFrameworkAttribute _targetFramework; private bool _requiresProfiling; private string _integrationsFileLocation; private string _profilerFileLocation; public EnvironmentHelper( string sampleName, Type anchorType, ITestOutputHelper output, string samplesDirectory = null, bool prependSamplesToAppName = true, bool requiresProfiling = true) { SampleName = sampleName; _samplesDirectory = samplesDirectory ?? Path.Combine("test", "test-applications", "integrations"); _targetFramework = Assembly.GetAssembly(anchorType).GetCustomAttribute(); _output = output; _requiresProfiling = requiresProfiling; var parts = _targetFramework.FrameworkName.Split(','); _runtime = parts[0]; _isCoreClr = _runtime.Equals(EnvironmentTools.CoreFramework); var versionParts = parts[1].Replace("Version=v", string.Empty).Split('.'); _major = int.Parse(versionParts[0]); _minor = int.Parse(versionParts[1]); if (versionParts.Length == 3) { _patch = versionParts[2]; } _appNamePrepend = prependSamplesToAppName ? "Samples." : string.Empty; } public bool DebugModeEnabled { get; set; } = true; public Dictionary CustomEnvironmentVariables { get; set; } = new Dictionary(); public string SampleName { get; } public string FullSampleName => $"{_appNamePrepend}{SampleName}"; public static bool IsNet5() { return Environment.Version.Major >= 5; } public static bool IsCoreClr() { return RuntimeFrameworkDescription.Contains("core") || IsNet5(); } public static void ClearProfilerEnvironmentVariables() { var environmentVariables = new[] { // .NET Core "CORECLR_ENABLE_PROFILING", "CORECLR_PROFILER", "CORECLR_PROFILER_PATH", "CORECLR_PROFILER_PATH_32", "CORECLR_PROFILER_PATH_64", "DOTNET_STARTUP_HOOKS", // .NET Framework "COR_ENABLE_PROFILING", "COR_PROFILER", "COR_PROFILER_PATH", // OpenTelemetry "OTEL_PROFILER_PROCESSES", "OTEL_DOTNET_TRACER_HOME", "OTEL_INTEGRATIONS", "OTEL_DISABLED_INTEGRATIONS", "OTEL_DOTNET_TRACER_ADDITIONAL_SOURCES", "OTEL_PROFILER_EXCLUDE_PROCESSES" }; foreach (string variable in environmentVariables) { Environment.SetEnvironmentVariable(variable, null); } } public static string GetNukeBuildOutput() { string nukeOutputPath = Path.Combine( EnvironmentTools.GetSolutionDirectory(), "bin", "tracer-home"); if (Directory.Exists(nukeOutputPath)) { _nukeOutputLocation = nukeOutputPath; return _nukeOutputLocation; } throw new Exception($"Unable to find Nuke output at: {nukeOutputPath}. Ensure Nuke has run first."); } public static bool IsRunningOnCI() { // https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables // Github sets CI environment variable string env = Environment.GetEnvironmentVariable("CI"); return !string.IsNullOrEmpty(env); } public void SetEnvironmentVariables( int agentPort, int aspNetCorePort, StringDictionary environmentVariables, bool enableStartupHook, string processToProfile = null) { string profilerEnabled = _requiresProfiling ? "1" : "0"; string profilerPath = GetProfilerPath(); if (IsCoreClr()) { // enableStartupHook should be true by default, and the parameter should only be set // to false when testing the case that instrumentation should not be available. if (enableStartupHook) { string hookPath = GetStartupHookOutputPath(); environmentVariables["DOTNET_STARTUP_HOOKS"] = hookPath; } environmentVariables["CORECLR_ENABLE_PROFILING"] = profilerEnabled; environmentVariables["CORECLR_PROFILER"] = EnvironmentTools.ProfilerClsId; environmentVariables["CORECLR_PROFILER_PATH"] = profilerPath; } else { environmentVariables["COR_ENABLE_PROFILING"] = profilerEnabled; environmentVariables["COR_PROFILER"] = EnvironmentTools.ProfilerClsId; environmentVariables["COR_PROFILER_PATH"] = profilerPath; } if (DebugModeEnabled) { environmentVariables["OTEL_TRACE_DEBUG"] = "1"; environmentVariables["OTEL_TRACE_LOG_DIRECTORY"] = Path.Combine(EnvironmentTools.GetSolutionDirectory(), "build_data", "profiler-logs"); } if (!string.IsNullOrEmpty(processToProfile)) { environmentVariables["OTEL_PROFILER_PROCESSES"] = Path.GetFileName(processToProfile); } string integrations = GetIntegrationsPath(); environmentVariables["OTEL_DOTNET_TRACER_HOME"] = GetNukeBuildOutput(); environmentVariables["OTEL_INTEGRATIONS"] = integrations; environmentVariables["OTEL_EXPORTER_ZIPKIN_ENDPOINT"] = $"http://127.0.0.1:{agentPort}"; environmentVariables["OTEL_EXPORTER"] = "zipkin"; // for ASP.NET Core sample apps, set the server's port environmentVariables["ASPNETCORE_URLS"] = $"http://127.0.0.1:{aspNetCorePort}/"; environmentVariables["OTEL_DOTNET_TRACER_ADDITIONAL_SOURCES"] = "Samples.*"; foreach (var key in CustomEnvironmentVariables.Keys) { environmentVariables[key] = CustomEnvironmentVariables[key]; } } public string GetProfilerPath() { if (_profilerFileLocation != null) { return _profilerFileLocation; } string extension = EnvironmentTools.GetOS() switch { "win" => "dll", "linux" => "so", "osx" => "dylib", _ => throw new PlatformNotSupportedException() }; string fileName = $"OpenTelemetry.ClrProfiler.Native.{extension}"; string nukeOutput = GetNukeBuildOutput(); string profilerPath = EnvironmentTools.IsWindows() ? Path.Combine(nukeOutput, $"win-{EnvironmentTools.GetPlatform().ToLower()}", fileName) : Path.Combine(nukeOutput, fileName); if (File.Exists(profilerPath)) { _profilerFileLocation = profilerPath; _output?.WriteLine($"Found profiler at {_profilerFileLocation}."); return _profilerFileLocation; } throw new Exception($"Unable to find profiler at: {profilerPath}"); } public string GetIntegrationsPath() { if (_integrationsFileLocation != null) { return _integrationsFileLocation; } string fileName = $"integrations.json"; string integrationsPath = Path.Combine(GetNukeBuildOutput(), fileName); if (File.Exists(integrationsPath)) { _integrationsFileLocation = integrationsPath; _output?.WriteLine($"Found integrations at {_profilerFileLocation}."); return _integrationsFileLocation; } throw new Exception($"Unable to find integrations at: {integrationsPath}"); } public string GetSampleApplicationPath(string packageVersion = "", string framework = "") { string extension = "exe"; if (IsCoreClr() || _samplesDirectory.Contains("aspnet")) { extension = "dll"; } var appFileName = $"{FullSampleName}.{extension}"; var sampleAppPath = Path.Combine(GetSampleApplicationOutputDirectory(packageVersion: packageVersion, framework: framework), appFileName); return sampleAppPath; } public string GetTestCommandForSampleApplicationPath(string packageVersion = "", string framework = "") { var appFileName = $"{FullSampleName}.dll"; var sampleAppPath = Path.Combine(GetSampleApplicationOutputDirectory(packageVersion: packageVersion, framework: framework), appFileName); return sampleAppPath; } public string GetSampleExecutionSource() { string executor; if (_samplesDirectory.Contains("aspnet")) { executor = $"C:\\Program Files{(Environment.Is64BitProcess ? string.Empty : " (x86)")}\\IIS Express\\iisexpress.exe"; } else if (IsCoreClr()) { executor = EnvironmentTools.IsWindows() ? "dotnet.exe" : "dotnet"; } else { var appFileName = $"{FullSampleName}.exe"; executor = Path.Combine(GetSampleApplicationOutputDirectory(), appFileName); if (!File.Exists(executor)) { throw new Exception($"Unable to find executing assembly at {executor}"); } } return executor; } public string GetDotNetTest() { if (EnvironmentTools.IsWindows()) { if (!IsCoreClr()) { string filePattern = @"C:\Program Files (x86)\Microsoft Visual Studio\{0}\{1}\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe"; List> lstTuple = new List> { Tuple.Create("2019", "Enterprise"), Tuple.Create("2019", "Professional"), Tuple.Create("2019", "Community"), Tuple.Create("2017", "Enterprise"), Tuple.Create("2017", "Professional"), Tuple.Create("2017", "Community"), }; foreach (Tuple tuple in lstTuple) { var tryPath = string.Format(filePattern, tuple.Item1, tuple.Item2); if (File.Exists(tryPath)) { return tryPath; } } } return "dotnet.exe"; } return "dotnet"; } public string GetSampleProjectDirectory() { var solutionDirectory = EnvironmentTools.GetSolutionDirectory(); var projectDir = Path.Combine( solutionDirectory, _samplesDirectory, $"{FullSampleName}"); return projectDir; } public string GetSampleApplicationOutputDirectory(string packageVersion = "", string framework = "") { var targetFramework = string.IsNullOrEmpty(framework) ? GetTargetFramework() : framework; var binDir = Path.Combine( GetSampleProjectDirectory(), "bin"); if (_samplesDirectory.Contains("aspnet")) { return Path.Combine( binDir, EnvironmentTools.GetBuildConfiguration(), "app.publish"); } return Path.Combine( binDir, packageVersion, EnvironmentTools.GetPlatform().ToLowerInvariant(), EnvironmentTools.GetBuildConfiguration(), targetFramework); } public string GetTargetFramework() { if (_isCoreClr) { if (_major >= 5) { return $"net{_major}.{_minor}"; } return $"netcoreapp{_major}.{_minor}"; } return $"net{_major}{_minor}{_patch ?? string.Empty}"; } private static string GetStartupHookOutputPath() { string startupHookOutputPath = Path.Combine( GetNukeBuildOutput(), "netcoreapp3.1", "OpenTelemetry.Instrumentation.StartupHook.dll"); return startupHookOutputPath; } } }