From 10ead9507102ac7cd3a752a1e7456bfb3738e540 Mon Sep 17 00:00:00 2001 From: Vitek Karas Date: Tue, 11 Jun 2019 01:54:39 -0700 Subject: [PATCH] Improvements to the component dependency resolution tests (dotnet/core-setup#6760) * Move these under the DependencyResolution (since they belong there) * Refactor to avoid code duplication * Change it to not use published real builds of components, but instead simply create mocks on disk (the tests never actually run the components) * Change where the tests put files on disk * Reduce file copying for tests which don't need to modify the prepared components * Delete test assets which are not used anymore Commit migrated from https://github.com/dotnet/core-setup/commit/bba327461e7521b640be91127309760a18f12416 --- .../ComponentWithDependencies/Component.cs | 6 - .../ComponentDependency/ComponentDependency.csproj | 8 - .../ComponentDependency/Dependency.cs | 6 - .../ComponentWithDependencies.csproj | 14 - .../ComponentWithResources/Component.cs | 6 - .../ComponentWithResources.csproj | 8 - .../ComponentWithResources/Resource.en.resx | 123 ----- .../ComponentWithResources/Resource.resx | 123 ----- .../ComponentDependencyResolution.cs | 500 --------------------- .../DependencyResolutionBase.cs | 4 +- .../PortableAppRidAssetResolution.cs | 2 +- .../ResolveComponentDependencies.cs | 469 +++++++++++++++++++ .../test/HostActivationTests/NetCoreAppBuilder.cs | 119 ++++- src/installer/test/TestUtils/FileUtils.cs | 10 +- src/installer/test/TestUtils/TestApp.cs | 9 + src/installer/test/TestUtils/TestArtifact.cs | 11 +- 16 files changed, 592 insertions(+), 826 deletions(-) delete mode 100644 src/installer/test/Assets/TestProjects/ComponentWithDependencies/Component.cs delete mode 100644 src/installer/test/Assets/TestProjects/ComponentWithDependencies/ComponentDependency/ComponentDependency.csproj delete mode 100644 src/installer/test/Assets/TestProjects/ComponentWithDependencies/ComponentDependency/Dependency.cs delete mode 100644 src/installer/test/Assets/TestProjects/ComponentWithDependencies/ComponentWithDependencies.csproj delete mode 100644 src/installer/test/Assets/TestProjects/ComponentWithResources/Component.cs delete mode 100644 src/installer/test/Assets/TestProjects/ComponentWithResources/ComponentWithResources.csproj delete mode 100644 src/installer/test/Assets/TestProjects/ComponentWithResources/Resource.en.resx delete mode 100644 src/installer/test/Assets/TestProjects/ComponentWithResources/Resource.resx delete mode 100644 src/installer/test/HostActivationTests/ComponentDependencyResolution.cs create mode 100644 src/installer/test/HostActivationTests/DependencyResolution/ResolveComponentDependencies.cs diff --git a/src/installer/test/Assets/TestProjects/ComponentWithDependencies/Component.cs b/src/installer/test/Assets/TestProjects/ComponentWithDependencies/Component.cs deleted file mode 100644 index e6b49c6..0000000 --- a/src/installer/test/Assets/TestProjects/ComponentWithDependencies/Component.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Component -{ - public class Component - { - } -} \ No newline at end of file diff --git a/src/installer/test/Assets/TestProjects/ComponentWithDependencies/ComponentDependency/ComponentDependency.csproj b/src/installer/test/Assets/TestProjects/ComponentWithDependencies/ComponentDependency/ComponentDependency.csproj deleted file mode 100644 index 07612ef..0000000 --- a/src/installer/test/Assets/TestProjects/ComponentWithDependencies/ComponentDependency/ComponentDependency.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - $(NETCoreAppFramework) - $(MNAVersion) - - - diff --git a/src/installer/test/Assets/TestProjects/ComponentWithDependencies/ComponentDependency/Dependency.cs b/src/installer/test/Assets/TestProjects/ComponentWithDependencies/ComponentDependency/Dependency.cs deleted file mode 100644 index a2a8e47..0000000 --- a/src/installer/test/Assets/TestProjects/ComponentWithDependencies/ComponentDependency/Dependency.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace ComponentDependency -{ - public class Dependency - { - } -} \ No newline at end of file diff --git a/src/installer/test/Assets/TestProjects/ComponentWithDependencies/ComponentWithDependencies.csproj b/src/installer/test/Assets/TestProjects/ComponentWithDependencies/ComponentWithDependencies.csproj deleted file mode 100644 index 35d3b20..0000000 --- a/src/installer/test/Assets/TestProjects/ComponentWithDependencies/ComponentWithDependencies.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - $(NETCoreAppFramework) - $(MNAVersion) - - - - - - - - - diff --git a/src/installer/test/Assets/TestProjects/ComponentWithResources/Component.cs b/src/installer/test/Assets/TestProjects/ComponentWithResources/Component.cs deleted file mode 100644 index e6b49c6..0000000 --- a/src/installer/test/Assets/TestProjects/ComponentWithResources/Component.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Component -{ - public class Component - { - } -} \ No newline at end of file diff --git a/src/installer/test/Assets/TestProjects/ComponentWithResources/ComponentWithResources.csproj b/src/installer/test/Assets/TestProjects/ComponentWithResources/ComponentWithResources.csproj deleted file mode 100644 index 07612ef..0000000 --- a/src/installer/test/Assets/TestProjects/ComponentWithResources/ComponentWithResources.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - $(NETCoreAppFramework) - $(MNAVersion) - - - diff --git a/src/installer/test/Assets/TestProjects/ComponentWithResources/Resource.en.resx b/src/installer/test/Assets/TestProjects/ComponentWithResources/Resource.en.resx deleted file mode 100644 index cd32d60..0000000 --- a/src/installer/test/Assets/TestProjects/ComponentWithResources/Resource.en.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - English - - \ No newline at end of file diff --git a/src/installer/test/Assets/TestProjects/ComponentWithResources/Resource.resx b/src/installer/test/Assets/TestProjects/ComponentWithResources/Resource.resx deleted file mode 100644 index af54d18..0000000 --- a/src/installer/test/Assets/TestProjects/ComponentWithResources/Resource.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Neutral - - \ No newline at end of file diff --git a/src/installer/test/HostActivationTests/ComponentDependencyResolution.cs b/src/installer/test/HostActivationTests/ComponentDependencyResolution.cs deleted file mode 100644 index b4d94db..0000000 --- a/src/installer/test/HostActivationTests/ComponentDependencyResolution.cs +++ /dev/null @@ -1,500 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.DotNet.CoreSetup.Test.HostActivation -{ - public class ComponentDependencyResolution : IClassFixture - { - private SharedTestState sharedTestState; - private readonly ITestOutputHelper output; - - public ComponentDependencyResolution(SharedTestState fixture, ITestOutputHelper output) - { - sharedTestState = fixture; - this.output = output; - } - - private const string corehost_resolve_component_dependencies = "corehost_resolve_component_dependencies"; - private const string corehost_resolve_component_dependencies_multithreaded = "corehost_resolve_component_dependencies_multithreaded"; - - [Fact] - public void InvalidMainComponentAssemblyPathFails() - { - var fixture = sharedTestState.HostApiInvokerAppFixture.Copy(); - - string[] args = - { - corehost_resolve_component_dependencies, - fixture.TestProject.AppDll + "_invalid" - }; - fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, args) - .CaptureStdOut().CaptureStdErr().EnvironmentVariable("COREHOST_TRACE", "1") - .Execute() - .StdErrAfter("corehost_resolve_component_dependencies = {") - .Should().Pass() - .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Fail[0x80008092]") - .And.HaveStdOutContaining("corehost reported errors:") - .And.HaveStdOutContaining("Failed to locate managed application"); - } - - [Fact] - public void ComponentWithNoDependenciesAndNoDeps() - { - var fixture = sharedTestState.HostApiInvokerAppFixture.Copy(); - var componentFixture = sharedTestState.ComponentWithNoDependenciesFixture.Copy(); - - // Remove .deps.json - File.Delete(componentFixture.TestProject.DepsJson); - - string[] args = - { - corehost_resolve_component_dependencies, - componentFixture.TestProject.AppDll - }; - fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, args) - .CaptureStdOut().CaptureStdErr().EnvironmentVariable("COREHOST_TRACE", "1") - .Execute() - .StdErrAfter("corehost_resolve_component_dependencies = {") - .Should().Pass() - .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") - .And.HaveStdOutContaining($"corehost_resolve_component_dependencies assemblies:[{componentFixture.TestProject.AppDll}{Path.PathSeparator}]") - .And.HaveStdErrContaining($"app_root='{componentFixture.TestProject.OutputDirectory}{Path.DirectorySeparatorChar}'") - .And.HaveStdErrContaining($"deps='{componentFixture.TestProject.DepsJson}'") - .And.HaveStdErrContaining($"mgd_app='{componentFixture.TestProject.AppDll}'") - .And.HaveStdErrContaining($"-- arguments_t: dotnet shared store: '{Path.Combine(fixture.BuiltDotnet.BinPath, "store", sharedTestState.RepoDirectories.BuildArchitecture, fixture.Framework)}'"); - } - - [Fact] - public void ComponentWithNoDependencies() - { - var fixture = sharedTestState.HostApiInvokerAppFixture.Copy(); - var componentFixture = sharedTestState.ComponentWithNoDependenciesFixture.Copy(); - - string[] args = - { - corehost_resolve_component_dependencies, - componentFixture.TestProject.AppDll - }; - fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, args) - .CaptureStdOut().CaptureStdErr().EnvironmentVariable("COREHOST_TRACE", "1") - .Execute() - .StdErrAfter("corehost_resolve_component_dependencies = {") - .Should().Pass() - .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") - .And.HaveStdOutContaining($"corehost_resolve_component_dependencies assemblies:[{componentFixture.TestProject.AppDll}{Path.PathSeparator}]"); - } - - private static readonly string[] SupportedOsList = new string[] - { - "ubuntu", - "debian", - "fedora", - "opensuse", - "osx", - "rhel", - "win" - }; - - private string GetExpectedLibuvRid(TestProjectFixture fixture) - { - // Simplified version of the RID fallback for libuv - // Note that we have to take the architecture from the fixture (since this test may run on x64 but the fixture on x86) - // but we can't use the OS part from the fixture RID as that may be too generic (like linux-x64). - string currentRid = PlatformAbstractions.RuntimeEnvironment.GetRuntimeIdentifier(); - string fixtureRid = fixture.CurrentRid; - string osName = currentRid.Split('-')[0]; - string architecture = fixtureRid.Split('-')[1]; - - string supportedOsName = SupportedOsList.FirstOrDefault(a => osName.StartsWith(a)); - if (supportedOsName == null) - { - return null; - } - - osName = supportedOsName; - if (osName == "ubuntu") { osName = "debian"; } - if (osName == "win") { osName = "win7"; } - if (osName == "osx") { return osName; } - - return osName + "-" + architecture; - } - - [Fact] - public void ComponentWithDependencies() - { - var fixture = sharedTestState.HostApiInvokerAppFixture.Copy(); - var componentFixture = sharedTestState.ComponentWithDependenciesFixture.Copy(); - - string libuvRid = GetExpectedLibuvRid(fixture); - if (libuvRid == null) - { - output.WriteLine($"RID {PlatformAbstractions.RuntimeEnvironment.GetRuntimeIdentifier()} is not supported by libuv and thus we can't run this test on it."); - return; - } - - string[] args = - { - corehost_resolve_component_dependencies, - componentFixture.TestProject.AppDll - }; - fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, args) - .CaptureStdOut().CaptureStdErr().EnvironmentVariable("COREHOST_TRACE", "1") - .Execute() - .StdErrAfter("corehost_resolve_component_dependencies = {") - .Should().Pass() - .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") - .And.HaveStdOutContaining( - $"corehost_resolve_component_dependencies assemblies:[" + - $"{Path.Combine(componentFixture.TestProject.OutputDirectory, "ComponentDependency.dll")}{Path.PathSeparator}" + - $"{componentFixture.TestProject.AppDll}{Path.PathSeparator}" + - $"{Path.Combine(componentFixture.TestProject.OutputDirectory, "Newtonsoft.Json.dll")}{Path.PathSeparator}]") - .And.HaveStdOutContaining( - $"corehost_resolve_component_dependencies native_search_paths:[" + - $"{ExpectedProbingPaths(Path.Combine(componentFixture.TestProject.OutputDirectory, "runtimes", libuvRid, "native"))}]"); - } - - [Fact] - public void ComponentWithDependenciesAndDependencyRemoved() - { - var fixture = sharedTestState.HostApiInvokerAppFixture.Copy(); - var componentFixture = sharedTestState.ComponentWithDependenciesFixture.Copy(); - - // Remove a dependency - // This will cause the resolution to fail - File.Delete(Path.Combine(componentFixture.TestProject.OutputDirectory, "ComponentDependency.dll")); - - string[] args = - { - corehost_resolve_component_dependencies, - componentFixture.TestProject.AppDll - }; - fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, args) - .CaptureStdOut().CaptureStdErr().EnvironmentVariable("COREHOST_TRACE", "1") - .Execute() - .StdErrAfter("corehost_resolve_component_dependencies = {") - .Should().Pass() - .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") - .And.HaveStdOutContaining( - $"corehost_resolve_component_dependencies assemblies:[" + - $"{componentFixture.TestProject.AppDll}{Path.PathSeparator}" + - $"{Path.Combine(componentFixture.TestProject.OutputDirectory, "Newtonsoft.Json.dll")}{Path.PathSeparator}]"); - } - - [Fact] - public void ComponentWithDependenciesAndNoDeps() - { - var fixture = sharedTestState.HostApiInvokerAppFixture.Copy(); - var componentFixture = sharedTestState.ComponentWithDependenciesFixture.Copy(); - - // Remove .deps.json - File.Delete(componentFixture.TestProject.DepsJson); - - string[] args = - { - corehost_resolve_component_dependencies, - componentFixture.TestProject.AppDll - }; - fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, args) - .CaptureStdOut().CaptureStdErr().EnvironmentVariable("COREHOST_TRACE", "1") - .Execute() - .StdErrAfter("corehost_resolve_component_dependencies = {") - .Should().Pass() - .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") - .And.HaveStdOutContaining( - $"corehost_resolve_component_dependencies assemblies:[" + - $"{Path.Combine(componentFixture.TestProject.OutputDirectory, "ComponentDependency.dll")}{Path.PathSeparator}" + - $"{componentFixture.TestProject.AppDll}{Path.PathSeparator}" + - $"{Path.Combine(componentFixture.TestProject.OutputDirectory, "Newtonsoft.Json.dll")}{Path.PathSeparator}]"); - } - - [Fact] - public void ComponentWithDependenciesAndNoDepsAndDependencyRemoved() - { - var fixture = sharedTestState.HostApiInvokerAppFixture.Copy(); - var componentFixture = sharedTestState.ComponentWithDependenciesFixture.Copy(); - - // Remove .deps.json - File.Delete(componentFixture.TestProject.DepsJson); - - // Remove a dependency - // Since there's no .deps.json - there's no way for the system to know about this dependency and thus should not be reported. - File.Delete(Path.Combine(componentFixture.TestProject.OutputDirectory, "ComponentDependency.dll")); - - string[] args = - { - corehost_resolve_component_dependencies, - componentFixture.TestProject.AppDll - }; - fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, args) - .CaptureStdOut().CaptureStdErr().EnvironmentVariable("COREHOST_TRACE", "1") - .Execute() - .StdErrAfter("corehost_resolve_component_dependencies = {") - .Should().Pass() - .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") - .And.HaveStdOutContaining( - $"corehost_resolve_component_dependencies assemblies:[" + - $"{componentFixture.TestProject.AppDll}{Path.PathSeparator}" + - $"{Path.Combine(componentFixture.TestProject.OutputDirectory, "Newtonsoft.Json.dll")}{Path.PathSeparator}]"); - } - - [Fact] - public void ComponentWithSameDependencyWithDifferentExtensionShouldFail() - { - var fixture = sharedTestState.HostApiInvokerAppFixture.Copy(); - var componentFixture = sharedTestState.ComponentWithDependenciesFixture.Copy(); - - // Add a reference to another package which has asset with the same name as the existing ComponentDependency - // but with a different extension. This causes a failure. - SharedFramework.AddReferenceToDepsJson( - componentFixture.TestProject.DepsJson, - "ComponentWithDependencies/1.0.0", - "ComponentDependency_Dupe", - "1.0.0", - testAssembly: "ComponentDependency.notdll"); - - // Make sure the file exists so that we avoid failing due to missing file. - File.Copy( - Path.Combine(componentFixture.TestProject.OutputDirectory, "ComponentDependency.dll"), - Path.Combine(componentFixture.TestProject.OutputDirectory, "ComponentDependency.notdll")); - - string[] args = - { - corehost_resolve_component_dependencies, - componentFixture.TestProject.AppDll - }; - fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, args) - .CaptureStdOut().CaptureStdErr().EnvironmentVariable("COREHOST_TRACE", "1") - .Execute() - .StdErrAfter("corehost_resolve_component_dependencies = {") - .Should().Pass() - .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Fail[0x8000808C]") - .And.HaveStdOutContaining("corehost reported errors:") - .And.HaveStdOutContaining("An assembly specified in the application dependencies manifest (ComponentWithDependencies.deps.json) has already been found but with a different file extension") - .And.HaveStdOutContaining("package: 'ComponentDependency_Dupe', version: '1.0.0'") - .And.HaveStdOutContaining("path: 'ComponentDependency.notdll'") - .And.HaveStdOutContaining($"previously found assembly: '{Path.Combine(componentFixture.TestProject.OutputDirectory, "ComponentDependency.dll")}'"); - } - - // This test also validates that corehost_set_error_writer custom writer - // correctly captures errors from hostpolicy. - [Fact] - public void ComponentWithCorruptedDepsJsonShouldFail() - { - var fixture = sharedTestState.HostApiInvokerAppFixture.Copy(); - var componentFixture = sharedTestState.ComponentWithDependenciesFixture.Copy(); - - // Corrupt the .deps.json by appending } to it (malformed json) - File.WriteAllText( - componentFixture.TestProject.DepsJson, - File.ReadAllLines(componentFixture.TestProject.DepsJson) + "}"); - - string[] args = - { - corehost_resolve_component_dependencies, - componentFixture.TestProject.AppDll - }; - fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, args) - .CaptureStdOut().CaptureStdErr().EnvironmentVariable("COREHOST_TRACE", "1") - .Execute() - .StdErrAfter("corehost_resolve_component_dependencies = {") - .Should().Pass() - .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Fail[0x8000808B]") - .And.HaveStdOutContaining("corehost reported errors:") - .And.HaveStdOutContaining($"A JSON parsing exception occurred in [{componentFixture.TestProject.DepsJson}]: * Line 1, Column 2 Syntax error: Malformed token") - .And.HaveStdOutContaining($"Error initializing the dependency resolver: An error occurred while parsing: {componentFixture.TestProject.DepsJson}"); - } - - [Fact] - public void ComponentWithResourcesShouldReportResourceSearchPaths() - { - var fixture = sharedTestState.HostApiInvokerAppFixture.Copy(); - var componentFixture = sharedTestState.ComponentWithResourcesFixture.Copy(); - - string[] args = - { - corehost_resolve_component_dependencies, - componentFixture.TestProject.AppDll - }; - fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, args) - .CaptureStdOut().CaptureStdErr().EnvironmentVariable("COREHOST_TRACE", "1") - .Execute() - .StdErrAfter("corehost_resolve_component_dependencies = {") - .Should().Pass() - .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") - .And.HaveStdOutContaining($"corehost_resolve_component_dependencies resource_search_paths:[" + - $"{ExpectedProbingPaths(componentFixture.TestProject.OutputDirectory)}]"); - } - - private string ExpectedProbingPaths(params string[] paths) - { - string result = string.Empty; - foreach (string path in paths) - { - string expectedPath = path; - if (expectedPath.EndsWith(Path.DirectorySeparatorChar)) - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // On non-windows the paths are normalized to not end with a / - expectedPath = expectedPath.Substring(0, expectedPath.Length - 1); - } - } - else - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // On windows all paths are normalized to end with a \ - expectedPath = expectedPath + Path.DirectorySeparatorChar; - } - } - - result += expectedPath + Path.PathSeparator; - } - - return result; - } - - [Fact] - public void AdditionalDepsDontAffectComponentDependencyResolution() - { - var fixture = sharedTestState.HostApiInvokerAppFixture.Copy(); - var componentFixture = sharedTestState.ComponentWithNoDependenciesFixture.Copy(); - - string additionalDepsPath = Path.Combine(Path.GetDirectoryName(fixture.TestProject.DepsJson), "__duplicate.deps.json"); - File.Copy(fixture.TestProject.DepsJson, additionalDepsPath); - - string[] args = - { - corehost_resolve_component_dependencies, - componentFixture.TestProject.AppDll - }; - fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, args) - .CaptureStdOut().CaptureStdErr().EnvironmentVariable("COREHOST_TRACE", "1").EnvironmentVariable("DOTNET_ADDITIONAL_DEPS", additionalDepsPath) - .Execute() - .StdErrAfter("corehost_resolve_component_dependencies = {") - .Should().Pass() - .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") - .And.HaveStdOutContaining($"corehost_resolve_component_dependencies assemblies:[{componentFixture.TestProject.AppDll}{Path.PathSeparator}]"); - } - - [Fact] - public void MultiThreadedComponentDependencyResolutionWhichSucceeeds() - { - var fixture = sharedTestState.HostApiInvokerAppFixture.Copy(); - var componentWithNoDependenciesFixture = sharedTestState.ComponentWithNoDependenciesFixture.Copy(); - var componentWithResourcesFixture = sharedTestState.ComponentWithResourcesFixture.Copy(); - - string componentWithNoDependenciesPrefix = Path.GetFileNameWithoutExtension(componentWithNoDependenciesFixture.TestProject.AppDll); - string componentWithResourcesPrefix = Path.GetFileNameWithoutExtension(componentWithResourcesFixture.TestProject.AppDll); - - string[] args = - { - corehost_resolve_component_dependencies_multithreaded, - componentWithNoDependenciesFixture.TestProject.AppDll, - componentWithResourcesFixture.TestProject.AppDll - }; - fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, args) - .CaptureStdOut().CaptureStdErr().EnvironmentVariable("COREHOST_TRACE", "1") - .Execute() - .Should().Pass() - .And.HaveStdOutContaining($"{componentWithNoDependenciesPrefix}: corehost_resolve_component_dependencies:Success") - .And.HaveStdOutContaining($"{componentWithNoDependenciesPrefix}: corehost_resolve_component_dependencies assemblies:[{componentWithNoDependenciesFixture.TestProject.AppDll}{Path.PathSeparator}]") - .And.HaveStdOutContaining($"{componentWithResourcesPrefix}: corehost_resolve_component_dependencies:Success") - .And.HaveStdOutContaining($"{componentWithResourcesPrefix}: corehost_resolve_component_dependencies resource_search_paths:[" + - $"{ExpectedProbingPaths(componentWithResourcesFixture.TestProject.OutputDirectory)}]"); - } - - [Fact] - public void MultiThreadedComponentDependencyResolutionWhichFailures() - { - var fixture = sharedTestState.HostApiInvokerAppFixture.Copy(); - var componentWithNoDependenciesFixture = sharedTestState.ComponentWithNoDependenciesFixture.Copy(); - var componentWithResourcesFixture = sharedTestState.ComponentWithResourcesFixture.Copy(); - - string componentWithNoDependenciesPrefix = Path.GetFileNameWithoutExtension(componentWithNoDependenciesFixture.TestProject.AppDll); - string componentWithResourcesPrefix = Path.GetFileNameWithoutExtension(componentWithResourcesFixture.TestProject.AppDll); - - // Corrupt the .deps.json by appending } to it (malformed json) - File.WriteAllText( - componentWithNoDependenciesFixture.TestProject.DepsJson, - File.ReadAllLines(componentWithNoDependenciesFixture.TestProject.DepsJson) + "}"); - - string[] args = - { - corehost_resolve_component_dependencies_multithreaded, - componentWithNoDependenciesFixture.TestProject.AppDll, - componentWithResourcesFixture.TestProject.AppDll + "_invalid" - }; - fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, args) - .CaptureStdOut().CaptureStdErr().EnvironmentVariable("COREHOST_TRACE", "1") - .Execute() - .Should().Pass() - .And.HaveStdOutContaining($"{componentWithNoDependenciesPrefix}: corehost_resolve_component_dependencies:Fail[0x8000808B]") - .And.HaveStdOutContaining($"{componentWithNoDependenciesPrefix}: corehost reported errors:") - .And.HaveStdOutContaining($"{componentWithNoDependenciesPrefix}: A JSON parsing exception occurred in [{componentWithNoDependenciesFixture.TestProject.DepsJson}]: * Line 1, Column 2 Syntax error: Malformed token") - .And.HaveStdOutContaining($"{componentWithNoDependenciesPrefix}: Error initializing the dependency resolver: An error occurred while parsing: {componentWithNoDependenciesFixture.TestProject.DepsJson}") - .And.HaveStdOutContaining($"{componentWithResourcesPrefix}: corehost_resolve_component_dependencies:Fail[0x80008092]") - .And.HaveStdOutContaining($"{componentWithResourcesPrefix}: corehost reported errors:") - .And.HaveStdOutContaining($"{componentWithResourcesPrefix}: Failed to locate managed application"); - } - - public class SharedTestState : IDisposable - { - public TestProjectFixture HostApiInvokerAppFixture { get; } - public TestProjectFixture ComponentWithNoDependenciesFixture { get; } - public TestProjectFixture ComponentWithDependenciesFixture { get; } - public TestProjectFixture ComponentWithResourcesFixture { get; } - public RepoDirectoriesProvider RepoDirectories { get; } - - public SharedTestState() - { - RepoDirectories = new RepoDirectoriesProvider(); - - HostApiInvokerAppFixture = new TestProjectFixture("HostApiInvokerApp", RepoDirectories) - .EnsureRestored(RepoDirectories.CorehostPackages) - .BuildProject(); - - ComponentWithNoDependenciesFixture = new TestProjectFixture("ComponentWithNoDependencies", RepoDirectories) - .EnsureRestored(RepoDirectories.CorehostPackages) - .PublishProject(); - - ComponentWithDependenciesFixture = new TestProjectFixture("ComponentWithDependencies", RepoDirectories) - .EnsureRestored(RepoDirectories.CorehostPackages) - .PublishProject(); - - ComponentWithResourcesFixture = new TestProjectFixture("ComponentWithResources", RepoDirectories) - .EnsureRestored(RepoDirectories.CorehostPackages) - .PublishProject(); - - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // On non-Windows, we can't just P/Invoke to already loaded hostpolicy, so copy it next to the app dll. - var fixture = HostApiInvokerAppFixture; - var hostpolicy = Path.Combine( - fixture.BuiltDotnet.GreatestVersionSharedFxPath, - RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("hostpolicy")); - - File.Copy( - hostpolicy, - Path.GetDirectoryName(fixture.TestProject.AppDll)); - } - } - - public void Dispose() - { - HostApiInvokerAppFixture.Dispose(); - ComponentWithNoDependenciesFixture.Dispose(); - ComponentWithDependenciesFixture.Dispose(); - ComponentWithResourcesFixture.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/src/installer/test/HostActivationTests/DependencyResolution/DependencyResolutionBase.cs b/src/installer/test/HostActivationTests/DependencyResolution/DependencyResolutionBase.cs index 1a4dfc7..946ac90 100644 --- a/src/installer/test/HostActivationTests/DependencyResolution/DependencyResolutionBase.cs +++ b/src/installer/test/HostActivationTests/DependencyResolution/DependencyResolutionBase.cs @@ -20,8 +20,8 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution return SharedFramework.CalculateUniqueTestDirectory(baseDir); } - public SharedTestStateBase(string name) - : base(GetBaseDir(name), name) + public SharedTestStateBase() + : base(GetBaseDir("dependencyResolution"), "dependencyResolution") { _builtDotnet = Path.Combine(TestArtifactsPath, "sharedFrameworkPublish"); } diff --git a/src/installer/test/HostActivationTests/DependencyResolution/PortableAppRidAssetResolution.cs b/src/installer/test/HostActivationTests/DependencyResolution/PortableAppRidAssetResolution.cs index beea862..0501f53 100644 --- a/src/installer/test/HostActivationTests/DependencyResolution/PortableAppRidAssetResolution.cs +++ b/src/installer/test/HostActivationTests/DependencyResolution/PortableAppRidAssetResolution.cs @@ -181,7 +181,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution public DotNetCli DotNetWithNetCoreApp { get; } - public SharedTestState() : base("DependencyResolution") + public SharedTestState() : base() { DotNetWithNetCoreApp = DotNet("WithNetCoreApp") .AddMicrosoftNETCoreAppFrameworkMockCoreClr("4.0.0") diff --git a/src/installer/test/HostActivationTests/DependencyResolution/ResolveComponentDependencies.cs b/src/installer/test/HostActivationTests/DependencyResolution/ResolveComponentDependencies.cs new file mode 100644 index 0000000..d50a387 --- /dev/null +++ b/src/installer/test/HostActivationTests/DependencyResolution/ResolveComponentDependencies.cs @@ -0,0 +1,469 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.DotNet.Cli.Build.Framework; +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution +{ + public class ResolveComponentDependencies : + DependencyResolutionBase, + IClassFixture + { + private readonly SharedTestState sharedTestState; + private readonly ITestOutputHelper output; + + public ResolveComponentDependencies(SharedTestState fixture, ITestOutputHelper output) + { + sharedTestState = fixture; + this.output = output; + } + + private const string corehost_resolve_component_dependencies = "corehost_resolve_component_dependencies"; + private const string corehost_resolve_component_dependencies_multithreaded = "corehost_resolve_component_dependencies_multithreaded"; + + [Fact] + public void InvalidMainComponentAssemblyPathFails() + { + RunTest(sharedTestState.HostApiInvokerAppFixture.TestProject.AppDll + "_invalid") + .Should().Pass() + .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Fail[0x80008092]") + .And.HaveStdOutContaining("corehost reported errors:") + .And.HaveStdOutContaining("Failed to locate managed application"); + } + + [Fact] + public void ComponentWithNoDependenciesAndNoDeps() + { + var component = sharedTestState.ComponentWithNoDependencies.Copy(); + + // Remove .deps.json + File.Delete(component.DepsJson); + + RunTest(component) + .Should().Pass() + .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") + .And.HaveStdOutContaining($"corehost_resolve_component_dependencies assemblies:[{component.AppDll}{Path.PathSeparator}]") + .And.HaveStdErrContaining($"app_root='{component.Location}{Path.DirectorySeparatorChar}'") + .And.HaveStdErrContaining($"deps='{component.DepsJson}'") + .And.HaveStdErrContaining($"mgd_app='{component.AppDll}'") + .And.HaveStdErrContaining($"-- arguments_t: dotnet shared store: '{Path.Combine(sharedTestState.HostApiInvokerAppFixture.BuiltDotnet.BinPath, "store", sharedTestState.RepoDirectories.BuildArchitecture, sharedTestState.HostApiInvokerAppFixture.Framework)}'"); + } + + [Fact] + public void ComponentWithNoDependencies() + { + RunTest(sharedTestState.ComponentWithNoDependencies) + .Should().Pass() + .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") + .And.HaveStdOutContaining($"corehost_resolve_component_dependencies assemblies:[{sharedTestState.ComponentWithNoDependencies.AppDll}{Path.PathSeparator}]"); + } + + private static readonly string[] SupportedOsList = new string[] + { + "ubuntu", + "debian", + "fedora", + "opensuse", + "osx", + "rhel", + "win" + }; + + private string GetExpectedLibuvRid(TestProjectFixture fixture) + { + // Simplified version of the RID fallback for libuv + // Note that we have to take the architecture from the fixture (since this test may run on x64 but the fixture on x86) + // but we can't use the OS part from the fixture RID as that may be too generic (like linux-x64). + string currentRid = PlatformAbstractions.RuntimeEnvironment.GetRuntimeIdentifier(); + string fixtureRid = fixture.CurrentRid; + string osName = currentRid.Split('-')[0]; + string architecture = fixtureRid.Split('-')[1]; + + string supportedOsName = SupportedOsList.FirstOrDefault(a => osName.StartsWith(a)); + if (supportedOsName == null) + { + return null; + } + + osName = supportedOsName; + if (osName == "ubuntu") { osName = "debian"; } + if (osName == "win") { osName = "win7"; } + if (osName == "osx") { return osName; } + + return osName + "-" + architecture; + } + + [Fact] + public void ComponentWithDependencies() + { + string libuvRid = GetExpectedLibuvRid(sharedTestState.HostApiInvokerAppFixture); + if (libuvRid == null) + { + output.WriteLine($"RID {PlatformAbstractions.RuntimeEnvironment.GetRuntimeIdentifier()} is not supported by libuv and thus we can't run this test on it."); + return; + } + + RunTest(sharedTestState.ComponentWithDependencies) + .Should().Pass() + .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") + .And.HaveStdOutContaining( + $"corehost_resolve_component_dependencies assemblies:[" + + $"{Path.Combine(sharedTestState.ComponentWithDependencies.Location, "ComponentDependency.dll")}{Path.PathSeparator}" + + $"{sharedTestState.ComponentWithDependencies.AppDll}{Path.PathSeparator}" + + $"{Path.Combine(sharedTestState.ComponentWithDependencies.Location, "Newtonsoft.Json.dll")}{Path.PathSeparator}]") + .And.HaveStdOutContaining( + $"corehost_resolve_component_dependencies native_search_paths:[" + + $"{ExpectedProbingPaths(Path.Combine(sharedTestState.ComponentWithDependencies.Location, "runtimes", libuvRid, "native"))}]"); + } + + [Fact] + public void ComponentWithDependenciesAndDependencyRemoved() + { + var component = sharedTestState.ComponentWithDependencies.Copy(); + + // Remove a dependency + // This will cause the resolution to fail + File.Delete(Path.Combine(component.Location, "ComponentDependency.dll")); + + RunTest(component) + .Should().Pass() + .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") + .And.HaveStdOutContaining( + $"corehost_resolve_component_dependencies assemblies:[" + + $"{component.AppDll}{Path.PathSeparator}" + + $"{Path.Combine(component.Location, "Newtonsoft.Json.dll")}{Path.PathSeparator}]"); + } + + [Fact] + public void ComponentWithDependenciesAndNoDeps() + { + var component = sharedTestState.ComponentWithDependencies.Copy(); + + // Remove .deps.json + File.Delete(component.DepsJson); + + RunTest(component) + .Should().Pass() + .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") + .And.HaveStdOutContaining( + $"corehost_resolve_component_dependencies assemblies:[" + + $"{Path.Combine(component.Location, "ComponentDependency.dll")}{Path.PathSeparator}" + + $"{component.AppDll}{Path.PathSeparator}" + + $"{Path.Combine(component.Location, "Newtonsoft.Json.dll")}{Path.PathSeparator}]"); + } + + [Fact] + public void ComponentWithDependenciesAndNoDepsAndDependencyRemoved() + { + var component = sharedTestState.ComponentWithDependencies.Copy(); + + // Remove .deps.json + File.Delete(component.DepsJson); + + // Remove a dependency + // Since there's no .deps.json - there's no way for the system to know about this dependency and thus should not be reported. + File.Delete(Path.Combine(component.Location, "ComponentDependency.dll")); + + RunTest(component) + .Should().Pass() + .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") + .And.HaveStdOutContaining( + $"corehost_resolve_component_dependencies assemblies:[" + + $"{component.AppDll}{Path.PathSeparator}" + + $"{Path.Combine(component.Location, "Newtonsoft.Json.dll")}{Path.PathSeparator}]"); + } + + [Fact] + public void ComponentWithSameDependencyWithDifferentExtensionShouldFail() + { + // Add a reference to another package which has asset with the same name as the existing ComponentDependency + // but with a different extension. This causes a failure. + // Make sure the file exists so that we avoid failing due to missing file. + var component = sharedTestState.CreateComponentWithDependencies(b => b + .WithPackage("ComponentDependency_Dupe", "1.0.0", p => p + .WithAssemblyGroup(null, g => g + .WithAsset("ComponentDependency.notdll")))); + + RunTest(component) + .Should().Pass() + .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Fail[0x8000808C]") + .And.HaveStdOutContaining("corehost reported errors:") + .And.HaveStdOutContaining("An assembly specified in the application dependencies manifest (ComponentWithDependencies.deps.json) has already been found but with a different file extension") + .And.HaveStdOutContaining("package: 'ComponentDependency_Dupe', version: '1.0.0'") + .And.HaveStdOutContaining("path: 'ComponentDependency.notdll'") + .And.HaveStdOutContaining($"previously found assembly: '{Path.Combine(component.Location, "ComponentDependency.dll")}'"); + } + + // This test also validates that corehost_set_error_writer custom writer + // correctly captures errors from hostpolicy. + [Fact] + public void ComponentWithCorruptedDepsJsonShouldFail() + { + var component = sharedTestState.ComponentWithDependencies.Copy(); + + // Corrupt the .deps.json by appending } to it (malformed json) + File.WriteAllText( + component.DepsJson, + File.ReadAllLines(component.DepsJson) + "}"); + + RunTest(component) + .Should().Pass() + .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Fail[0x8000808B]") + .And.HaveStdOutContaining("corehost reported errors:") + .And.HaveStdOutContaining($"A JSON parsing exception occurred in [{component.DepsJson}]: * Line 1, Column 2 Syntax error: Malformed token") + .And.HaveStdOutContaining($"Error initializing the dependency resolver: An error occurred while parsing: {component.DepsJson}"); + } + + [Fact] + public void ComponentWithResourcesShouldReportResourceSearchPaths() + { + RunTest(sharedTestState.ComponentWithResources) + .Should().Pass() + .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") + .And.HaveStdOutContaining($"corehost_resolve_component_dependencies resource_search_paths:[" + + $"{ExpectedProbingPaths(sharedTestState.ComponentWithResources.Location)}]"); + } + + private string ExpectedProbingPaths(params string[] paths) + { + string result = string.Empty; + foreach (string path in paths) + { + string expectedPath = path; + if (expectedPath.EndsWith(Path.DirectorySeparatorChar)) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // On non-windows the paths are normalized to not end with a / + expectedPath = expectedPath.Substring(0, expectedPath.Length - 1); + } + } + else + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // On windows all paths are normalized to end with a \ + expectedPath += Path.DirectorySeparatorChar; + } + } + + result += expectedPath + Path.PathSeparator; + } + + return result; + } + + [Fact] + public void AdditionalDepsDontAffectComponentDependencyResolution() + { + var component = sharedTestState.ComponentWithNoDependencies.Copy(); + + string additionalDepsPath = Path.Combine(Path.GetDirectoryName(component.DepsJson), "__duplicate.deps.json"); + File.Copy(sharedTestState.HostApiInvokerAppFixture.TestProject.DepsJson, additionalDepsPath); + + RunTest(component, command => command + .EnvironmentVariable("DOTNET_ADDITIONAL_DEPS", additionalDepsPath)) + .Should().Pass() + .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") + .And.HaveStdOutContaining($"corehost_resolve_component_dependencies assemblies:[{component.AppDll}{Path.PathSeparator}]"); + } + + [Fact] + public void MultiThreadedComponentDependencyResolutionWhichSucceeeds() + { + string componentWithNoDependenciesPrefix = Path.GetFileNameWithoutExtension(sharedTestState.ComponentWithNoDependencies.AppDll); + string componentWithResourcesPrefix = Path.GetFileNameWithoutExtension(sharedTestState.ComponentWithResources.AppDll); + + RunMultiThreadedTest(sharedTestState.ComponentWithNoDependencies, sharedTestState.ComponentWithResources) + .Should().Pass() + .And.HaveStdOutContaining($"{componentWithNoDependenciesPrefix}: corehost_resolve_component_dependencies:Success") + .And.HaveStdOutContaining($"{componentWithNoDependenciesPrefix}: corehost_resolve_component_dependencies assemblies:[{sharedTestState.ComponentWithNoDependencies.AppDll}{Path.PathSeparator}]") + .And.HaveStdOutContaining($"{componentWithResourcesPrefix}: corehost_resolve_component_dependencies:Success") + .And.HaveStdOutContaining($"{componentWithResourcesPrefix}: corehost_resolve_component_dependencies resource_search_paths:[" + + $"{ExpectedProbingPaths(sharedTestState.ComponentWithResources.Location)}]"); + } + + [Fact] + public void MultiThreadedComponentDependencyResolutionWhithFailures() + { + var componentWithNoDependencies = sharedTestState.ComponentWithNoDependencies.Copy(); + + string componentWithNoDependenciesPrefix = Path.GetFileNameWithoutExtension(componentWithNoDependencies.AppDll); + string componentWithResourcesPrefix = Path.GetFileNameWithoutExtension(sharedTestState.ComponentWithResources.AppDll); + + // Corrupt the .deps.json by appending } to it (malformed json) + File.WriteAllText( + componentWithNoDependencies.DepsJson, + File.ReadAllLines(componentWithNoDependencies.DepsJson) + "}"); + + RunMultiThreadedTest( + componentWithNoDependencies.AppDll, + sharedTestState.ComponentWithResources.AppDll + "_invalid") + .Should().Pass() + .And.HaveStdOutContaining($"{componentWithNoDependenciesPrefix}: corehost_resolve_component_dependencies:Fail[0x8000808B]") + .And.HaveStdOutContaining($"{componentWithNoDependenciesPrefix}: corehost reported errors:") + .And.HaveStdOutContaining($"{componentWithNoDependenciesPrefix}: A JSON parsing exception occurred in [{componentWithNoDependencies.DepsJson}]: * Line 1, Column 2 Syntax error: Malformed token") + .And.HaveStdOutContaining($"{componentWithNoDependenciesPrefix}: Error initializing the dependency resolver: An error occurred while parsing: {componentWithNoDependencies.DepsJson}") + .And.HaveStdOutContaining($"{componentWithResourcesPrefix}: corehost_resolve_component_dependencies:Fail[0x80008092]") + .And.HaveStdOutContaining($"{componentWithResourcesPrefix}: corehost reported errors:") + .And.HaveStdOutContaining($"{componentWithResourcesPrefix}: Failed to locate managed application"); + } + + private CommandResult RunTest(TestApp component, Action commandCustomizer = null) + { + return RunTest(component.AppDll, commandCustomizer); + } + + private CommandResult RunTest(string componentPath, Action commandCustomizer = null) + { + string[] args = + { + corehost_resolve_component_dependencies, + componentPath + }; + + Command command = sharedTestState.HostApiInvokerAppFixture.BuiltDotnet.Exec(sharedTestState.HostApiInvokerAppFixture.TestProject.AppDll, args) + .EnableTracingAndCaptureOutputs(); + commandCustomizer?.Invoke(command); + + return command.Execute() + .StdErrAfter("corehost_resolve_component_dependencies = {"); + } + + private CommandResult RunMultiThreadedTest(TestApp componentOne, TestApp componentTwo) + { + return RunMultiThreadedTest(componentOne.AppDll, componentTwo.AppDll); + } + + private CommandResult RunMultiThreadedTest(string componentOnePath, string componentTwoPath) + { + string[] args = + { + corehost_resolve_component_dependencies_multithreaded, + componentOnePath, + componentTwoPath + }; + return sharedTestState.HostApiInvokerAppFixture.BuiltDotnet.Exec(sharedTestState.HostApiInvokerAppFixture.TestProject.AppDll, args) + .EnableTracingAndCaptureOutputs() + .Execute(); + } + + public class SharedTestState : SharedTestStateBase + { + public TestProjectFixture HostApiInvokerAppFixture { get; } + public TestApp ComponentWithNoDependencies { get; } + public TestApp ComponentWithDependencies { get; } + public TestApp ComponentWithResources { get; } + public RepoDirectoriesProvider RepoDirectories { get; } + + public SharedTestState() + { + RepoDirectories = new RepoDirectoriesProvider(); + + HostApiInvokerAppFixture = new TestProjectFixture("HostApiInvokerApp", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .BuildProject(); + + ComponentWithNoDependencies = CreateComponentWithNoDependencies(null, Location); + + ComponentWithDependencies = CreateComponentWithDependencies(null, Location); + + ComponentWithResources = CreateComponentWithResources(null, Location); + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // On non-Windows, we can't just P/Invoke to already loaded hostpolicy, so copy it next to the app dll. + var fixture = HostApiInvokerAppFixture; + var hostpolicy = Path.Combine( + fixture.BuiltDotnet.GreatestVersionSharedFxPath, + RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("hostpolicy")); + + File.Copy( + hostpolicy, + Path.GetDirectoryName(fixture.TestProject.AppDll)); + } + } + + private TestApp CreateTestApp(string location, string name) + { + TestApp testApp; + if (location == null) + { + testApp = TestApp.CreateEmpty(name); + } + else + { + string path = Path.Combine(location, name); + FileUtils.EnsureDirectoryExists(path); + testApp = new TestApp(path); + } + + RegisterCopy(testApp); + return testApp; + } + + public TestApp CreateComponentWithNoDependencies(Action customizer = null, string location = null) + { + TestApp componentWithNoDependencies = CreateTestApp(location, "ComponentWithNoDependencies"); + FileUtils.EnsureDirectoryExists(componentWithNoDependencies.Location); + NetCoreAppBuilder builder = NetCoreAppBuilder.PortableForNETCoreApp(componentWithNoDependencies) + .WithProject(p => p.WithAssemblyGroup(null, g => g.WithMainAssembly())); + customizer?.Invoke(builder); + + return builder.Build(componentWithNoDependencies); + } + + public TestApp CreateComponentWithDependencies(Action customizer = null, string location = null) + { + TestApp componentWithDependencies = CreateTestApp(location, "ComponentWithDependencies"); + FileUtils.EnsureDirectoryExists(componentWithDependencies.Location); + NetCoreAppBuilder builder = NetCoreAppBuilder.PortableForNETCoreApp(componentWithDependencies) + .WithProject(p => p.WithAssemblyGroup(null, g => g.WithMainAssembly())) + .WithProject("ComponentDependency", "1.0.0", p => p.WithAssemblyGroup(null, g => g.WithAsset("ComponentDependency.dll"))) + .WithPackage("Newtonsoft.Json", "9.0.1", p => p.WithAssemblyGroup(null, g => g + .WithAsset("lib/netstandard1.0/Newtonsoft.Json.dll", f => f + .WithVersion("9.0.0.0", "9.0.1.19813") + .WithFileOnDiskPath("Newtonsoft.Json.dll")))) + .WithPackage("Libuv", "1.9.1", p => p + .WithNativeLibraryGroup("debian-x64", g => g.WithAsset("runtimes/debian-x64/native/libuv.so")) + .WithNativeLibraryGroup("fedora-x64", g => g.WithAsset("runtimes/fedora-x64/native/libuv.so")) + .WithNativeLibraryGroup("opensuse-x64", g => g.WithAsset("runtimes/opensuse-x64/native/libuv.so")) + .WithNativeLibraryGroup("osx", g => g.WithAsset("runtimes/osx/native/libuv.dylib")) + .WithNativeLibraryGroup("rhel-x64", g => g.WithAsset("runtimes/rhel-x64/native/libuv.so")) + .WithNativeLibraryGroup("win7-arm", g => g.WithAsset("runtimes/win7-arm/native/libuv.dll")) + .WithNativeLibraryGroup("win7-x64", g => g.WithAsset("runtimes/win7-x64/native/libuv.dll")) + .WithNativeLibraryGroup("win7-x86", g => g.WithAsset("runtimes/win7-x86/native/libuv.dll"))); + customizer?.Invoke(builder); + + return builder.Build(componentWithDependencies); + } + + public TestApp CreateComponentWithResources(Action customizer = null, string location = null) + { + TestApp componentWithResources = CreateTestApp(location, "ComponentWithResources"); + NetCoreAppBuilder builder = NetCoreAppBuilder.PortableForNETCoreApp(componentWithResources) + .WithProject(p => p + .WithAssemblyGroup(null, g => g.WithMainAssembly()) + .WithResourceAssembly("en/ComponentWithResources.resources.dll")); + + customizer?.Invoke(builder); + + return builder.Build(componentWithResources); + } + + public override void Dispose() + { + base.Dispose(); + + HostApiInvokerAppFixture.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/installer/test/HostActivationTests/NetCoreAppBuilder.cs b/src/installer/test/HostActivationTests/NetCoreAppBuilder.cs index 0339b7a..1234715 100644 --- a/src/installer/test/HostActivationTests/NetCoreAppBuilder.cs +++ b/src/installer/test/HostActivationTests/NetCoreAppBuilder.cs @@ -29,33 +29,19 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation public TestApp App { get; set; } } - public class RuntimeFileBuilder + public abstract class FileBuilder { public string Path { get; set; } - public string AssemblyVersion { get; set; } - public string FileVersion { get; set; } public string SourcePath { get; set; } public string FileOnDiskPath { get; set; } - public RuntimeFileBuilder(string path) + public FileBuilder(string path) { Path = path; } - public RuntimeFileBuilder CopyFromFile(string sourcePath) - { - SourcePath = sourcePath; - return this; - } - - public RuntimeFileBuilder WithFileOnDiskPath(string relativePath) - { - FileOnDiskPath = relativePath; - return this; - } - - internal RuntimeFile Build(BuildContext context) + internal void Build(BuildContext context) { string path = ToDiskPath(FileOnDiskPath ?? Path); string absolutePath = System.IO.Path.Combine(context.App.Location, path); @@ -64,17 +50,93 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation FileUtils.EnsureFileDirectoryExists(absolutePath); File.Copy(SourcePath, absolutePath); } - else + else if (FileOnDiskPath == null || FileOnDiskPath.Length >= 0) { FileUtils.CreateEmptyFile(absolutePath); } + } + protected static string ToDiskPath(string assetPath) + { + return assetPath.Replace('/', System.IO.Path.DirectorySeparatorChar); + } + } + + public abstract class FileBuilder : FileBuilder + where T : FileBuilder + { + public FileBuilder(string path) + : base(path) + { + } + + public T CopyFromFile(string sourcePath) + { + SourcePath = sourcePath; + return this as T; + } + + public T WithFileOnDiskPath(string relativePath) + { + FileOnDiskPath = relativePath; + return this as T; + } + + public T NotOnDisk() + { + FileOnDiskPath = string.Empty; + return this as T; + } + } + + public class RuntimeFileBuilder : FileBuilder + { + public string AssemblyVersion { get; set; } + public string FileVersion { get; set; } + + public RuntimeFileBuilder(string path) + : base(path) + { + } + + public RuntimeFileBuilder WithVersion(string assemblyVersion, string fileVersion) + { + AssemblyVersion = assemblyVersion; + FileVersion = fileVersion; + return this; + } + + internal new RuntimeFile Build(BuildContext context) + { + base.Build(context); return new RuntimeFile(Path, AssemblyVersion, FileVersion); } + } + + public class ResourceAssemblyBuilder : FileBuilder + { + public string Locale { get; set; } + + public ResourceAssemblyBuilder(string path) + : base(path) + { + int i = path.IndexOf('/'); + if (i > 0) + { + Locale = path.Substring(0, i); + } + } - private static string ToDiskPath(string assetPath) + public ResourceAssemblyBuilder WithLocale(string locale) { - return assetPath.Replace('/', System.IO.Path.DirectorySeparatorChar); + Locale = locale; + return this; + } + + internal new ResourceAssembly Build(BuildContext context) + { + base.Build(context); + return new ResourceAssembly(Path, Locale); } } @@ -103,9 +165,11 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation return this; } - public RuntimeAssetGroupBuilder WithAsset(string path) + public RuntimeAssetGroupBuilder WithAsset(string path, Action customizer = null) { - return WithAsset(new RuntimeFileBuilder(path)); + RuntimeFileBuilder runtimeFile = new RuntimeFileBuilder(path); + customizer?.Invoke(runtimeFile); + return WithAsset(runtimeFile); } internal RuntimeAssetGroup Build(BuildContext context) @@ -136,6 +200,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation public List AssemblyGroups { get; } = new List(); public List NativeLibraryGroups { get; } = new List(); + public List ResourceAssemblies { get; } = new List(); public RuntimeLibraryBuilder(RuntimeLibraryType type, string name, string version) { @@ -166,6 +231,14 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation return this; } + public RuntimeLibraryBuilder WithResourceAssembly(string path, Action customizer = null) + { + ResourceAssemblyBuilder resourceAssembly = new ResourceAssemblyBuilder(path); + customizer?.Invoke(resourceAssembly); + ResourceAssemblies.Add(resourceAssembly); + return this; + } + internal RuntimeLibrary Build(BuildContext context) { return new RuntimeLibrary( @@ -175,7 +248,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation string.Empty, AssemblyGroups.Select(g => g.Build(context)).ToList(), NativeLibraryGroups.Select(g => g.Build(context)).ToList(), - Enumerable.Empty(), + ResourceAssemblies.Select(ra => ra.Build(context)).ToList(), Enumerable.Empty(), false); } diff --git a/src/installer/test/TestUtils/FileUtils.cs b/src/installer/test/TestUtils/FileUtils.cs index aecfbae..ee8c9b3 100644 --- a/src/installer/test/TestUtils/FileUtils.cs +++ b/src/installer/test/TestUtils/FileUtils.cs @@ -130,10 +130,14 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation public static void EnsureFileDirectoryExists(string filePath) { - string directory = Path.GetDirectoryName(filePath); - if (!Directory.Exists(directory)) + EnsureDirectoryExists(Path.GetDirectoryName(filePath)); + } + + public static void EnsureDirectoryExists(string path) + { + if (!Directory.Exists(path)) { - Directory.CreateDirectory(directory); + Directory.CreateDirectory(path); } } diff --git a/src/installer/test/TestUtils/TestApp.cs b/src/installer/test/TestUtils/TestApp.cs index 8c71a63..3e4e72f 100644 --- a/src/installer/test/TestUtils/TestApp.cs +++ b/src/installer/test/TestUtils/TestApp.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.DotNet.CoreSetup.Test.HostActivation; using System.IO; namespace Microsoft.DotNet.CoreSetup.Test @@ -32,6 +33,14 @@ namespace Microsoft.DotNet.CoreSetup.Test LoadAssets(); } + public static TestApp CreateEmpty(string name) + { + string location = GetNewTestArtifactPath(name); + FileUtils.EnsureDirectoryExists(location); + + return new TestApp(location); + } + public TestApp Copy() { return new TestApp(this); diff --git a/src/installer/test/TestUtils/TestArtifact.cs b/src/installer/test/TestUtils/TestArtifact.cs index c70f5b2..e49b520 100644 --- a/src/installer/test/TestUtils/TestArtifact.cs +++ b/src/installer/test/TestUtils/TestArtifact.cs @@ -11,7 +11,7 @@ namespace Microsoft.DotNet.CoreSetup.Test public class TestArtifact : IDisposable { private static readonly string TestArtifactDirectoryEnvironmentVariable = "TEST_ARTIFACTS"; - private static Lazy _testArtifactsPath = new Lazy(() => + private static readonly Lazy _testArtifactsPath = new Lazy(() => { return Environment.GetEnvironmentVariable(TestArtifactDirectoryEnvironmentVariable) ?? Path.Combine(AppContext.BaseDirectory, TestArtifactDirectoryEnvironmentVariable); @@ -28,7 +28,7 @@ namespace Microsoft.DotNet.CoreSetup.Test public string Location { get; } public string Name { get; } - private List _copies = new List(); + private readonly List _copies = new List(); public TestArtifact(string location, string name = null) { @@ -46,7 +46,12 @@ namespace Microsoft.DotNet.CoreSetup.Test source._copies.Add(this); } - public void Dispose() + protected void RegisterCopy(TestArtifact artifact) + { + _copies.Add(artifact); + } + + public virtual void Dispose() { if (!PreserveTestRuns() && Directory.Exists(Location)) { -- 2.7.4