Tests for startup hook using simple name only (dotnet/core-setup#5597)
authorVitek Karas <vitek.karas@microsoft.com>
Thu, 11 Apr 2019 09:41:28 +0000 (02:41 -0700)
committerGitHub <noreply@github.com>
Thu, 11 Apr 2019 09:41:28 +0000 (02:41 -0700)
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

docs/installer/design-docs/host-startup-hook.md
src/installer/test/HostActivationTests/StartupHooks.cs

index 7061383..40dae41 100644 (file)
@@ -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
index d55c35f..49dc578 100644 (file)
@@ -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;