Add tests that use the host's computed RID (#82574)
authorElinor Fung <elfung@microsoft.com>
Fri, 24 Feb 2023 18:14:43 +0000 (10:14 -0800)
committerGitHub <noreply@github.com>
Fri, 24 Feb 2023 18:14:43 +0000 (10:14 -0800)
Add tests that rely on the host's computed RID (sets `DOTNET_RUNTIME_ID` to empty, which we treat as the same as not set) and use the fallback graph from the built `Microsoft.NETCore.App.deps.json`.

src/installer/tests/HostActivation.Tests/DependencyResolution/DependencyResolutionCommandResultExtensions.cs
src/installer/tests/HostActivation.Tests/DependencyResolution/RidAssetResolution.cs

index 14b44a1..0f66913 100644 (file)
@@ -147,6 +147,12 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
             return assertion.HaveStdErrContaining($"Using specified additional deps.json: '{depsFilePath}'");
         }
 
+        public static AndConstraint<CommandResultAssertions> HaveUsedFallbackRid(this CommandResultAssertions assertion, bool usedFallbackRid)
+        {
+            string msg = "Falling back to base HostRID";
+            return usedFallbackRid ? assertion.HaveStdErrContaining(msg) : assertion.NotHaveStdErrContaining(msg);
+        }
+
         private static string GetAppMockPropertyValue(CommandResultAssertions assertion, string propertyName) =>
             GetMockPropertyValue(assertion, $"mock property[{propertyName}] = ");
 
index 9c6cb58..b90d252 100644 (file)
@@ -2,6 +2,11 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.DotNet.Cli.Build;
+using Microsoft.Extensions.DependencyModel;
 using Xunit;
 
 namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
@@ -24,6 +29,8 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
             string excludedNativeLibraryPaths,
             Action<NetCoreAppBuilder> appCustomizer = null);
 
+        protected const string UnknownRid = "unknown-rid";
+
         private const string LinuxAssembly = "linux/LinuxAssembly.dll";
         private const string MacOSAssembly = "osx/MacOSAssembly.dll";
         private const string WindowsAssembly = "win/WindowsAssembly.dll";
@@ -43,13 +50,15 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
                 rid, includedPath, excludedPath, null, null);
         }
 
-        [Fact]
-        public void RidSpecificAssembly_UnknownRid()
+        [Theory]
+        [InlineData(null)]          // RID is computed at run-time
+        [InlineData(UnknownRid)]    // RID is from a compile-time fallback
+        public void RidSpecificAssembly_CurrentRid(string rid)
         {
             string includedPath = null;
             string excludedPath = null;
 
-            // When the RID is unknown, the host uses a compile-time fallback based on the target OS
+            // Host should resolve to the RID corresponding to the platform on which it is running
             if (OperatingSystem.IsLinux())
             {
                 includedPath = LinuxAssembly;
@@ -71,7 +80,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
                 excludedPath = $"{LinuxAssembly};{MacOSAssembly};{WindowsAssembly}";
             }
 
-            RidSpecificAssembly("unknown-rid", includedPath, excludedPath);
+            RidSpecificAssembly(rid, includedPath, excludedPath);
         }
 
         [Theory]
@@ -89,12 +98,15 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
                 rid, null, null, includedPath, excludedPath);
         }
 
