From: Sven Boemer Date: Thu, 13 Sep 2018 16:25:46 +0000 (-0700) Subject: Add host startup hook and tests (dotnet/core-setup#4465) X-Git-Tag: submit/tizen/20210909.063632~11032^2~566 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=8c8e2858ded1bdc46c382a60d33e602312c3f236;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Add host startup hook and tests (dotnet/core-setup#4465) * Expose coreclr_create_delegate function * Add startup hook call Read DOTNET_STARTUP_HOOKS environment variable, and pass it along to a private function in System.Private.CoreLib (StartupHookProvider.ProcessStartupHookS). * Add startup hook tests * Add some more syntax checks * Reorder the tests * Add test for exclamation mark in assembly path * Fix error behavior for missing methods MissingMethodException is now only thrown when the Initialize method doesn't exist at all. This also reorganizes the existing tests for the invalid signature, and adds some cases. * Change test behavior to not expect eager file exists check * Expect full paths for startup hook. Fixes this in existing tests, and adds a new check for the error behavior when a relative path is passed. * Add test for startup hook trace output * Update tests to use new syntax (without type name) * Pass STARTUP_HOOKS property key to coreclr_initialize STARTUP_HOOKS is now passed to the clr during initialization, instead of using create_delegate in the host. This will allow the clr to call the startup hooks closer to the execution of the main method, allowing the threading apartment state to be set based on the main method's attributes. * Allow non-public Initialize method * Remove coreclr::create_delegate * Add comments clarifying purpose of startup hook test projects * Add test with ALC assembly resolve event The startup hook will inject a dependency into the app. * Fix the ALC startup hook testcase - Don't set ReferenceOutputAssembly in the projectreference, because that results in it not being published. - Dispose the new projects - Don't throw exceptions from the resolver, since they are swallowed. Commit migrated from https://github.com/dotnet/core-setup/commit/841b7e407f6b782b1f5900873f4ffc57875111c6 --- diff --git a/src/installer/corehost/cli/hostpolicy.cpp b/src/installer/corehost/cli/hostpolicy.cpp index e80a088..a9b8db1 100644 --- a/src/installer/corehost/cli/hostpolicy.cpp +++ b/src/installer/corehost/cli/hostpolicy.cpp @@ -108,7 +108,7 @@ int run(const arguments_t& args, pal::string_t* out_host_command_result = nullpt }; // Note: these variables' lifetime should be longer than coreclr_initialize. - std::vector tpa_paths_cstr, app_base_cstr, native_dirs_cstr, resources_dirs_cstr, fx_deps, deps, clrjit_path_cstr, probe_directories, clr_library_version; + std::vector tpa_paths_cstr, app_base_cstr, native_dirs_cstr, resources_dirs_cstr, fx_deps, deps, clrjit_path_cstr, probe_directories, clr_library_version, startup_hooks_cstr; pal::pal_clrstring(probe_paths.tpa, &tpa_paths_cstr); pal::pal_clrstring(args.app_root, &app_base_cstr); pal::pal_clrstring(probe_paths.native, &native_dirs_cstr); @@ -197,6 +197,15 @@ int run(const arguments_t& args, pal::string_t* out_host_command_result = nullpt property_values.push_back(app_base_cstr.data()); } + // Startup hooks + pal::string_t startup_hooks; + if (pal::getenv(_X("DOTNET_STARTUP_HOOKS"), &startup_hooks)) + { + pal::pal_clrstring(startup_hooks, &startup_hooks_cstr); + property_keys.push_back("STARTUP_HOOKS"); + property_values.push_back(startup_hooks_cstr.data()); + } + size_t property_size = property_keys.size(); assert(property_keys.size() == property_values.size()); diff --git a/src/installer/test/Assets/TestProjects/LightupLib/Program.cs b/src/installer/test/Assets/TestProjects/LightupLib/Program.cs index ee04cac..d40468c 100644 --- a/src/installer/test/Assets/TestProjects/LightupLib/Program.cs +++ b/src/installer/test/Assets/TestProjects/LightupLib/Program.cs @@ -8,7 +8,7 @@ namespace LightupLib public static string Hello(string name) { // Load a dependency of LightupLib - var t = typeof(Newtonsoft.Json.JsonReader); + var t = typeof(Newtonsoft.Json.JsonReader); if (t != null) return "Hello "+name; else diff --git a/src/installer/test/Assets/TestProjects/PortableAppWithMissingRef/PortableAppWithMissingRef.csproj b/src/installer/test/Assets/TestProjects/PortableAppWithMissingRef/PortableAppWithMissingRef.csproj new file mode 100644 index 0000000..138175a --- /dev/null +++ b/src/installer/test/Assets/TestProjects/PortableAppWithMissingRef/PortableAppWithMissingRef.csproj @@ -0,0 +1,23 @@ + + + + $(NETCoreAppFramework) + Exe + $(MNAVersion) + false + + + + + + + + + + False + + + + diff --git a/src/installer/test/Assets/TestProjects/PortableAppWithMissingRef/Program.cs b/src/installer/test/Assets/TestProjects/PortableAppWithMissingRef/Program.cs new file mode 100644 index 0000000..e38f537 --- /dev/null +++ b/src/installer/test/Assets/TestProjects/PortableAppWithMissingRef/Program.cs @@ -0,0 +1,17 @@ +using System; +using SharedLibrary; + +namespace PortableApp +{ + public static class Program + { + public static int Main(string[] args) + { + // Returns 1 if using the reference assembly, and 2 if + // using the assembly injected by the startup hook. This + // should never actually use the reference assembly, which + // is not published with the app. + return SharedType.Value; + } + } +} diff --git a/src/installer/test/Assets/TestProjects/PortableAppWithMissingRef/SharedLibrary/ReferenceLibrary.csproj b/src/installer/test/Assets/TestProjects/PortableAppWithMissingRef/SharedLibrary/ReferenceLibrary.csproj new file mode 100644 index 0000000..427e95c --- /dev/null +++ b/src/installer/test/Assets/TestProjects/PortableAppWithMissingRef/SharedLibrary/ReferenceLibrary.csproj @@ -0,0 +1,10 @@ + + + + $(NETCoreAppFramework) + Library + $(MNAVersion) + SharedLibrary + + + diff --git a/src/installer/test/Assets/TestProjects/PortableAppWithMissingRef/SharedLibrary/SharedLibrary.cs b/src/installer/test/Assets/TestProjects/PortableAppWithMissingRef/SharedLibrary/SharedLibrary.cs new file mode 100644 index 0000000..6a11a3b --- /dev/null +++ b/src/installer/test/Assets/TestProjects/PortableAppWithMissingRef/SharedLibrary/SharedLibrary.cs @@ -0,0 +1,11 @@ +using System; + +namespace SharedLibrary +{ + public class SharedType + { + // This is only used as a reference library for a portable + // app. It will never be called. + public static int Value = 1; + } +} diff --git a/src/installer/test/Assets/TestProjects/StartupHook/StartupHook.cs b/src/installer/test/Assets/TestProjects/StartupHook/StartupHook.cs new file mode 100644 index 0000000..6c2ebb9 --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHook/StartupHook.cs @@ -0,0 +1,10 @@ +using System; + +internal class StartupHook +{ + public static void Initialize() + { + // Normal success case with a simple startup hook. + Console.WriteLine("Hello from startup hook!"); + } +} diff --git a/src/installer/test/Assets/TestProjects/StartupHook/StartupHook.csproj b/src/installer/test/Assets/TestProjects/StartupHook/StartupHook.csproj new file mode 100644 index 0000000..07612ef --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHook/StartupHook.csproj @@ -0,0 +1,8 @@ + + + + $(NETCoreAppFramework) + $(MNAVersion) + + + diff --git a/src/installer/test/Assets/TestProjects/StartupHookFake/StartupHookFake.csproj b/src/installer/test/Assets/TestProjects/StartupHookFake/StartupHookFake.csproj new file mode 100644 index 0000000..30c7a27 --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookFake/StartupHookFake.csproj @@ -0,0 +1,12 @@ + + + + $(NETCoreAppFramework) + $(MNAVersion) + + + + + + + diff --git a/src/installer/test/Assets/TestProjects/StartupHookFake/StartupHookInvalidAssembly.dll b/src/installer/test/Assets/TestProjects/StartupHookFake/StartupHookInvalidAssembly.dll new file mode 100644 index 0000000..d487a97 --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookFake/StartupHookInvalidAssembly.dll @@ -0,0 +1 @@ +Used to test assembly load failure in startup hook path. \ No newline at end of file diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithAssemblyResolver/SharedLibrary/SharedLibrary.cs b/src/installer/test/Assets/TestProjects/StartupHookWithAssemblyResolver/SharedLibrary/SharedLibrary.cs new file mode 100644 index 0000000..f5e1599 --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithAssemblyResolver/SharedLibrary/SharedLibrary.cs @@ -0,0 +1,9 @@ +namespace SharedLibrary +{ + public class SharedType + { + // This is injected into a portable application by a startup + // hook. + public static int Value = 2; + } +} diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithAssemblyResolver/SharedLibrary/SharedLibrary.csproj b/src/installer/test/Assets/TestProjects/StartupHookWithAssemblyResolver/SharedLibrary/SharedLibrary.csproj new file mode 100644 index 0000000..2affa76 --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithAssemblyResolver/SharedLibrary/SharedLibrary.csproj @@ -0,0 +1,9 @@ + + + + $(NETCoreAppFramework) + Library + $(MNAVersion) + + + diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithAssemblyResolver/StartupHookWithAssemblyResolver.cs b/src/installer/test/Assets/TestProjects/StartupHookWithAssemblyResolver/StartupHookWithAssemblyResolver.cs new file mode 100644 index 0000000..caf14e5 --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithAssemblyResolver/StartupHookWithAssemblyResolver.cs @@ -0,0 +1,29 @@ +using System; +using System.IO; +using System.Reflection; +using System.Runtime.Loader; + +internal class StartupHook +{ + public static void Initialize() + { + AssemblyLoadContext.Default.Resolving += SharedHostPolicy.SharedAssemblyResolver.Resolve; + } +} + +namespace SharedHostPolicy +{ + public class SharedAssemblyResolver + { + public static Assembly Resolve(AssemblyLoadContext context, AssemblyName assemblyName) + { + if (assemblyName.Name == "SharedLibrary") + { + string startupHookDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + string sharedLibrary = Path.GetFullPath(Path.Combine(startupHookDirectory, "SharedLibrary.dll")); + return AssemblyLoadContext.Default.LoadFromAssemblyPath(sharedLibrary); + } + return null; + } + } +} diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithAssemblyResolver/StartupHookWithAssemblyResolver.csproj b/src/installer/test/Assets/TestProjects/StartupHookWithAssemblyResolver/StartupHookWithAssemblyResolver.csproj new file mode 100644 index 0000000..3fe7878 --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithAssemblyResolver/StartupHookWithAssemblyResolver.csproj @@ -0,0 +1,19 @@ + + + + $(NETCoreAppFramework) + $(MNAVersion) + false + + + + + + + + + + + + diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithDependency/StartupHookWithDependency.cs b/src/installer/test/Assets/TestProjects/StartupHookWithDependency/StartupHookWithDependency.cs new file mode 100644 index 0000000..bd04c4a --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithDependency/StartupHookWithDependency.cs @@ -0,0 +1,14 @@ +using System; + +internal class StartupHook +{ + public static void Initialize() + { + // This startup hook can pass or fail depending on whether the + // app comes with Newtonsoft.Json. + Console.WriteLine("Hello from startup hook with dependency!"); + + // A small operation involving NewtonSoft.Json to ensure the assembly is loaded properly + var t = typeof(Newtonsoft.Json.JsonReader); + } +} diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithDependency/StartupHookWithDependency.csproj b/src/installer/test/Assets/TestProjects/StartupHookWithDependency/StartupHookWithDependency.csproj new file mode 100644 index 0000000..8726c60 --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithDependency/StartupHookWithDependency.csproj @@ -0,0 +1,12 @@ + + + + $(NETCoreAppFramework) + $(MNAVersion) + + + + + + + diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithInstanceMethod/StartupHookWithInstanceMethod.cs b/src/installer/test/Assets/TestProjects/StartupHookWithInstanceMethod/StartupHookWithInstanceMethod.cs new file mode 100644 index 0000000..f369d47 --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithInstanceMethod/StartupHookWithInstanceMethod.cs @@ -0,0 +1,12 @@ +using System; + +internal class StartupHook +{ + public void Initialize() + { + // This hook should not be called because it's an instance + // method. Instead, the startup hook provider code should + // throw an exception. + Console.WriteLine("Hello from startup hook with instance method!"); + } +} diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithInstanceMethod/StartupHookWithInstanceMethod.csproj b/src/installer/test/Assets/TestProjects/StartupHookWithInstanceMethod/StartupHookWithInstanceMethod.csproj new file mode 100644 index 0000000..07612ef --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithInstanceMethod/StartupHookWithInstanceMethod.csproj @@ -0,0 +1,8 @@ + + + + $(NETCoreAppFramework) + $(MNAVersion) + + + diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithMultipleIncorrectSignatures/StartupHookWithMultipleIncorrectSignatures.cs b/src/installer/test/Assets/TestProjects/StartupHookWithMultipleIncorrectSignatures/StartupHookWithMultipleIncorrectSignatures.cs new file mode 100644 index 0000000..5b94f7e --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithMultipleIncorrectSignatures/StartupHookWithMultipleIncorrectSignatures.cs @@ -0,0 +1,22 @@ +using System; + +internal class StartupHook +{ + // Neither of these hooks should be called, because they have the + // wrong signature (it should be static void Initialize()). This + // is used to check that the provider code properly detects the + // case where there are multiple incorrect Initialize + // methods. Instead, the startup hook provider code should throw + // an exception. + + public static int Initialize() + { + Console.WriteLine("Hello from startup hook returning int!"); + return 10; + } + + public static void Initialize(int input) + { + Console.WriteLine("Hello from startup hook taking int! Input: " + input); + } +} diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithMultipleIncorrectSignatures/StartupHookWithMultipleIncorrectSignatures.csproj b/src/installer/test/Assets/TestProjects/StartupHookWithMultipleIncorrectSignatures/StartupHookWithMultipleIncorrectSignatures.csproj new file mode 100644 index 0000000..07612ef --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithMultipleIncorrectSignatures/StartupHookWithMultipleIncorrectSignatures.csproj @@ -0,0 +1,8 @@ + + + + $(NETCoreAppFramework) + $(MNAVersion) + + + diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithNonPublicMethod/StartupHookWithNonPublicMethod.cs b/src/installer/test/Assets/TestProjects/StartupHookWithNonPublicMethod/StartupHookWithNonPublicMethod.cs new file mode 100644 index 0000000..125fce5 --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithNonPublicMethod/StartupHookWithNonPublicMethod.cs @@ -0,0 +1,10 @@ +using System; + +internal class StartupHook +{ + static void Initialize() + { + // Success case with a startup hook that is a private method. + Console.WriteLine("Hello from startup hook with non-public method!"); + } +} diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithNonPublicMethod/StartupHookWithNonPublicMethod.csproj b/src/installer/test/Assets/TestProjects/StartupHookWithNonPublicMethod/StartupHookWithNonPublicMethod.csproj new file mode 100644 index 0000000..07612ef --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithNonPublicMethod/StartupHookWithNonPublicMethod.csproj @@ -0,0 +1,8 @@ + + + + $(NETCoreAppFramework) + $(MNAVersion) + + + diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithOverload/StartupHookWithOverload.cs b/src/installer/test/Assets/TestProjects/StartupHookWithOverload/StartupHookWithOverload.cs new file mode 100644 index 0000000..2fffd46 --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithOverload/StartupHookWithOverload.cs @@ -0,0 +1,18 @@ +using System; + +internal class StartupHook +{ + public static void Initialize() + { + // Success case with a startup hook that contains multiple + // Initialize methods. This is used to check that the startup + // hook provider doesn't get confused by the presence of an + // extra Initialize method with an incorrect signature. + Initialize(123); + } + + public static void Initialize(int input) + { + Console.WriteLine("Hello from startup hook with overload! Input: " + input); + } +} diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithOverload/StartupHookWithOverload.csproj b/src/installer/test/Assets/TestProjects/StartupHookWithOverload/StartupHookWithOverload.csproj new file mode 100644 index 0000000..07612ef --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithOverload/StartupHookWithOverload.csproj @@ -0,0 +1,8 @@ + + + + $(NETCoreAppFramework) + $(MNAVersion) + + + diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithParameter/StartupHookWithParameter.cs b/src/installer/test/Assets/TestProjects/StartupHookWithParameter/StartupHookWithParameter.cs new file mode 100644 index 0000000..c89eb29 --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithParameter/StartupHookWithParameter.cs @@ -0,0 +1,12 @@ +using System; + +internal class StartupHook +{ + public static void Initialize(int input) + { + // This hook should not be called because it takes a + // parameter. Instead, the startup hook provider code should + // throw an exception. + Console.WriteLine("Hello from startup hook taking int! Input: " + input); + } +} diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithParameter/StartupHookWithParameter.csproj b/src/installer/test/Assets/TestProjects/StartupHookWithParameter/StartupHookWithParameter.csproj new file mode 100644 index 0000000..07612ef --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithParameter/StartupHookWithParameter.csproj @@ -0,0 +1,8 @@ + + + + $(NETCoreAppFramework) + $(MNAVersion) + + + diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithReturnType/StartupHookWithReturnType.cs b/src/installer/test/Assets/TestProjects/StartupHookWithReturnType/StartupHookWithReturnType.cs new file mode 100644 index 0000000..a0853cc --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithReturnType/StartupHookWithReturnType.cs @@ -0,0 +1,13 @@ +using System; + +internal class StartupHook +{ + public static int Initialize() + { + // This hook should not be called because it doesn't have a + // void return type. Instead, the startup hook provider code + // should throw an exception. + Console.WriteLine("Hello from startup hook returning int!"); + return 10; + } +} diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithReturnType/StartupHookWithReturnType.csproj b/src/installer/test/Assets/TestProjects/StartupHookWithReturnType/StartupHookWithReturnType.csproj new file mode 100644 index 0000000..07612ef --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithReturnType/StartupHookWithReturnType.csproj @@ -0,0 +1,8 @@ + + + + $(NETCoreAppFramework) + $(MNAVersion) + + + diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithoutInitializeMethod/StartupHookWithoutInitializeMethod.cs b/src/installer/test/Assets/TestProjects/StartupHookWithoutInitializeMethod/StartupHookWithoutInitializeMethod.cs new file mode 100644 index 0000000..5fcbb8f --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithoutInitializeMethod/StartupHookWithoutInitializeMethod.cs @@ -0,0 +1,12 @@ +using System; + +internal class StartupHook +{ + public static void Init() + { + // This hook should not be called because it doesn't have the + // correct name (Initialize). Instead, the startup hook + // provider code should throw an exception. + Console.WriteLine("Hello from startup hook!"); + } +} diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithoutInitializeMethod/StartupHookWithoutInitializeMethod.csproj b/src/installer/test/Assets/TestProjects/StartupHookWithoutInitializeMethod/StartupHookWithoutInitializeMethod.csproj new file mode 100644 index 0000000..07612ef --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithoutInitializeMethod/StartupHookWithoutInitializeMethod.csproj @@ -0,0 +1,8 @@ + + + + $(NETCoreAppFramework) + $(MNAVersion) + + + diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithoutStartupHookType/StartupHookWithoutStartupHookType.cs b/src/installer/test/Assets/TestProjects/StartupHookWithoutStartupHookType/StartupHookWithoutStartupHookType.cs new file mode 100644 index 0000000..71bb435 --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithoutStartupHookType/StartupHookWithoutStartupHookType.cs @@ -0,0 +1,12 @@ +using System; + +internal class StartupHookWrongType +{ + public static void Initialize() + { + // This hook should not be called because it doesn't have the + // correct type name (StartupHook). Instead, the startup hook + // provider code should throw an exception. + Console.WriteLine("Hello from startup hook!"); + } +} diff --git a/src/installer/test/Assets/TestProjects/StartupHookWithoutStartupHookType/StartupHookWithoutStartupHookType.csproj b/src/installer/test/Assets/TestProjects/StartupHookWithoutStartupHookType/StartupHookWithoutStartupHookType.csproj new file mode 100644 index 0000000..07612ef --- /dev/null +++ b/src/installer/test/Assets/TestProjects/StartupHookWithoutStartupHookType/StartupHookWithoutStartupHookType.csproj @@ -0,0 +1,8 @@ + + + + $(NETCoreAppFramework) + $(MNAVersion) + + + diff --git a/src/installer/test/HostActivationTests/GivenThatICareAboutStartupHooks.cs b/src/installer/test/HostActivationTests/GivenThatICareAboutStartupHooks.cs new file mode 100644 index 0000000..0b20339 --- /dev/null +++ b/src/installer/test/HostActivationTests/GivenThatICareAboutStartupHooks.cs @@ -0,0 +1,717 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; +using System.Linq; +using Xunit; +using Microsoft.Extensions.DependencyModel; + +namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.StartupHooks +{ + public class GivenThatICareAboutStartupHooks : IClassFixture + { + private SharedTestState sharedTestState; + private string startupHookVarName = "DOTNET_STARTUP_HOOKS"; + + public GivenThatICareAboutStartupHooks(GivenThatICareAboutStartupHooks.SharedTestState fixture) + { + sharedTestState = fixture; + } + + // Run the app with a startup hook + [Fact] + public void Muxer_activation_of_StartupHook_Succeeds() + { + var fixture = sharedTestState.PreviouslyPublishedAndRestoredPortableAppProjectFixture.Copy(); + var dotnet = fixture.BuiltDotnet; + var appDll = fixture.TestProject.AppDll; + + var startupHookFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookProjectFixture.Copy(); + var startupHookDll = startupHookFixture.TestProject.AppDll; + + var startupHookWithNonPublicMethodFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookWithNonPublicMethodProjectFixture.Copy(); + var startupHookWithNonPublicMethodDll = startupHookWithNonPublicMethodFixture.TestProject.AppDll; + + // Simple startup hook + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookDll) + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should() + .Pass() + .And + .HaveStdOutContaining("Hello from startup hook!") + .And + .HaveStdOutContaining("Hello World"); + + // Non-public Initialize method + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookWithNonPublicMethodDll) + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should() + .Pass() + .And + .HaveStdOutContaining("Hello from startup hook with non-public method"); + + // Ensure startup hook tracing works + dotnet.Exec(appDll) + .EnvironmentVariable("COREHOST_TRACE", "1") + .EnvironmentVariable(startupHookVarName, startupHookDll) + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should() + .Pass() + .And + .HaveStdErrContaining("Property STARTUP_HOOKS = " + startupHookDll) + .And + .HaveStdOutContaining("Hello from startup hook!") + .And + .HaveStdOutContaining("Hello World"); + + // Startup hook in type that has an additional overload of Initialize with a different signature + startupHookFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookWithOverloadProjectFixture.Copy(); + startupHookDll = startupHookFixture.TestProject.AppDll; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookDll) + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should() + .Pass() + .And + .HaveStdOutContaining("Hello from startup hook with overload! Input: 123") + .And + .HaveStdOutContaining("Hello World"); + } + + // Run the app with multiple startup hooks + [Fact] + public void Muxer_activation_of_Multiple_StartupHooks_Succeeds() + { + var fixture = sharedTestState.PreviouslyPublishedAndRestoredPortableAppProjectFixture.Copy(); + var dotnet = fixture.BuiltDotnet; + var appDll = fixture.TestProject.AppDll; + + var startupHookFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookProjectFixture.Copy(); + var startupHookDll = startupHookFixture.TestProject.AppDll; + + var startupHook2Fixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookWithDependencyProjectFixture.Copy(); + var startupHook2Dll = startupHook2Fixture.TestProject.AppDll; + + // Multiple startup hooks + var startupHookVar = startupHookDll + Path.PathSeparator + startupHook2Dll; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should() + .Pass() + .And + .HaveStdOutContaining("Hello from startup hook!") + .And + .HaveStdOutContaining("Hello from startup hook with dependency!") + .And + .HaveStdOutContaining("Hello World"); + } + + // Empty startup hook variable + [Fact] + public void Muxer_activation_of_Empty_StartupHook_Variable_Succeeds() + { + var fixture = sharedTestState.PreviouslyPublishedAndRestoredPortableAppProjectFixture.Copy(); + var dotnet = fixture.BuiltDotnet; + var appDll = fixture.TestProject.AppDll; + + var startupHookVar = ""; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should() + .Pass() + .And + .HaveStdOutContaining("Hello World"); + } + + // Run the app with a startup hook assembly that depends on assemblies not on the TPA list + [Fact] + public void Muxer_activation_of_StartupHook_With_Missing_Dependencies_Fails() + { + var fixture = sharedTestState.PreviouslyPublishedAndRestoredPortableAppWithExceptionProjectFixture.Copy(); + var dotnet = fixture.BuiltDotnet; + var appDll = fixture.TestProject.AppDll; + + var startupHookFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookWithDependencyProjectFixture.Copy(); + var startupHookDll = startupHookFixture.TestProject.AppDll; + + // Startup hook has a dependency not on the TPA list + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookDll) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining("System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json"); + } + + // Run the app with an invalid syntax in startup hook variable + [Fact] + public void Muxer_activation_of_Invalid_StartupHook_Fails() + { + var fixture = sharedTestState.PreviouslyPublishedAndRestoredPortableAppProjectFixture.Copy(); + var dotnet = fixture.BuiltDotnet; + var appDll = fixture.TestProject.AppDll; + + var startupHookFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookProjectFixture.Copy(); + var startupHookDll = startupHookFixture.TestProject.AppDll; + + var fakeAssembly = Path.GetFullPath("Assembly.dll"); + var fakeAssembly2 = Path.GetFullPath("Assembly2.dll"); + + var expectedError = "System.ArgumentException: The syntax of the startup hook variable was invalid."; + + // Missing entries in the hook + var startupHookVar = fakeAssembly + Path.PathSeparator + Path.PathSeparator + fakeAssembly2; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining(expectedError); + + // Leading separator + startupHookVar = Path.PathSeparator + startupHookDll; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining(expectedError); + + // Trailing separator + startupHookVar = fakeAssembly + Path.PathSeparator + fakeAssembly2 + Path.PathSeparator; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining(expectedError); + + // Syntax errors are caught before any hooks run + startupHookVar = startupHookDll + Path.PathSeparator; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining(expectedError) + .And + .NotHaveStdOutContaining("Hello from startup hook!"); + } + + // Run the app with a relative path to the startup hook assembly + [Fact] + public void Muxer_activation_of_StartupHook_With_Relative_Path_Fails() + { + var fixture = sharedTestState.PreviouslyPublishedAndRestoredPortableAppProjectFixture.Copy(); + var dotnet = fixture.BuiltDotnet; + var appDll = fixture.TestProject.AppDll; + + var startupHookFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookProjectFixture.Copy(); + var startupHookDll = startupHookFixture.TestProject.AppDll; + + var relativeAssemblyPath = "Assembly.dll"; + + var expectedError = "System.ArgumentException: Absolute path information is required."; + + // Relative path + var startupHookVar = relativeAssemblyPath; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining(expectedError); + + // Relative path error is caught before any hooks run + startupHookVar = startupHookDll + Path.PathSeparator + "Assembly.dll"; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining(expectedError) + .And + .NotHaveStdOutContaining("Hello from startup hook!"); + } + + // Run the app with missing startup hook assembly + [Fact] + public void Muxer_activation_of_Missing_StartupHook_Assembly_Fails() + { + var fixture = sharedTestState.PreviouslyPublishedAndRestoredPortableAppProjectFixture.Copy(); + var dotnet = fixture.BuiltDotnet; + var appDll = fixture.TestProject.AppDll; + + var startupHookFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookProjectFixture.Copy(); + var startupHookDll = startupHookFixture.TestProject.AppDll; + var startupHookMissingDll = Path.Combine(Path.GetDirectoryName(startupHookDll), "StartupHookMissing.dll"); + + var expectedError = "System.IO.FileNotFoundException: Could not load file or assembly '{0}'."; + + // Missing dll is detected with appropriate error + var startupHookVar = startupHookMissingDll; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining(String.Format(expectedError, Path.GetFullPath(startupHookMissingDll))); + + // Missing dll is detected after previous hooks run + startupHookVar = startupHookDll + Path.PathSeparator + startupHookMissingDll; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdOutContaining("Hello from startup hook!") + .And + .HaveStdErrContaining(String.Format(expectedError, Path.GetFullPath((startupHookMissingDll)))); + } + + // Run the app with an invalid startup hook assembly + [Fact] + public void Muxer_activation_of_Invalid_StartupHook_Assembly_Fails() + { + var fixture = sharedTestState.PreviouslyPublishedAndRestoredPortableAppProjectFixture.Copy(); + var dotnet = fixture.BuiltDotnet; + var appDll = fixture.TestProject.AppDll; + + var startupHookFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookProjectFixture.Copy(); + var startupHookDll = startupHookFixture.TestProject.AppDll; + + var startupHookInvalidAssembly = sharedTestState.PreviouslyPublishedAndRestoredStartupHookWithInvalidAssembly.Copy(); + var startupHookInvalidAssemblyDll = Path.Combine(Path.GetDirectoryName(startupHookInvalidAssembly.TestProject.AppDll), "StartupHookInvalidAssembly.dll"); + + var expectedError = "System.BadImageFormatException"; + + // Dll load gives meaningful error message + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookInvalidAssemblyDll) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining(expectedError); + + // Dll load error happens after previous hooks run + var startupHookVar = startupHookDll + Path.PathSeparator + startupHookInvalidAssemblyDll; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining(expectedError); + } + + // Run the app with the startup hook type missing + [Fact] + public void Muxer_activation_of_Missing_StartupHook_Type_Fails() + { + var fixture = sharedTestState.PreviouslyPublishedAndRestoredPortableAppProjectFixture.Copy(); + var dotnet = fixture.BuiltDotnet; + var appDll = fixture.TestProject.AppDll; + + var startupHookFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookProjectFixture.Copy(); + var startupHookDll = startupHookFixture.TestProject.AppDll; + + var startupHookMissingTypeFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookWithoutStartupHookTypeProjectFixture.Copy(); + var startupHookMissingTypeDll = startupHookMissingTypeFixture.TestProject.AppDll; + + // Missing type is detected + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookMissingTypeDll) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining("System.TypeLoadException: Could not load type 'StartupHook' from assembly 'StartupHook"); + + // Missing type is detected after previous hooks have run + var startupHookVar = startupHookDll + Path.PathSeparator + startupHookMissingTypeDll; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdOutContaining("Hello from startup hook!") + .And + .HaveStdErrContaining("System.TypeLoadException: Could not load type 'StartupHook' from assembly 'StartupHookWithoutStartupHookType"); + } + + + // Run the app with a startup hook that doesn't have any Initialize method + [Fact] + public void Muxer_activation_of_StartupHook_With_Missing_Method() + { + var fixture = sharedTestState.PreviouslyPublishedAndRestoredPortableAppProjectFixture.Copy(); + var dotnet = fixture.BuiltDotnet; + var appDll = fixture.TestProject.AppDll; + + var startupHookFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookProjectFixture.Copy(); + var startupHookDll = startupHookFixture.TestProject.AppDll; + + var startupHookMissingMethodFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookWithoutInitializeMethodProjectFixture.Copy(); + var startupHookMissingMethodDll = startupHookMissingMethodFixture.TestProject.AppDll; + + var expectedError = "System.MissingMethodException: Method 'StartupHook.Initialize' not found."; + + // No Initialize method + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookMissingMethodDll) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining(expectedError); + + // Missing Initialize method is caught after previous hooks have run + var startupHookVar = startupHookDll + Path.PathSeparator + startupHookMissingMethodDll; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdOutContaining("Hello from startup hook!") + .And + .HaveStdErrContaining(expectedError); + } + + // Run the app with startup hook that has no static void Initialize() method + [Fact] + public void Muxer_activation_of_StartupHook_With_Incorrect_Method_Signature_Fails() + { + var fixture = sharedTestState.PreviouslyPublishedAndRestoredPortableAppProjectFixture.Copy(); + var dotnet = fixture.BuiltDotnet; + var appDll = fixture.TestProject.AppDll; + + var startupHookFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookProjectFixture.Copy(); + var startupHookDll = startupHookFixture.TestProject.AppDll; + + var expectedError = "System.ArgumentException: The signature of the startup hook 'StartupHook.Initialize' in assembly '{0}' was invalid. It must be 'public static void Initialize()'."; + + // Initialize is an instance method + var startupHookWithInstanceMethodFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookWithInstanceMethodProjectFixture.Copy(); + var startupHookWithInstanceMethodDll = startupHookWithInstanceMethodFixture.TestProject.AppDll; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookWithInstanceMethodDll) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining(String.Format(expectedError, startupHookWithInstanceMethodDll)); + + // Initialize method takes parameters + var startupHookWithParameterFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookWithParameterProjectFixture.Copy(); + var startupHookWithParameterDll = startupHookWithParameterFixture.TestProject.AppDll; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookWithParameterDll) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining(String.Format(expectedError, startupHookWithParameterDll)); + + // Initialize method has non-void return type + var startupHookWithReturnTypeFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookWithReturnTypeProjectFixture.Copy(); + var startupHookWithReturnTypeDll = startupHookWithReturnTypeFixture.TestProject.AppDll; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookWithReturnTypeDll) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining(String.Format(expectedError, startupHookWithReturnTypeDll)); + + // Initialize method that has multiple methods with an incorrect signature + var startupHookWithMultipleIncorrectSignaturesFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookWithMultipleIncorrectSignaturesProjectFixture.Copy(); + var startupHookWithMultipleIncorrectSignaturesDll = startupHookWithMultipleIncorrectSignaturesFixture.TestProject.AppDll; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookWithMultipleIncorrectSignaturesDll) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining(String.Format(expectedError, startupHookWithMultipleIncorrectSignaturesDll)); + + // Signature problem is caught after previous hooks have run + var startupHookVar = startupHookDll + Path.PathSeparator + startupHookWithMultipleIncorrectSignaturesDll; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdOutContaining("Hello from startup hook!") + .And + .HaveStdErrContaining(String.Format(expectedError, startupHookWithMultipleIncorrectSignaturesDll)); + } + + private static void RemoveLibraryFromDepsJson(string depsJsonPath, string libraryName) + { + DependencyContext context; + using (FileStream fileStream = File.Open(depsJsonPath, FileMode.Open)) + { + using (DependencyContextJsonReader reader = new DependencyContextJsonReader()) + { + context = reader.Read(fileStream); + } + } + + context = new DependencyContext(context.Target, + context.CompilationOptions, + context.CompileLibraries, + context.RuntimeLibraries.Select(lib => new RuntimeLibrary( + lib.Type, + lib.Name, + lib.Version, + lib.Hash, + lib.RuntimeAssemblyGroups.Select(assemblyGroup => new RuntimeAssetGroup( + assemblyGroup.Runtime, + assemblyGroup.RuntimeFiles.Where(f => !f.Path.EndsWith("SharedLibrary.dll")))).ToList().AsReadOnly(), + lib.NativeLibraryGroups, + lib.ResourceAssemblies, + lib.Dependencies, + lib.Serviceable, + lib.Path, + lib.HashPath, + lib.RuntimeStoreManifestName)), + context.RuntimeGraph); + + using (FileStream fileStream = File.Open(depsJsonPath, FileMode.Truncate, FileAccess.Write)) + { + DependencyContextWriter writer = new DependencyContextWriter(); + writer.Write(context, fileStream); + } + } + + // Run startup hook that adds an assembly resolver + [Fact] + public void Muxer_activation_of_StartupHook_With_Assembly_Resolver() + { + var fixture = sharedTestState.PreviouslyPublishedAndRestoredPortableAppWithMissingRefProjectFixture.Copy(); + var dotnet = fixture.BuiltDotnet; + var appDll = fixture.TestProject.AppDll; + var appDepsJson = Path.Combine(Path.GetDirectoryName(appDll), Path.GetFileNameWithoutExtension(appDll) + ".deps.json"); + RemoveLibraryFromDepsJson(appDepsJson, "SharedLibrary.dll"); + + var startupHookFixture = sharedTestState.PreviouslyPublishedAndRestoredStartupHookWithAssemblyResolver.Copy(); + var startupHookDll = startupHookFixture.TestProject.AppDll; + + // No startup hook results in failure due to missing app dependency + dotnet.Exec(appDll) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining("FileNotFoundException: Could not load file or assembly 'SharedLibrary"); + + // Startup hook with assembly resolver results in use of injected dependency (which has value 2) + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookDll) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .ExitWith(2); + } + + public class SharedTestState : IDisposable + { + // Entry point projects + public TestProjectFixture PreviouslyPublishedAndRestoredPortableAppProjectFixture { get; set; } + public TestProjectFixture PreviouslyPublishedAndRestoredPortableAppWithExceptionProjectFixture { get; set; } + // Entry point with missing reference assembly + public TestProjectFixture PreviouslyPublishedAndRestoredPortableAppWithMissingRefProjectFixture { get; set; } + + // Correct startup hooks + public TestProjectFixture PreviouslyPublishedAndRestoredStartupHookProjectFixture { get; set; } + public TestProjectFixture PreviouslyPublishedAndRestoredStartupHookWithOverloadProjectFixture { get; set; } + // Missing startup hook type (no StartupHook type defined) + public TestProjectFixture PreviouslyPublishedAndRestoredStartupHookWithoutStartupHookTypeProjectFixture { get; set; } + // Missing startup hook method (no Initialize method defined) + public TestProjectFixture PreviouslyPublishedAndRestoredStartupHookWithoutInitializeMethodProjectFixture { get; set; } + // Invalid startup hook assembly + public TestProjectFixture PreviouslyPublishedAndRestoredStartupHookWithInvalidAssembly { get; set; } + // Invalid startup hooks (incorrect signatures) + public TestProjectFixture PreviouslyPublishedAndRestoredStartupHookWithNonPublicMethodProjectFixture { get; set; } + public TestProjectFixture PreviouslyPublishedAndRestoredStartupHookWithInstanceMethodProjectFixture { get; set; } + public TestProjectFixture PreviouslyPublishedAndRestoredStartupHookWithParameterProjectFixture { get; set; } + public TestProjectFixture PreviouslyPublishedAndRestoredStartupHookWithReturnTypeProjectFixture { get; set; } + public TestProjectFixture PreviouslyPublishedAndRestoredStartupHookWithMultipleIncorrectSignaturesProjectFixture { get; set; } + // Valid startup hooks with incorrect behavior + public TestProjectFixture PreviouslyPublishedAndRestoredStartupHookWithDependencyProjectFixture { get; set; } + + // Startup hook with an assembly resolver + public TestProjectFixture PreviouslyPublishedAndRestoredStartupHookWithAssemblyResolver { get; set; } + + public RepoDirectoriesProvider RepoDirectories { get; set; } + + public SharedTestState() + { + RepoDirectories = new RepoDirectoriesProvider(); + + // Entry point projects + PreviouslyPublishedAndRestoredPortableAppProjectFixture = new TestProjectFixture("PortableApp", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(); + + PreviouslyPublishedAndRestoredPortableAppWithExceptionProjectFixture = new TestProjectFixture("PortableAppWithException", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(); + // Entry point with missing reference assembly + PreviouslyPublishedAndRestoredPortableAppWithMissingRefProjectFixture = new TestProjectFixture("PortableAppWithMissingRef", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(); + + // Correct startup hooks + PreviouslyPublishedAndRestoredStartupHookProjectFixture = new TestProjectFixture("StartupHook", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(); + PreviouslyPublishedAndRestoredStartupHookWithOverloadProjectFixture = new TestProjectFixture("StartupHookWithOverload", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(); + // Missing startup hook type (no StartupHook type defined) + PreviouslyPublishedAndRestoredStartupHookWithoutStartupHookTypeProjectFixture = new TestProjectFixture("StartupHookWithoutStartupHookType", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(); + // Missing startup hook method (no Initialize method defined) + PreviouslyPublishedAndRestoredStartupHookWithoutInitializeMethodProjectFixture = new TestProjectFixture("StartupHookWithoutInitializeMethod", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(); + // Invalid startup hook assembly + PreviouslyPublishedAndRestoredStartupHookWithInvalidAssembly = new TestProjectFixture("StartupHookFake", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(); + // Invalid startup hooks (incorrect signatures) + PreviouslyPublishedAndRestoredStartupHookWithNonPublicMethodProjectFixture = new TestProjectFixture("StartupHookWithNonPublicMethod", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(); + PreviouslyPublishedAndRestoredStartupHookWithInstanceMethodProjectFixture = new TestProjectFixture("StartupHookWithInstanceMethod", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(); + PreviouslyPublishedAndRestoredStartupHookWithParameterProjectFixture = new TestProjectFixture("StartupHookWithParameter", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(); + PreviouslyPublishedAndRestoredStartupHookWithReturnTypeProjectFixture = new TestProjectFixture("StartupHookWithReturnType", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(); + PreviouslyPublishedAndRestoredStartupHookWithMultipleIncorrectSignaturesProjectFixture = new TestProjectFixture("StartupHookWithMultipleIncorrectSignatures", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(); + // Valid startup hooks with incorrect behavior + PreviouslyPublishedAndRestoredStartupHookWithDependencyProjectFixture = new TestProjectFixture("StartupHookWithDependency", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(); + + // Startup hook with an assembly resolver + PreviouslyPublishedAndRestoredStartupHookWithAssemblyResolver = new TestProjectFixture("StartupHookWithAssemblyResolver", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(); + } + + public void Dispose() + { + // Entry point projects + PreviouslyPublishedAndRestoredPortableAppProjectFixture.Dispose(); + PreviouslyPublishedAndRestoredPortableAppWithExceptionProjectFixture.Dispose(); + // Entry point with missing reference assembly + PreviouslyPublishedAndRestoredPortableAppWithMissingRefProjectFixture.Dispose(); + + // Correct startup hooks + PreviouslyPublishedAndRestoredStartupHookProjectFixture.Dispose(); + PreviouslyPublishedAndRestoredStartupHookWithOverloadProjectFixture.Dispose(); + // Missing startup hook type (no StartupHook type defined) + PreviouslyPublishedAndRestoredStartupHookWithoutStartupHookTypeProjectFixture.Dispose(); + // Missing startup hook method (no Initialize method defined) + PreviouslyPublishedAndRestoredStartupHookWithoutInitializeMethodProjectFixture.Dispose(); + // Invalid startup hook assembly + PreviouslyPublishedAndRestoredStartupHookWithInvalidAssembly.Dispose(); + // Invalid startup hooks (incorrect signatures) + PreviouslyPublishedAndRestoredStartupHookWithNonPublicMethodProjectFixture.Dispose(); + PreviouslyPublishedAndRestoredStartupHookWithInstanceMethodProjectFixture.Dispose(); + PreviouslyPublishedAndRestoredStartupHookWithParameterProjectFixture.Dispose(); + PreviouslyPublishedAndRestoredStartupHookWithReturnTypeProjectFixture.Dispose(); + PreviouslyPublishedAndRestoredStartupHookWithMultipleIncorrectSignaturesProjectFixture.Dispose(); + // Valid startup hooks with incorrect behavior + PreviouslyPublishedAndRestoredStartupHookWithDependencyProjectFixture.Dispose(); + + // Startup hook with an assembly resolver + PreviouslyPublishedAndRestoredStartupHookWithAssemblyResolver.Dispose(); + } + } + } +} diff --git a/src/installer/test/HostActivationTests/HostActivationTests.csproj b/src/installer/test/HostActivationTests/HostActivationTests.csproj index fa72d98..b798dba 100644 --- a/src/installer/test/HostActivationTests/HostActivationTests.csproj +++ b/src/installer/test/HostActivationTests/HostActivationTests.csproj @@ -16,6 +16,7 @@ + diff --git a/src/installer/test/TestUtils/Assertions/CommandResultAssertions.cs b/src/installer/test/TestUtils/Assertions/CommandResultAssertions.cs index 8888013..55100f6 100644 --- a/src/installer/test/TestUtils/Assertions/CommandResultAssertions.cs +++ b/src/installer/test/TestUtils/Assertions/CommandResultAssertions.cs @@ -60,6 +60,13 @@ namespace Microsoft.DotNet.CoreSetup.Test return new AndConstraint(this); } + public AndConstraint NotHaveStdOutContaining(string pattern) + { + Execute.Assertion.ForCondition(!_commandResult.StdErr.Contains(pattern)) + .FailWith("The command output contained a result it should not have contained: {0}{1}", pattern, GetDiagnosticsInfo()); + return new AndConstraint(this); + } + public AndConstraint HaveStdOutMatching(string pattern, RegexOptions options = RegexOptions.None) { Execute.Assertion.ForCondition(Regex.Match(_commandResult.StdOut, pattern, options).Success) diff --git a/src/installer/test/TestUtils/TestProjectFixture.cs b/src/installer/test/TestUtils/TestProjectFixture.cs index f3d3ba9..ecad2f6 100644 --- a/src/installer/test/TestUtils/TestProjectFixture.cs +++ b/src/installer/test/TestUtils/TestProjectFixture.cs @@ -375,6 +375,7 @@ namespace Microsoft.DotNet.CoreSetup.Test { publishArgs.Add("--framework"); publishArgs.Add(framework); + publishArgs.Add($"/p:NETCoreAppFramework={framework}"); } if (outputDirectory != null)