diff --git a/eng/testing/RunnerTemplate.sh b/eng/testing/RunnerTemplate.sh
index 0e6e0b778bd4e5..c990b67d06496f 100644
--- a/eng/testing/RunnerTemplate.sh
+++ b/eng/testing/RunnerTemplate.sh
@@ -22,7 +22,9 @@ while [[ $# -gt 0 ]]; do
exit 0
;;
--runtime-path|-r)
- RUNTIME_PATH=$2
+ # Exported so that tests which need to launch a portable application of their own can find the
+ # same host the test run itself was handed. RunnerTemplate.cmd's "set" already does this.
+ export RUNTIME_PATH=$2
shift
;;
--rsp-file)
diff --git a/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/Common/DotNetCommands.cs b/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/Common/DotNetCommands.cs
index 7282154c33498c..3e6c5eabde7ac4 100644
--- a/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/Common/DotNetCommands.cs
+++ b/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/Common/DotNetCommands.cs
@@ -9,69 +9,42 @@ namespace Microsoft.Extensions.Hosting.IntegrationTesting
{
public static class DotNetCommands
{
- private const string _dotnetFolderName = ".dotnet";
+ // Set by the test run script to the testhost it was handed via --runtime-path: the locally built
+ // testhost for a local run, the Helix correlation payload in CI. See eng/testing/RunnerTemplate.sh
+ // and eng/testing/RunnerTemplate.cmd.
+ private const string RuntimePathVariableName = "RUNTIME_PATH";
- internal static string DotNetHome { get; } = GetDotNetHome();
+ ///
+ /// Gets the full path of the muxer that portable applications are launched with, or
+ /// when the current test environment has not got one.
+ ///
+ public static string DotNetMuxerPath { get; } = FindDotNetMuxer();
- // Compare to https://github.com/aspnet/BuildTools/blob/314c98e4533217a841ff9767bb38e144eb6c93e4/tools/KoreBuild.Console/Commands/CommandContext.cs#L76
- public static string GetDotNetHome()
- {
- var dotnetHome = Environment.GetEnvironmentVariable("DOTNET_HOME");
- var dotnetRoot = Environment.GetEnvironmentVariable("DOTNET_ROOT");
- var userProfile = Environment.GetEnvironmentVariable("USERPROFILE");
- var home = Environment.GetEnvironmentVariable("HOME");
-
- var result = Path.Combine(Directory.GetCurrentDirectory(), _dotnetFolderName);
- if (!string.IsNullOrEmpty(dotnetHome))
- {
- result = dotnetHome;
- }
- else if (!string.IsNullOrEmpty(dotnetRoot))
- {
- // DOTNET_ROOT has x64 appended to the path, which we append again in GetDotNetInstallDir
- result = dotnetRoot.Substring(0, dotnetRoot.Length - 3);
- }
- else if (!string.IsNullOrEmpty(userProfile))
- {
- result = Path.Combine(userProfile, _dotnetFolderName);
- }
- else if (!string.IsNullOrEmpty(home))
- {
- result = home;
- }
-
- return result;
- }
+ public static string DotNetExecutableName
+ => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "dotnet.exe" : "dotnet";
- public static string GetDotNetInstallDir(RuntimeArchitecture arch)
+ private static string FindDotNetMuxer()
{
- var dotnetDir = DotNetHome;
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ var runtimePath = Environment.GetEnvironmentVariable(RuntimePathVariableName);
+ if (!string.IsNullOrEmpty(runtimePath))
{
- dotnetDir = Path.Combine(dotnetDir, arch.ToString());
+ var fromRunScript = Path.Combine(runtimePath, DotNetExecutableName);
+ if (File.Exists(fromRunScript))
+ {
+ return fromRunScript;
+ }
}
- return dotnetDir;
- }
-
- public static string GetDotNetExecutable(RuntimeArchitecture arch)
- {
- var dotnetDir = GetDotNetInstallDir(arch);
-
- var dotnetFile = "dotnet";
-
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- dotnetFile += ".exe";
- }
-
- return Path.Combine(dotnetDir, dotnetFile);
- }
-
- public static bool IsRunningX86OnX64(RuntimeArchitecture arch)
- {
- return (RuntimeInformation.OSArchitecture == Architecture.X64 || RuntimeInformation.OSArchitecture == Architecture.Arm64)
- && arch == RuntimeArchitecture.x86;
+#if NETFRAMEWORK
+ return null;
+#else
+ // Outside the run script the only host we can vouch for is the one running the tests, which is
+ // the muxer itself on every leg that does not publish the tests as a self-contained application.
+ var processPath = Environment.ProcessPath;
+ return string.Equals(Path.GetFileName(processPath), DotNetExecutableName, StringComparison.OrdinalIgnoreCase)
+ ? processPath
+ : null;
+#endif
}
}
}
diff --git a/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/Deployers/ApplicationDeployer.cs b/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/Deployers/ApplicationDeployer.cs
index 6a96659f406835..31b689d3f1114a 100644
--- a/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/Deployers/ApplicationDeployer.cs
+++ b/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/Deployers/ApplicationDeployer.cs
@@ -18,8 +18,6 @@ namespace Microsoft.Extensions.Hosting.IntegrationTesting
///
public abstract class ApplicationDeployer : IDisposable
{
- public static readonly string DotnetCommandName = "dotnet";
-
private readonly Stopwatch _stopwatch = new Stopwatch();
private PublishedApplication _publishedApplication;
@@ -100,33 +98,21 @@ protected void CleanPublishedOutput()
}
}
- protected string GetDotNetExeForArchitecture()
- {
- var executableName = DotnetCommandName;
- // We expect x64 dotnet.exe to be on the path but we have to go searching for the x86 version.
- if (DotNetCommands.IsRunningX86OnX64(DeploymentParameters.RuntimeArchitecture))
- {
- executableName = DotNetCommands.GetDotNetExecutable(DeploymentParameters.RuntimeArchitecture);
- if (!File.Exists(executableName))
- {
- throw new Exception($"Unable to find '{executableName}'.'");
- }
- }
-
- return executableName;
- }
+ protected static string GetDotNetMuxerPath()
+ => DotNetCommands.DotNetMuxerPath
+ ?? throw new Exception($"Unable to find '{DotNetCommands.DotNetExecutableName}'.");
protected void ShutDownIfAnyHostProcess(Process hostProcess)
{
- if (hostProcess != null && !hostProcess.HasExited)
+ if (hostProcess is not null && IsRunning(hostProcess))
{
Logger.LogInformation("Attempting to cancel process {0}", hostProcess.Id);
// Shutdown the host process.
hostProcess.KillTree();
- if (!hostProcess.HasExited)
+ if (IsRunning(hostProcess))
{
- Logger.LogWarning("Unable to terminate the host process with process Id '{processId}", hostProcess.Id);
+ Logger.LogWarning("Unable to terminate the host process with process Id '{processId}'", hostProcess.Id);
}
else
{
@@ -139,6 +125,22 @@ protected void ShutDownIfAnyHostProcess(Process hostProcess)
}
}
+ // Process.HasExited throws InvalidOperationException ("No process is associated with this object")
+ // when the process was never started (and also after the Process has been disposed, which disassociates
+ // it). Treat that as "not running" so shutdown cleanup stays non-throwing rather than masking the
+ // original start failure with a misleading exception.
+ private static bool IsRunning(Process hostProcess)
+ {
+ try
+ {
+ return !hostProcess.HasExited;
+ }
+ catch (InvalidOperationException)
+ {
+ return false;
+ }
+ }
+
protected void AddEnvironmentVariablesToProcess(ProcessStartInfo startInfo, IDictionary environmentVariables)
{
var environment = startInfo.Environment;
diff --git a/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/Deployers/SelfHostDeployer.cs b/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/Deployers/SelfHostDeployer.cs
index b5ef69dee0664d..9cd579b1886962 100644
--- a/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/Deployers/SelfHostDeployer.cs
+++ b/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/Deployers/SelfHostDeployer.cs
@@ -96,7 +96,7 @@ protected async Task StartSelfHostAsync()
if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr && DeploymentParameters.ApplicationType == ApplicationType.Portable)
{
- executableName = GetDotNetExeForArchitecture();
+ executableName = GetDotNetMuxerPath();
executableArgs = executable;
}
else
@@ -121,7 +121,8 @@ protected async Task StartSelfHostAsync()
AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.EnvironmentVariables);
- var started = new TaskCompletionSource