diff --git a/src/installer/tests/HostActivation.Tests/StartupHooks.cs b/src/installer/tests/HostActivation.Tests/StartupHooks.cs index a0533a60fd477..3e1685356167f 100644 --- a/src/installer/tests/HostActivation.Tests/StartupHooks.cs +++ b/src/installer/tests/HostActivation.Tests/StartupHooks.cs @@ -13,6 +13,7 @@ public class StartupHooks : IClassFixture { private SharedTestState sharedTestState; private string startupHookVarName = "DOTNET_STARTUP_HOOKS"; + private string startupHookRuntimeConfigName = "STARTUP_HOOKS"; private string startupHookSupport = "System.StartupHookProvider.IsSupported"; public StartupHooks(StartupHooks.SharedTestState fixture) @@ -105,6 +106,64 @@ public void Muxer_activation_of_Multiple_StartupHooks_Succeeds() .And.HaveStdOutContaining("Hello World"); } + [Fact] + public void Muxer_activation_of_RuntimeConfig_StartupHook_Succeeds() + { + var fixture = sharedTestState.PortableAppFixture.Copy(); + var dotnet = fixture.BuiltDotnet; + var appDll = fixture.TestProject.AppDll; + + var startupHookFixture = sharedTestState.StartupHookFixture.Copy(); + var startupHookDll = startupHookFixture.TestProject.AppDll; + + RuntimeConfig.FromFile(fixture.TestProject.RuntimeConfigJson) + .WithProperty(startupHookRuntimeConfigName, startupHookDll) + .Save(); + + // RuntimeConfig defined startup hook + dotnet.Exec(appDll) + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should().Pass() + .And.HaveStdOutContaining("Hello from startup hook!") + .And.HaveStdOutContaining("Hello World"); + } + + [Fact] + public void Muxer_activation_of_RuntimeConfig_And_Environment_StartupHooks_SucceedsInExpectedOrder() + { + var fixture = sharedTestState.PortableAppFixture.Copy(); + var dotnet = fixture.BuiltDotnet; + var appDll = fixture.TestProject.AppDll; + + var startupHookFixture = sharedTestState.StartupHookFixture.Copy(); + var startupHookDll = startupHookFixture.TestProject.AppDll; + + RuntimeConfig.FromFile(fixture.TestProject.RuntimeConfigJson) + .WithProperty(startupHookRuntimeConfigName, startupHookDll) + .Save(); + + var startupHook2Fixture = sharedTestState.StartupHookWithDependencyFixture.Copy(); + var startupHook2Dll = startupHook2Fixture.TestProject.AppDll; + + // include any char to counter output from other threads such as in #57243 + const string wildcardPattern = @"[\r\n\s.]*"; + + // RuntimeConfig and Environment startup hooks in expected order + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHook2Dll) + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should().Pass() + .And.HaveStdOutMatching("Hello from startup hook with dependency!" + + wildcardPattern + + "Hello from startup hook!" + + wildcardPattern + + "Hello World"); + } + // Empty startup hook variable [Fact] public void Muxer_activation_of_Empty_StartupHook_Variable_Succeeds() diff --git a/src/native/corehost/hostpolicy/hostpolicy_context.cpp b/src/native/corehost/hostpolicy/hostpolicy_context.cpp index 324c70e63ff27..22d26f25d92af 100644 --- a/src/native/corehost/hostpolicy/hostpolicy_context.cpp +++ b/src/native/corehost/hostpolicy/hostpolicy_context.cpp @@ -281,11 +281,16 @@ int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const a pal::string_t startup_hooks; if (pal::getenv(_X("DOTNET_STARTUP_HOOKS"), &startup_hooks)) { - if (!coreclr_properties.add(common_property::StartUpHooks, startup_hooks.c_str())) + const pal::char_t *config_startup_hooks; + if (coreclr_properties.try_get(common_property::StartUpHooks, &config_startup_hooks)) { - log_duplicate_property_error(coreclr_property_bag_t::common_property_to_string(common_property::StartUpHooks)); - return StatusCode::LibHostDuplicateProperty; + // env startup hooks shoold have precedence over config startup hooks + // therefore append config_startup_hooks AFTER startup_hooks + startup_hooks.push_back(PATH_SEPARATOR); + startup_hooks.append(config_startup_hooks); } + + coreclr_properties.add(common_property::StartUpHooks, startup_hooks.c_str()); } // Single-File Bundle Probe @@ -297,7 +302,7 @@ int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const a if (!coreclr_properties.add(common_property::BundleProbe, ptr_stream.str().c_str())) { - log_duplicate_property_error(coreclr_property_bag_t::common_property_to_string(common_property::StartUpHooks)); + log_duplicate_property_error(coreclr_property_bag_t::common_property_to_string(common_property::BundleProbe)); return StatusCode::LibHostDuplicateProperty; } } @@ -312,7 +317,7 @@ int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const a if (!coreclr_properties.add(common_property::PInvokeOverride, ptr_stream.str().c_str())) { - log_duplicate_property_error(coreclr_property_bag_t::common_property_to_string(common_property::StartUpHooks)); + log_duplicate_property_error(coreclr_property_bag_t::common_property_to_string(common_property::PInvokeOverride)); return StatusCode::LibHostDuplicateProperty; } } @@ -321,7 +326,7 @@ int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const a #if defined(HOSTPOLICY_EMBEDDED) if (!coreclr_properties.add(common_property::HostPolicyEmbedded, _X("true"))) { - log_duplicate_property_error(coreclr_property_bag_t::common_property_to_string(common_property::StartUpHooks)); + log_duplicate_property_error(coreclr_property_bag_t::common_property_to_string(common_property::HostPolicyEmbedded)); return StatusCode::LibHostDuplicateProperty; } #endif