From: Vitek Karas Date: Thu, 11 Apr 2019 09:41:28 +0000 (-0700) Subject: Tests for startup hook using simple name only (dotnet/core-setup#5597) X-Git-Tag: submit/tizen/20210909.063632~11032^2~267 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=2865ac364e565c52ef4a958da610e078b3b0783b;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Tests for startup hook using simple name only (dotnet/core-setup#5597) Also includes: Update dependencies from https://github.com/dotnet/coreclr build 20190410.72 - Microsoft.NETCore.Runtime.CoreCLR - 3.0.0-preview5-27610-72 Commit migrated from https://github.com/dotnet/core-setup/commit/0cc40ba37ae41be6f66128278df898c4f6ea0a85 --- diff --git a/docs/installer/design-docs/host-startup-hook.md b/docs/installer/design-docs/host-startup-hook.md index 7061383..40dae41 100644 --- a/docs/installer/design-docs/host-startup-hook.md +++ b/docs/installer/design-docs/host-startup-hook.md @@ -34,12 +34,34 @@ Windows: DOTNET_STARTUP_HOOKS=D:\path\to\StartupHook1.dll;D:\path\to\StartupHook2.dll ``` -This variable is a list of absolute assembly paths, delimited by the +This variable is a list of assembly paths or names, delimited by the platform-specific path separator (`;` on Windows and `:` on Unix). It -may not contain any empty entries or a trailing path separator. The +may contain leading, trailing or duplicate path separators. The type must be named `StartupHook` without any namespace, and should be `internal`. +Each part may be either +* absolute path to the assembly with the startup hook. In this case + the assembly is loaded from the specified path before running + the startup hook. +* name of the assembly with the startup hook. In this case the assembly + is loaded by its name from the `AssemblyLoadContext.Default`. For + this to work the assembly needs to be part of the application + otherwise the default context won't be able to resolve it. The assembly + name must not be a relative path, so the following rules apply + * the assembly name must not contain directory separator characters + `/` and `\` + * the assembly name must not contain the space characters ` ` and + the comma character `,` + * the assembly name must not end with `.dll` (any casing) + * the assembly name must be considered a valid assembly name as specified + by the `AssemblyName` class. + +Note that white-spaces are preserved and considered part of the specified +path/name. So for example path separator followed by a white-space and +another path separator is invalid, since the white-space only string +in between the path separators will be considered as assembly name. + Setting this environment variable will cause the `public static void Initialize()` method of the `StartupHook` type in each of the specified assemblies to be called in order, synchronously, before the diff --git a/src/installer/test/HostActivationTests/StartupHooks.cs b/src/installer/test/HostActivationTests/StartupHooks.cs index d55c35f..49dc578 100644 --- a/src/installer/test/HostActivationTests/StartupHooks.cs +++ b/src/installer/test/HostActivationTests/StartupHooks.cs @@ -144,9 +144,9 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation .And.HaveStdErrContaining("System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json"); } - // Run the app with an invalid syntax in startup hook variable + // Different variants of the startup hook variable format [Fact] - public void Muxer_activation_of_Invalid_StartupHook_Fails() + public void Muxer_activation_of_StartupHook_VariableVariants() { var fixture = sharedTestState.PortableAppFixture.Copy(); var dotnet = fixture.BuiltDotnet; @@ -155,20 +155,30 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation var startupHookFixture = sharedTestState.StartupHookFixture.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."; + var startupHook2Fixture = sharedTestState.StartupHookWithDependencyFixture.Copy(); + var startupHook2Dll = startupHook2Fixture.TestProject.AppDll; // Missing entries in the hook - var startupHookVar = fakeAssembly + Path.PathSeparator + Path.PathSeparator + fakeAssembly2; + var startupHookVar = startupHookDll + Path.PathSeparator + Path.PathSeparator + startupHook2Dll; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should().Pass() + .And.HaveStdOutContaining("Hello from startup hook!") + .And.HaveStdOutContaining("Hello from startup hook with dependency!") + .And.HaveStdOutContaining("Hello World"); + + // Whitespace is invalid + startupHookVar = startupHookDll + Path.PathSeparator + " " + Path.PathSeparator + startupHook2Dll; dotnet.Exec(appDll) .EnvironmentVariable(startupHookVarName, startupHookVar) .CaptureStdOut() .CaptureStdErr() .Execute(fExpectedToFail: true) .Should().Fail() - .And.HaveStdErrContaining(expectedError); + .And.HaveStdErrContaining("System.ArgumentException: The startup hook simple assembly name ' ' is invalid."); // Leading separator startupHookVar = Path.PathSeparator + startupHookDll; @@ -177,34 +187,117 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation .CaptureStdOut() .CaptureStdErr() .Execute(fExpectedToFail: true) - .Should().Fail() - .And.HaveStdErrContaining(expectedError); + .Should().Pass() + .And.HaveStdOutContaining("Hello from startup hook!") + .And.HaveStdOutContaining("Hello World"); // Trailing separator - startupHookVar = fakeAssembly + Path.PathSeparator + fakeAssembly2 + Path.PathSeparator; + startupHookVar = startupHookDll + Path.PathSeparator + startupHook2Dll + Path.PathSeparator; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should().Pass() + .And.HaveStdOutContaining("Hello from startup hook!") + .And.HaveStdOutContaining("Hello from startup hook with dependency!") + .And.HaveStdOutContaining("Hello World"); + } + + [Fact] + public void Muxer_activation_of_StartupHook_With_Invalid_Simple_Name_Fails() + { + var fixture = sharedTestState.PortableAppFixture.Copy(); + var dotnet = fixture.BuiltDotnet; + var appDll = fixture.TestProject.AppDll; + + var startupHookFixture = sharedTestState.StartupHookFixture.Copy(); + var startupHookDll = startupHookFixture.TestProject.AppDll; + + var relativeAssemblyPath = $".{Path.DirectorySeparatorChar}Assembly"; + + var expectedError = "System.ArgumentException: The startup hook simple assembly name '{0}' is invalid."; + + // With directory separator + var startupHookVar = relativeAssemblyPath; dotnet.Exec(appDll) .EnvironmentVariable(startupHookVarName, startupHookVar) .CaptureStdOut() .CaptureStdErr() .Execute(fExpectedToFail: true) .Should().Fail() - .And.HaveStdErrContaining(expectedError); + .And.HaveStdErrContaining(string.Format(expectedError, startupHookVar)) + .And.NotHaveStdErrContaining("--->"); + + // With alternative directory separator + startupHookVar = $".{Path.AltDirectorySeparatorChar}Assembly"; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should().Fail() + .And.HaveStdErrContaining(string.Format(expectedError, startupHookVar)) + .And.NotHaveStdErrContaining("--->"); - // Syntax errors are caught before any hooks run - startupHookVar = startupHookDll + Path.PathSeparator; + // With comma + startupHookVar = $"Assembly,version=1.0.0.0"; dotnet.Exec(appDll) .EnvironmentVariable(startupHookVarName, startupHookVar) .CaptureStdOut() .CaptureStdErr() .Execute(fExpectedToFail: true) .Should().Fail() - .And.HaveStdErrContaining(expectedError) + .And.HaveStdErrContaining(string.Format(expectedError, startupHookVar)) + .And.NotHaveStdErrContaining("--->"); + + // With space + startupHookVar = $"Assembly version"; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should().Fail() + .And.HaveStdErrContaining(string.Format(expectedError, startupHookVar)) + .And.NotHaveStdErrContaining("--->"); + + // With .dll suffix + startupHookVar = $".{Path.AltDirectorySeparatorChar}Assembly.DLl"; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should().Fail() + .And.HaveStdErrContaining(string.Format(expectedError, startupHookVar)) + .And.NotHaveStdErrContaining("--->"); + + // With invalid name + startupHookVar = $"Assembly=Name"; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should().Fail() + .And.HaveStdErrContaining(string.Format(expectedError, startupHookVar)) + .And.HaveStdErrContaining("---> System.IO.FileLoadException: The given assembly name or codebase was invalid."); + + // Relative path error is caught before any hooks run + startupHookVar = startupHookDll + Path.PathSeparator + relativeAssemblyPath; + dotnet.Exec(appDll) + .EnvironmentVariable(startupHookVarName, startupHookVar) + .CaptureStdOut() + .CaptureStdErr() + .Execute(fExpectedToFail: true) + .Should().Fail() + .And.HaveStdErrContaining(string.Format(expectedError, relativeAssemblyPath)) .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() + public void Muxer_activation_of_StartupHook_With_Missing_Assembly_Fails() { var fixture = sharedTestState.PortableAppFixture.Copy(); var dotnet = fixture.BuiltDotnet; @@ -213,30 +306,55 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation var startupHookFixture = sharedTestState.StartupHookFixture.Copy(); var startupHookDll = startupHookFixture.TestProject.AppDll; - var relativeAssemblyPath = "Assembly.dll"; + var expectedError = "System.ArgumentException: Startup hook assembly '{0}' failed to load."; - var expectedError = "System.ArgumentException: Absolute path information is required."; - - // Relative path - var startupHookVar = relativeAssemblyPath; + // With file path which doesn't exist + var startupHookVar = startupHookDll + ".missing.dll"; dotnet.Exec(appDll) .EnvironmentVariable(startupHookVarName, startupHookVar) .CaptureStdOut() .CaptureStdErr() .Execute(fExpectedToFail: true) .Should().Fail() - .And.HaveStdErrContaining(expectedError); + .And.HaveStdErrContaining(string.Format(expectedError, startupHookVar)) + .And.HaveStdErrContaining($"---> System.IO.FileNotFoundException: Could not load file or assembly '{startupHookVar}'. The system cannot find the file specified."); - // Relative path error is caught before any hooks run - startupHookVar = startupHookDll + Path.PathSeparator + "Assembly.dll"; + // With simple name which won't resolve + startupHookVar = "MissingAssembly"; dotnet.Exec(appDll) .EnvironmentVariable(startupHookVarName, startupHookVar) .CaptureStdOut() .CaptureStdErr() .Execute(fExpectedToFail: true) .Should().Fail() - .And.HaveStdErrContaining(expectedError) - .And.NotHaveStdOutContaining("Hello from startup hook!"); + .And.HaveStdErrContaining(string.Format(expectedError, startupHookVar)) + .And.HaveStdErrContaining($"---> System.IO.FileNotFoundException: Could not load file or assembly '{startupHookVar}"); + } + + [Fact] + public void Muxer_activation_of_StartupHook_WithSimpleAssemblyName_Succeeds() + { + var fixture = sharedTestState.PortableAppFixture.Copy(); + var startupHookFixture = sharedTestState.StartupHookFixture.Copy(); + var startupHookDll = startupHookFixture.TestProject.AppDll; + var startupHookAssemblyName = Path.GetFileNameWithoutExtension(startupHookDll); + + File.Copy(startupHookDll, Path.Combine(fixture.TestProject.BuiltApp.Location, Path.GetFileName(startupHookDll))); + + SharedFramework.AddReferenceToDepsJson( + fixture.TestProject.DepsJson, + $"{fixture.TestProject.AssemblyName}/1.0.0", + startupHookAssemblyName, + "1.0.0"); + + fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll) + .EnvironmentVariable(startupHookVarName, startupHookAssemblyName) + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should().Pass() + .And.HaveStdOutContaining("Hello from startup hook!") + .And.HaveStdOutContaining("Hello World"); } // Run the app with missing startup hook assembly @@ -349,7 +467,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation // Run the app with a startup hook that doesn't have any Initialize method [Fact] - public void Muxer_activation_of_StartupHook_With_Missing_Method() + public void Muxer_activation_of_StartupHook_With_Missing_Method_Fails() { var fixture = sharedTestState.PortableAppFixture.Copy(); var dotnet = fixture.BuiltDotnet;