-        [Fact]
-        public void RidSpecificNativeLibrary_UnknownRid()
+        [Theory]
+        [InlineData(null)]          // RID is computed at run-time
+        [InlineData(UnknownRid)]    // RID is from a compile-time fallback
+        public void RidSpecificNativeLibrary_CurrentRid(string rid)
         {
-            // When the RID is unknown, the host uses a compile-time fallback based on the target OS
             string includedPath;
             string excludedPath;
+
+            // Host should resolve to the RID corresponding to the platform on which it is running
             if (OperatingSystem.IsLinux())
             {
                 includedPath = "linux";
@@ -164,7 +176,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
         }
 
         [Theory]
-        // For "win" RIDs the DependencyLib which is RID-agnostic will not be included, 
+        // For "win" RIDs the DependencyLib which is RID-agnostic will not be included,
         // since there are other assembly (runtime) assets with more specific RID match.
         [InlineData("win10-x64", "win/ManagedWin.dll;win/AnotherWin.dll", "native/win10-x64;native/win10-x64-2")]
         [InlineData("win10-x86", "win/ManagedWin.dll;win/AnotherWin.dll", "native/win-x86")]
@@ -196,7 +208,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
                     .WithPackage("ridAgnosticLib", "2.0.0", p => p
                         .WithAssemblyGroup(null, g => g.WithAsset("PortableLib.dll").WithAsset("PortableLib2.dll"))),
                 rid: rid,
-                // The PortableLib an PortableLib2 are from a separate package which has no RID specific assets, 
+                // The PortableLib an PortableLib2 are from a separate package which has no RID specific assets,
                 // so the RID-agnostic assets are always included
                 includedAssemblyPaths: expectedAssemblyPath + ";PortableLib.dll;PortableLib2.dll", excludedAssemblyPaths: null,
                 includedNativeLibraryPaths: expectedNativePath, excludedNativeLibraryPaths: null);
@@ -204,8 +216,30 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
 
         public class SharedTestState : ComponentSharedTestStateBase
         {
+            public DotNetCli DotNetWithNetCoreApp_RuntimeFallbacks { get; }
+
             public SharedTestState() : base()
             {
+                DotNetWithNetCoreApp_RuntimeFallbacks = DotNet("WithNetCoreApp_RuntimeFallbacks")
+                    .AddMicrosoftNETCoreAppFrameworkMockCoreClr("4.0.0", UseFallbacksFromBuiltDotNet)
+                    .Build();
+            }
+
+            protected void UseFallbacksFromBuiltDotNet(NetCoreAppBuilder builder)
+            {
+                IReadOnlyList<RuntimeFallbacks> fallbacks;
+                string depsJson = Path.Combine(new DotNetCli(BuiltDotnetPath).GreatestVersionSharedFxPath, $"{Constants.MicrosoftNETCoreApp}.deps.json");
+                using (FileStream fileStream = File.Open(depsJson, FileMode.Open))
+                using (DependencyContextJsonReader reader = new DependencyContextJsonReader())
+                {
+                    fallbacks = reader.Read(fileStream).RuntimeGraph;
+                }
+
+                builder.RuntimeFallbacks.Clear();
+                foreach (RuntimeFallbacks fallback in fallbacks)
+                {
+                    builder.WithRuntimeFallbacks(fallback.Runtime, fallback.Fallbacks.ToArray());
+                }
             }
         }
     }
@@ -234,7 +268,9 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
                 .WithCustomizer(appCustomizer)
                 .Build())
             {
-                SharedState.DotNetWithNetCoreApp.Exec(app.AppDll)
+                // Use the fallbacks from the product when testing the computed RID
+                DotNetCli dotnet = rid == null ? SharedState.DotNetWithNetCoreApp_RuntimeFallbacks : SharedState.DotNetWithNetCoreApp;
+                dotnet.Exec(app.AppDll)
                     .EnableTracingAndCaptureOutputs()
                     .RuntimeId(rid)
                     .Execute()
@@ -242,7 +278,8 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
                     .And.HaveResolvedAssembly(includedAssemblyPaths, app)
                     .And.NotHaveResolvedAssembly(excludedAssemblyPaths, app)
                     .And.HaveResolvedNativeLibraryPath(includedNativeLibraryPaths, app)
-                    .And.NotHaveResolvedNativeLibraryPath(excludedNativeLibraryPaths, app);
+                    .And.NotHaveResolvedNativeLibraryPath(excludedNativeLibraryPaths, app)
+                    .And.HaveUsedFallbackRid(rid == UnknownRid);
             }
         }
     }
@@ -270,14 +307,17 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
                 .WithPackage("NativeDependency", "1.0.0", p => assetsCustomizer?.Invoke(p))
                 .WithCustomizer(appCustomizer));
 
