From 0ae75473168c76aa5b46497e4c9e1d05f2b65dbe Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 21 Jul 2025 11:39:56 -0700 Subject: [PATCH 1/4] Check DOTNET_HOST_* for host (COREHOST_*) tracing environment variables --- .../tests/HostActivation.Tests/Tracing.cs | 62 +++++++------------ src/installer/tests/TestUtils/Constants.cs | 6 +- src/native/corehost/hostmisc/trace.cpp | 39 ++++++++---- .../hostpolicy/hostpolicy_context.cpp | 2 +- 4 files changed, 54 insertions(+), 55 deletions(-) diff --git a/src/installer/tests/HostActivation.Tests/Tracing.cs b/src/installer/tests/HostActivation.Tests/Tracing.cs index 9c99d9d68d1b2c..9c0eb97c1f08ab 100644 --- a/src/installer/tests/HostActivation.Tests/Tracing.cs +++ b/src/installer/tests/HostActivation.Tests/Tracing.cs @@ -7,24 +7,18 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation { - public class Tracing : IClassFixture + public class Tracing { - private SharedTestState sharedTestState; + // Trace messages currently expected for running dotnet --list-runtimes + private const string ExpectedVerboseMessage = "--- Executing in muxer mode"; + private const string ExpectedInfoMessage = "--- Invoked dotnet"; - // Trace messages currently expected for a passing app (somewhat randomly selected) - private const string ExpectedVerboseMessage = "--- Begin breadcrumb write"; - private const string ExpectedInfoMessage = "Deps file:"; - private const string ExpectedBadPathMessage = "Unable to open COREHOST_TRACEFILE="; - - public Tracing(Tracing.SharedTestState fixture) - { - sharedTestState = fixture; - } + private const string ExpectedBadPathMessage = "Unable to open specified trace file for writing:"; [Fact] public void TracingOff() { - TestContext.BuiltDotNet.Exec(sharedTestState.App.AppDll) + TestContext.BuiltDotNet.Exec("--list-runtimes") .CaptureStdOut() .CaptureStdErr() .Execute() @@ -36,11 +30,10 @@ public void TracingOff() [Fact] public void TracingOnDefault() { - TestContext.BuiltDotNet.Exec(sharedTestState.App.AppDll) + TestContext.BuiltDotNet.Exec("--list-runtimes") .EnableTracingAndCaptureOutputs() .Execute() .Should().Pass() - .And.HaveStdOutContaining("Hello World") .And.HaveStdErrContaining(ExpectedInfoMessage) .And.HaveStdErrContaining(ExpectedVerboseMessage); } @@ -48,12 +41,11 @@ public void TracingOnDefault() [Fact] public void TracingOnVerbose() { - TestContext.BuiltDotNet.Exec(sharedTestState.App.AppDll) + TestContext.BuiltDotNet.Exec("--list-runtimes") .EnableTracingAndCaptureOutputs() .EnvironmentVariable(Constants.HostTracing.VerbosityEnvironmentVariable, "4") .Execute() .Should().Pass() - .And.HaveStdOutContaining("Hello World") .And.HaveStdErrContaining(ExpectedInfoMessage) .And.HaveStdErrContaining(ExpectedVerboseMessage); } @@ -61,12 +53,11 @@ public void TracingOnVerbose() [Fact] public void TracingOnInfo() { - TestContext.BuiltDotNet.Exec(sharedTestState.App.AppDll) + TestContext.BuiltDotNet.Exec("--list-runtimes") .EnableTracingAndCaptureOutputs() .EnvironmentVariable(Constants.HostTracing.VerbosityEnvironmentVariable, "3") .Execute() .Should().Pass() - .And.HaveStdOutContaining("Hello World") .And.HaveStdErrContaining(ExpectedInfoMessage) .And.NotHaveStdErrContaining(ExpectedVerboseMessage); } @@ -74,12 +65,11 @@ public void TracingOnInfo() [Fact] public void TracingOnWarning() { - TestContext.BuiltDotNet.Exec(sharedTestState.App.AppDll) + TestContext.BuiltDotNet.Exec("--list-runtimes") .EnableTracingAndCaptureOutputs() .EnvironmentVariable(Constants.HostTracing.VerbosityEnvironmentVariable, "2") .Execute() .Should().Pass() - .And.HaveStdOutContaining("Hello World") .And.NotHaveStdErrContaining(ExpectedInfoMessage) .And.NotHaveStdErrContaining(ExpectedVerboseMessage); } @@ -87,15 +77,13 @@ public void TracingOnWarning() [Fact] public void TracingOnToFileDefault() { - string traceFilePath; - TestContext.BuiltDotNet.Exec(sharedTestState.App.AppDll) + TestContext.BuiltDotNet.Exec("--list-runtimes") .EnableHostTracingToFile(out traceFilePath) .CaptureStdOut() .CaptureStdErr() .Execute() .Should().Pass() - .And.HaveStdOutContaining("Hello World") .And.NotHaveStdErrContaining(ExpectedInfoMessage) .And.NotHaveStdErrContaining(ExpectedVerboseMessage) .And.FileExists(traceFilePath) @@ -107,12 +95,11 @@ public void TracingOnToFileDefault() [Fact] public void TracingOnToFileBadPathDefault() { - TestContext.BuiltDotNet.Exec(sharedTestState.App.AppDll) + TestContext.BuiltDotNet.Exec("--list-runtimes") .EnableTracingAndCaptureOutputs() .EnvironmentVariable(Constants.HostTracing.TraceFileEnvironmentVariable, "badpath/TracingOnToFileBadPathDefault.log") .Execute() .Should().Pass() - .And.HaveStdOutContaining("Hello World") .And.HaveStdErrContaining(ExpectedInfoMessage) .And.HaveStdErrContaining(ExpectedVerboseMessage) .And.HaveStdErrContaining(ExpectedBadPathMessage); @@ -123,7 +110,7 @@ public void TracingOnToDirectory() { using (TestArtifact directory = TestArtifact.Create("trace")) { - var result = TestContext.BuiltDotNet.Exec(sharedTestState.App.AppDll) + var result = TestContext.BuiltDotNet.Exec("--list-runtimes") .EnableHostTracingToPath(directory.Location) .CaptureStdOut() .CaptureStdErr() @@ -131,7 +118,6 @@ public void TracingOnToDirectory() string traceFilePath = Path.Combine(directory.Location, $"{Path.GetFileNameWithoutExtension(Binaries.DotNet.FileName)}.{result.ProcessId}.log"); result.Should().Pass() - .And.HaveStdOutContaining("Hello World") .And.NotHaveStdErrContaining(ExpectedInfoMessage) .And.NotHaveStdErrContaining(ExpectedVerboseMessage) .And.FileExists(traceFilePath) @@ -139,20 +125,16 @@ public void TracingOnToDirectory() } } - public class SharedTestState : IDisposable + [Fact] + public void LegacyVariableName() { - public TestApp App { get; } - - public SharedTestState() - { - App = TestApp.CreateFromBuiltAssets("HelloWorld"); - App.CreateAppHost(); - } - - public void Dispose() - { - App?.Dispose(); - } + TestContext.BuiltDotNet.Exec("--list-runtimes") + .EnvironmentVariable("COREHOST_TRACE", "1") + .CaptureStdErr() + .Execute() + .Should().Pass() + .And.HaveStdErrContaining(ExpectedInfoMessage) + .And.HaveStdErrContaining(ExpectedVerboseMessage); } } } diff --git a/src/installer/tests/TestUtils/Constants.cs b/src/installer/tests/TestUtils/Constants.cs index 9eb5b6e661e32f..e9e2e21b2df22e 100644 --- a/src/installer/tests/TestUtils/Constants.cs +++ b/src/installer/tests/TestUtils/Constants.cs @@ -103,9 +103,9 @@ public static class MultilevelLookup public static class HostTracing { - public const string TraceLevelEnvironmentVariable = "COREHOST_TRACE"; - public const string TraceFileEnvironmentVariable = "COREHOST_TRACEFILE"; - public const string VerbosityEnvironmentVariable = "COREHOST_TRACE_VERBOSITY"; + public const string TraceLevelEnvironmentVariable = "DOTNET_HOST_TRACE"; + public const string TraceFileEnvironmentVariable = "DOTNET_HOST_TRACEFILE"; + public const string VerbosityEnvironmentVariable = "DOTNET_HOST_TRACE_VERBOSITY"; } public static class DotnetRoot diff --git a/src/native/corehost/hostmisc/trace.cpp b/src/native/corehost/hostmisc/trace.cpp index 70b36ebd3a42c3..4a68d196c239c4 100644 --- a/src/native/corehost/hostmisc/trace.cpp +++ b/src/native/corehost/hostmisc/trace.cpp @@ -12,13 +12,13 @@ #define TRACE_VERBOSITY_INFO 3 #define TRACE_VERBOSITY_VERBOSE 4 -// g_trace_verbosity is used to encode COREHOST_TRACE and COREHOST_TRACE_VERBOSITY to selectively control output of +// g_trace_verbosity is used to encode DOTNET_HOST_TRACE and DOTNET_HOST_TRACE_VERBOSITY to selectively control output of // trace::warn(), trace::info(), and trace::verbose() -// COREHOST_TRACE=0 COREHOST_TRACE_VERBOSITY=N/A implies g_trace_verbosity = 0. // Trace "disabled". error() messages will be produced. -// COREHOST_TRACE=1 COREHOST_TRACE_VERBOSITY=4 or unset implies g_trace_verbosity = 4. // Trace "enabled". verbose(), info(), warn() and error() messages will be produced -// COREHOST_TRACE=1 COREHOST_TRACE_VERBOSITY=3 implies g_trace_verbosity = 3. // Trace "enabled". info(), warn() and error() messages will be produced -// COREHOST_TRACE=1 COREHOST_TRACE_VERBOSITY=2 implies g_trace_verbosity = 2. // Trace "enabled". warn() and error() messages will be produced -// COREHOST_TRACE=1 COREHOST_TRACE_VERBOSITY=1 implies g_trace_verbosity = 1. // Trace "enabled". error() messages will be produced +// DOTNET_HOST_TRACE=0 DOTNET_HOST_TRACE_VERBOSITY=N/A implies g_trace_verbosity = 0. // Trace "disabled". error() messages will be produced. +// DOTNET_HOST_TRACE=1 DOTNET_HOST_TRACE_VERBOSITY=4 or unset implies g_trace_verbosity = 4. // Trace "enabled". verbose(), info(), warn() and error() messages will be produced +// DOTNET_HOST_TRACE=1 DOTNET_HOST_TRACE_VERBOSITY=3 implies g_trace_verbosity = 3. // Trace "enabled". info(), warn() and error() messages will be produced +// DOTNET_HOST_TRACE=1 DOTNET_HOST_TRACE_VERBOSITY=2 implies g_trace_verbosity = 2. // Trace "enabled". warn() and error() messages will be produced +// DOTNET_HOST_TRACE=1 DOTNET_HOST_TRACE_VERBOSITY=1 implies g_trace_verbosity = 1. // Trace "enabled". error() messages will be produced static int g_trace_verbosity = 0; static FILE * g_trace_file = nullptr; thread_local static trace::error_writer_fn g_error_writer = nullptr; @@ -52,16 +52,33 @@ namespace }; spin_lock g_trace_lock; + + template + bool get_host_env_var(const pal::char_t (&name)[NameLen], pal::string_t* value) + { + // DOTNET_HOST_* takes precedence + constexpr size_t dotnet_host_prefix_len = STRING_LENGTH("DOTNET_HOST_"); + pal::char_t dotnet_host_name[dotnet_host_prefix_len + NameLen] = _X("DOTNET_HOST_"); + memcpy(dotnet_host_name + dotnet_host_prefix_len, name, NameLen * sizeof(pal::char_t)); + if (pal::getenv(dotnet_host_name, value)) + return true; + + // COREHOST_* for backwards compatibility + constexpr size_t corehost_prefix_len = STRING_LENGTH("COREHOST_"); + pal::char_t corehost_name[corehost_prefix_len + NameLen] = _X("COREHOST_"); + memcpy(corehost_name + corehost_prefix_len, name, NameLen * sizeof(pal::char_t)); + return pal::getenv(corehost_name, value); + } } // -// Turn on tracing for the corehost based on "COREHOST_TRACE" & "COREHOST_TRACEFILE" env. +// Turn on tracing for the corehost based on "DOTNET_HOST_TRACE" & "DOTNET_HOST_TRACEFILE" env. // void trace::setup() { // Read trace environment variable pal::string_t trace_str; - if (!pal::getenv(_X("COREHOST_TRACE"), &trace_str)) + if (!get_host_env_var(_X("TRACE"), &trace_str)) { return; } @@ -91,7 +108,7 @@ bool trace::enable() std::lock_guard lock(g_trace_lock); g_trace_file = stderr; // Trace to stderr by default - if (pal::getenv(_X("COREHOST_TRACEFILE"), &tracefile_str)) + if (get_host_env_var(_X("TRACEFILE"), &tracefile_str)) { if (pal::is_directory(tracefile_str)) { @@ -122,7 +139,7 @@ bool trace::enable() } pal::string_t trace_str; - if (!pal::getenv(_X("COREHOST_TRACE_VERBOSITY"), &trace_str)) + if (!get_host_env_var(_X("TRACE_VERBOSITY"), &trace_str)) { g_trace_verbosity = TRACE_VERBOSITY_VERBOSE; // Verbose trace by default } @@ -134,7 +151,7 @@ bool trace::enable() if (file_open_error) { - trace::error(_X("Unable to open COREHOST_TRACEFILE=%s for writing"), tracefile_str.c_str()); + trace::error(_X("Unable to open specified trace file for writing: %s"), tracefile_str.c_str()); } return true; } diff --git a/src/native/corehost/hostpolicy/hostpolicy_context.cpp b/src/native/corehost/hostpolicy/hostpolicy_context.cpp index 9c0c7c0468d5cb..e513472211f01b 100644 --- a/src/native/corehost/hostpolicy/hostpolicy_context.cpp +++ b/src/native/corehost/hostpolicy/hostpolicy_context.cpp @@ -224,7 +224,7 @@ int hostpolicy_context_t::initialize(const hostpolicy_init_t &hostpolicy_init, c // otherwise fail early. if (!bundle::info_t::is_single_file_bundle()) { - trace::error(_X("Could not resolve CoreCLR path. For more details, enable tracing by setting COREHOST_TRACE environment variable to 1")); + trace::error(_X("Could not resolve CoreCLR path. For more details, enable tracing by setting DOTNET_HOST_TRACE environment variable to 1")); return StatusCode::CoreClrResolveFailure; } From f698f6181a2e344f5c0c6c82a94dcb47ae808b66 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 21 Jul 2025 11:57:32 -0700 Subject: [PATCH 2/4] Update src/native/corehost/hostmisc/trace.cpp --- src/native/corehost/hostmisc/trace.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/native/corehost/hostmisc/trace.cpp b/src/native/corehost/hostmisc/trace.cpp index 4a68d196c239c4..f0fd67409a93c2 100644 --- a/src/native/corehost/hostmisc/trace.cpp +++ b/src/native/corehost/hostmisc/trace.cpp @@ -56,6 +56,8 @@ namespace template bool get_host_env_var(const pal::char_t (&name)[NameLen], pal::string_t* value) { + assert(name[NameLen - 1] == _X('\0')); // name is expected to be null-terminated + // DOTNET_HOST_* takes precedence constexpr size_t dotnet_host_prefix_len = STRING_LENGTH("DOTNET_HOST_"); pal::char_t dotnet_host_name[dotnet_host_prefix_len + NameLen] = _X("DOTNET_HOST_"); From 31252b72d1ae04cd2fba85639d92df6838d157b3 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 21 Jul 2025 15:36:34 -0700 Subject: [PATCH 3/4] Fix OldHost_LatestRuntime_ForwardCompatible test --- .../tests/HostActivation.Tests/HostVersionCompatibility.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/installer/tests/HostActivation.Tests/HostVersionCompatibility.cs b/src/installer/tests/HostActivation.Tests/HostVersionCompatibility.cs index fe008645f75594..37a9a4b24e2168 100644 --- a/src/installer/tests/HostActivation.Tests/HostVersionCompatibility.cs +++ b/src/installer/tests/HostActivation.Tests/HostVersionCompatibility.cs @@ -80,7 +80,8 @@ private void OldHost_LatestRuntime_ForwardCompatible(TestApp previousVersionApp) // 2) App rolls forward to newer runtime File.Copy(previousVersionApp.AppExe, appExe, true); Command.Create(appExe) - .EnableTracingAndCaptureOutputs() + .CaptureStdOut().CaptureStdErr() + .EnvironmentVariable("COREHOST_TRACE", "1") // Old host, so we need to use the old variable name .Execute() .Should().Pass() .And.HaveStdOutContaining("Hello World") @@ -95,7 +96,8 @@ private void OldHost_LatestRuntime_ForwardCompatible(TestApp previousVersionApp) { File.Copy(previousVersionApp.HostFxrDll, app.HostFxrDll, true); Command.Create(appExe) - .EnableTracingAndCaptureOutputs() + .CaptureStdOut().CaptureStdErr() + .EnvironmentVariable("COREHOST_TRACE", "1") // Old host, so we need to use the old variable name .Execute() .Should().Pass() .And.HaveStdOutContaining("Hello World") From dbc9b1c7f00a2e7e0ae61248e1cb44ea4ca03d95 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 22 Jul 2025 12:14:29 -0700 Subject: [PATCH 4/4] Use pal::snwprintf --- src/native/corehost/hostmisc/trace.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/native/corehost/hostmisc/trace.cpp b/src/native/corehost/hostmisc/trace.cpp index f0fd67409a93c2..77f88d0f1dd5cd 100644 --- a/src/native/corehost/hostmisc/trace.cpp +++ b/src/native/corehost/hostmisc/trace.cpp @@ -60,15 +60,15 @@ namespace // DOTNET_HOST_* takes precedence constexpr size_t dotnet_host_prefix_len = STRING_LENGTH("DOTNET_HOST_"); - pal::char_t dotnet_host_name[dotnet_host_prefix_len + NameLen] = _X("DOTNET_HOST_"); - memcpy(dotnet_host_name + dotnet_host_prefix_len, name, NameLen * sizeof(pal::char_t)); + pal::char_t dotnet_host_name[dotnet_host_prefix_len + NameLen]; + pal::snwprintf(dotnet_host_name, ARRAY_SIZE(dotnet_host_name), _X("DOTNET_HOST_%s"), name); if (pal::getenv(dotnet_host_name, value)) return true; // COREHOST_* for backwards compatibility constexpr size_t corehost_prefix_len = STRING_LENGTH("COREHOST_"); - pal::char_t corehost_name[corehost_prefix_len + NameLen] = _X("COREHOST_"); - memcpy(corehost_name + corehost_prefix_len, name, NameLen * sizeof(pal::char_t)); + pal::char_t corehost_name[corehost_prefix_len + NameLen]; + pal::snwprintf(corehost_name, ARRAY_SIZE(corehost_name), _X("COREHOST_%s"), name); return pal::getenv(corehost_name, value); } }