-            SharedState.RunComponentResolutionTest(component, command => command
+            // Use the fallbacks from the product when testing the computed RID
+            DotNetCli dotnet = rid == null ? SharedState.DotNetWithNetCoreApp_RuntimeFallbacks : SharedState.DotNetWithNetCoreApp;
+            SharedState.RunComponentResolutionTest(component.AppDll, SharedState.FrameworkReferenceApp, dotnet.GreatestVersionHostFxrPath, command => command
                 .RuntimeId(rid))
                 .Should().Pass()
                 .And.HaveSuccessfullyResolvedComponentDependencies()
                 .And.HaveResolvedComponentDependencyAssembly(includedAssemblyPaths, component)
                 .And.NotHaveResolvedComponentDependencyAssembly(excludedAssemblyPaths, component)
                 .And.HaveResolvedComponentDependencyNativeLibraryPath(includedNativeLibraryPaths, component)
-                .And.NotHaveResolvedComponentDependencyNativeLibraryPath(excludedNativeLibraryPaths, component);
+                .And.NotHaveResolvedComponentDependencyNativeLibraryPath(excludedNativeLibraryPaths, component)
+                .And.HaveUsedFallbackRid(rid == UnknownRid);
         }
     }
 
@@ -327,7 +367,8 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
                 .Should().Pass()
                 .And.HaveSuccessfullyResolvedComponentDependencies()
                 .And.NotHaveResolvedComponentDependencyAssembly(assemblyPaths, component)
-                .And.NotHaveResolvedComponentDependencyNativeLibraryPath(nativeLibrarypaths, component);
+                .And.NotHaveResolvedComponentDependencyNativeLibraryPath(nativeLibrarypaths, component)
+                .And.HaveUsedFallbackRid(true);
         }
 
         public class ComponentSharedTestState : SharedTestState
@@ -342,7 +383,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
     }
 
     // Run the tests on a portable component hosted by a self-contained app which does have a RID fallback graph
-    // This is testing the scenario after SDK starts generating RID fallback graph even for self-contained apps 
+    // This is testing the scenario after SDK starts generating RID fallback graph even for self-contained apps
     //   - https://github.com/dotnet/sdk/issues/3361
     public class PortableComponentOnSelfContainedAppRidAssetResolutionWithRidFallbackGraph :
         RidAssetResolutionBase,
@@ -369,26 +410,35 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
                 .WithPackage("NativeDependency", "1.0.0", p => assetsCustomizer?.Invoke(p))
                 .WithCustomizer(appCustomizer));
 
-            SharedState.RunComponentResolutionTest(component.AppDll, ComponentSharedState.HostApp, ComponentSharedState.HostApp.Location, command => command
+            // Use the fallbacks from the product when testing the computed RID
+            TestApp app = rid == null ? ComponentSharedState.HostApp_RuntimeFallbacks : ComponentSharedState.HostApp;
+            SharedState.RunComponentResolutionTest(component.AppDll, app, app.Location, command => command
                 .RuntimeId(rid))
                 .Should().Pass()
                 .And.HaveSuccessfullyResolvedComponentDependencies()
                 .And.HaveResolvedComponentDependencyAssembly(includedAssemblyPaths, component)
                 .And.NotHaveResolvedComponentDependencyAssembly(excludedAssemblyPaths, component)
                 .And.HaveResolvedComponentDependencyNativeLibraryPath(includedNativeLibraryPaths, component)
-                .And.NotHaveResolvedComponentDependencyNativeLibraryPath(excludedNativeLibraryPaths, component);
+                .And.NotHaveResolvedComponentDependencyNativeLibraryPath(excludedNativeLibraryPaths, component)
+                .And.HaveUsedFallbackRid(rid == UnknownRid);
         }
 
         public class ComponentSharedTestState : SharedTestState
         {
             public TestApp HostApp { get; }
+            public TestApp HostApp_RuntimeFallbacks { get; }
 
             public ComponentSharedTestState()
             {
                 HostApp = CreateSelfContainedAppWithMockCoreClr(
-                    "ComponentHostSelfContainedApp", 
+                    "ComponentHostSelfContainedApp",
                     "1.0.0",
                     b => b.WithStandardRuntimeFallbacks());
+
+                HostApp_RuntimeFallbacks = CreateSelfContainedAppWithMockCoreClr(
+                    "ComponentHostSelfContainedApp_RuntimeFallbacks",
+                    "1.0.0",
+                    UseFallbacksFromBuiltDotNet);
             }
         }
     }