diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/DotnetHostHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/DotnetHostHelper.cs
index 0fadf4e631..6c9efbd274 100644
--- a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/DotnetHostHelper.cs
+++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/DotnetHostHelper.cs
@@ -5,6 +5,7 @@
namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers
{
+ using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers;
using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Resources;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
@@ -12,9 +13,10 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers
using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces;
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers;
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces;
-
+ using Microsoft.Win32;
using System;
using System.IO;
+ using System.Reflection.PortableExecutable;
public class DotnetHostHelper : IDotnetHostHelper
{
@@ -22,11 +24,15 @@ public class DotnetHostHelper : IDotnetHostHelper
private readonly IFileHelper fileHelper;
private readonly IEnvironment environment;
+ private readonly IWindowsRegistryHelper windowsRegistryHelper;
+ private readonly IEnvironmentVariableHelper environmentVariableHelper;
+ private readonly IProcessHelper processHelper;
+ private readonly string muxerName;
///
/// Initializes a new instance of the class.
///
- public DotnetHostHelper() : this(new FileHelper(), new PlatformEnvironment())
+ public DotnetHostHelper() : this(new FileHelper(), new PlatformEnvironment(), new WindowsRegistryHelper(), new EnvironmentVariableHelper(), new ProcessHelper())
{
}
@@ -34,10 +40,32 @@ public class DotnetHostHelper : IDotnetHostHelper
/// Initializes a new instance of the class.
///
/// File Helper
- public DotnetHostHelper(IFileHelper fileHelper, IEnvironment environment)
+ /// Environment Helper
+ public DotnetHostHelper(IFileHelper fileHelper, IEnvironment environment) : this(fileHelper, environment, new WindowsRegistryHelper(), new EnvironmentVariableHelper(), new ProcessHelper())
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// File Helper
+ /// Environment Helper
+ /// WindowsRegistry Helper
+ /// EnvironmentVariable Helper
+ /// Process Helper
+ internal DotnetHostHelper(
+ IFileHelper fileHelper,
+ IEnvironment environment,
+ IWindowsRegistryHelper windowsRegistryHelper,
+ IEnvironmentVariableHelper environmentVariableHelper,
+ IProcessHelper processHelper)
{
this.fileHelper = fileHelper;
this.environment = environment;
+ this.windowsRegistryHelper = windowsRegistryHelper;
+ this.environmentVariableHelper = environmentVariableHelper;
+ this.processHelper = processHelper;
+ this.muxerName = environment.OperatingSystem == PlatformOperatingSystem.Windows ? "dotnet.exe" : "dotnet";
}
///
@@ -88,6 +116,375 @@ private bool TryGetExecutablePath(string executableBaseName, out string executab
return false;
}
+
+ public bool TryGetDotnetPathByArchitecture(PlatformArchitecture targetArchitecture, out string muxerPath)
+ {
+ if (this.environment.Architecture == targetArchitecture)
+ {
+ string currentProcessFileName = this.processHelper.GetCurrentProcessFileName();
+ if (Path.GetFileName(currentProcessFileName) != this.muxerName)
+ {
+ EqtTrace.Verbose($"DotnetHostHelper.TryGetDotnetPathByArchitecture: Target architecture is the same as the current process architecture '{targetArchitecture}', but the current process is not a muxer: '{currentProcessFileName}'");
+ }
+ else
+ {
+ muxerPath = currentProcessFileName;
+ EqtTrace.Verbose($"DotnetHostHelper.TryGetDotnetPathByArchitecture: Target architecture is the same as the current process architecture '{targetArchitecture}', and the current process is a muxer, using that: '{muxerPath}'");
+ return true;
+ }
+ }
+
+ // We used similar approach as the runtime resolver.
+ // https://github.com/dotnet/runtime/blob/main/src/native/corehost/fxr_resolver.cpp#L55
+
+ bool isWinOs = environment.OperatingSystem == PlatformOperatingSystem.Windows;
+ EqtTrace.Verbose($"DotnetHostHelper.TryGetDotnetPathByArchitecture: Searching for muxer named '{muxerName}'");
+
+ // Try to search using env vars in the order
+ // DOTNET_ROOT_{arch}
+ // DOTNET_ROOT(x86) if X86 on Win (here we cannot check if current process is WOW64 because this is SDK process arch and not real host arch so it's irrelevant)
+ // "DOTNET_ROOT(x86) is used instead when running a 32-bit executable on a 64-bit OS."
+ // DOTNET_ROOT
+ string envKey = $"DOTNET_ROOT_{targetArchitecture.ToString().ToUpperInvariant()}";
+
+ // Try on arch specific env var
+ string envVar = this.environmentVariableHelper.GetEnvironmentVariable(envKey);
+
+ // Try on non virtualized x86 var(should happen only on non-x86 architecture)
+ if ((envVar == null || !this.fileHelper.DirectoryExists(envVar)) &&
+ targetArchitecture == PlatformArchitecture.X86 && this.environment.OperatingSystem == PlatformOperatingSystem.Windows)
+ {
+ envKey = $"DOTNET_ROOT(x86)";
+ envVar = this.environmentVariableHelper.GetEnvironmentVariable(envKey);
+ }
+
+ // Try on default DOTNET_ROOT
+ if (envVar == null || !this.fileHelper.DirectoryExists(envVar))
+ {
+ envKey = "DOTNET_ROOT";
+ envVar = this.environmentVariableHelper.GetEnvironmentVariable(envKey);
+ }
+
+ if (envVar != null)
+ {
+ // If directory specified by env vars does not exists, it's like env var doesn't exists as well.
+ if (!this.fileHelper.DirectoryExists(envVar))
+ {
+ EqtTrace.Verbose($"DotnetHostHelper.TryGetDotnetPathByArchitecture: Folder specified by env variable does not exist: '{envVar}={envKey}'");
+ }
+ else
+ {
+ muxerPath = Path.Combine(envVar, muxerName);
+ if (!this.fileHelper.Exists(muxerPath))
+ {
+ // If environment variable was specified, and the directory it points at exists, but it does not contain a muxer, or the muxer is incompatible with the target architecture
+ // we stop the search to be compliant with the approach that apphost (compiled .NET executables) use to find the muxer.
+ EqtTrace.Verbose($"DotnetHostHelper.TryGetDotnetPathByArchitecture: Folder specified by env variable does not contain any muxer: '{envVar}={envKey}'");
+ muxerPath = null;
+ return false;
+ }
+
+ if (!IsValidArchitectureMuxer(targetArchitecture, muxerPath))
+ {
+ EqtTrace.Verbose($"DotnetHostHelper: Invalid muxer resolved using env var key '{envKey}' in '{envVar}'");
+ muxerPath = null;
+ return false;
+ }
+
+ EqtTrace.Verbose($"DotnetHostHelper.TryGetDotnetPathByArchitecture: Muxer compatible with '{targetArchitecture}' resolved from env variable '{envKey}' in '{muxerPath}'");
+ return true;
+ }
+ }
+
+ EqtTrace.Verbose($"DotnetHostHelper.TryGetDotnetPathByArchitecture: Muxer was not found using DOTNET_ROOT* env variables.");
+
+ // Try to search for global registration
+ if (isWinOs)
+ {
+ muxerPath = GetMuxerFromGlobalRegistrationWin(targetArchitecture);
+ }
+ else
+ {
+ muxerPath = GetMuxerFromGlobalRegistrationOnUnix(targetArchitecture);
+ }
+
+ if (muxerPath != null)
+ {
+ if (!this.fileHelper.Exists(muxerPath))
+ {
+ // If muxer doesn't exists or it's wrong we stop the search
+ EqtTrace.Verbose($"DotnetHostHelper.TryGetDotnetPathByArchitecture: Muxer file not found for global registration '{muxerPath}'");
+ muxerPath = null;
+ return false;
+ }
+
+ if (!IsValidArchitectureMuxer(targetArchitecture, muxerPath))
+ {
+ // If muxer is wrong we stop the search
+ EqtTrace.Verbose($"DotnetHostHelper.TryGetDotnetPathByArchitecture: Muxer resolved using global registration is not compatible with the target architecture: '{muxerPath}'");
+ muxerPath = null;
+ return false;
+ }
+
+ EqtTrace.Verbose($"DotnetHostHelper.TryGetDotnetPathByArchitecture: Muxer compatible with '{targetArchitecture}' resolved from global registration: '{muxerPath}'");
+ return true;
+ }
+
+ EqtTrace.Verbose($"DotnetHostHelper.TryGetDotnetPathByArchitecture: Muxer not found using global registrations");
+
+ // Try searching in default installation location if it exists
+ if (isWinOs)
+ {
+ // If we're on x64/arm64 SDK and target is x86 we need to search on non virtualized windows folder
+ if ((this.environment.Architecture == PlatformArchitecture.X64 || this.environment.Architecture == PlatformArchitecture.ARM64) &&
+ targetArchitecture == PlatformArchitecture.X86)
+ {
+ muxerPath = Path.Combine(this.environmentVariableHelper.GetEnvironmentVariable("ProgramFiles(x86)"), "dotnet", muxerName);
+ }
+ else
+ {
+ // If we're on ARM and target is x64 we expect correct installation inside x64 folder
+ if (this.environment.Architecture == PlatformArchitecture.ARM64 && targetArchitecture == PlatformArchitecture.X64)
+ {
+ muxerPath = Path.Combine(this.environmentVariableHelper.GetEnvironmentVariable("ProgramFiles"), "dotnet", "x64", muxerName);
+ }
+ else
+ {
+ muxerPath = Path.Combine(this.environmentVariableHelper.GetEnvironmentVariable("ProgramFiles"), "dotnet", muxerName);
+ }
+ }
+ }
+ else
+ {
+ if (this.environment.OperatingSystem == PlatformOperatingSystem.OSX)
+ {
+ // If we're on ARM and target is x64 we expect correct installation inside x64 folder
+ if (this.environment.Architecture == PlatformArchitecture.ARM64 && targetArchitecture == PlatformArchitecture.X64)
+ {
+ muxerPath = Path.Combine("/usr/local/share/dotnet/x64", muxerName);
+ }
+ else
+ {
+ muxerPath = Path.Combine("/usr/local/share/dotnet", muxerName);
+ }
+ }
+ else
+ {
+ muxerPath = Path.Combine("/usr/share/dotnet", muxerName);
+ }
+ }
+
+ if (!this.fileHelper.Exists(muxerPath))
+ {
+ // If muxer doesn't exists we stop the search
+ EqtTrace.Verbose($"DotnetHostHelper.TryGetDotnetPathByArchitecture: Muxer was not found in default installation location: '{muxerPath}'");
+ muxerPath = null;
+ return false;
+ }
+
+ if (!IsValidArchitectureMuxer(targetArchitecture, muxerPath))
+ {
+ // If muxer is wrong we stop the search
+ EqtTrace.Verbose($"DotnetHostHelper.TryGetDotnetPathByArchitecture: Muxer resolved in default installation path is not compatible with the target architecture: '{muxerPath}'");
+ muxerPath = null;
+ return false;
+ }
+
+ EqtTrace.Verbose($"DotnetHostHelper.TryGetDotnetPathByArchitecture: Muxer compatible with '{targetArchitecture}' resolved from default installation path: '{muxerPath}'");
+ return true;
+ }
+
+ private string GetMuxerFromGlobalRegistrationWin(PlatformArchitecture targetArchitecture)
+ {
+ // Installed version are always in 32-bit view of registry
+ // https://github.com/dotnet/designs/blob/main/accepted/2020/install-locations.md#globally-registered-install-location-new
+ // "Note that this registry key is "redirected" that means that 32-bit processes see different copy of the key than 64bit processes.
+ // So it's important that both installers and the host access only the 32-bit view of the registry."
+ using (IRegistryKey hklm = windowsRegistryHelper.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
+ {
+ if (hklm != null)
+ {
+ using (IRegistryKey dotnetInstalledVersion = hklm.OpenSubKey(@"SOFTWARE\dotnet\Setup\InstalledVersions"))
+ {
+ if (dotnetInstalledVersion != null)
+ {
+ using (IRegistryKey nativeArch = dotnetInstalledVersion.OpenSubKey(targetArchitecture.ToString().ToLowerInvariant()))
+ {
+ string installLocation = nativeArch?.GetValue("InstallLocation")?.ToString();
+
+ if (installLocation != null)
+ {
+ string path = Path.Combine(installLocation.Trim(), this.muxerName);
+ EqtTrace.Verbose($@"DotnetHostHelper.GetMuxerFromGlobalRegistrationWin: Muxer resolved using win registry key 'SOFTWARE\dotnet\Setup\InstalledVersions\{targetArchitecture.ToString().ToLowerInvariant()}\InstallLocation' in '{path}'");
+ return path;
+ }
+ else
+ {
+ EqtTrace.Verbose($@"DotnetHostHelper.GetMuxerFromGlobalRegistrationWin: Missing registry InstallLocation");
+ }
+ }
+ }
+ else
+ {
+ EqtTrace.Verbose($@"DotnetHostHelper.GetMuxerFromGlobalRegistrationWin: Missing RegistryHive.LocalMachine for RegistryView.Registry32");
+ }
+ }
+ }
+ else
+ {
+ EqtTrace.Verbose($@"DotnetHostHelper.GetMuxerFromGlobalRegistrationWin: Missing SOFTWARE\dotnet\Setup\InstalledVersions subkey");
+ }
+ }
+
+ return null;
+ }
+
+ private string GetMuxerFromGlobalRegistrationOnUnix(PlatformArchitecture targetArchitecture)
+ {
+ string baseInstallLocation = "/etc/dotnet/";
+
+ // We search for architecture specific installation
+ string installLocation = $"{baseInstallLocation}install_location_{targetArchitecture.ToString().ToLowerInvariant()}";
+
+ // We try to load archless install location file
+ if (!this.fileHelper.Exists(installLocation))
+ {
+ installLocation = $"{baseInstallLocation}install_location";
+ }
+
+ if (this.fileHelper.Exists(installLocation))
+ {
+ try
+ {
+ using (Stream stream = this.fileHelper.GetStream(installLocation, FileMode.Open, FileAccess.Read))
+ using (StreamReader streamReader = new StreamReader(stream))
+ {
+ string content = streamReader.ReadToEnd().Trim();
+ EqtTrace.Verbose($"DotnetHostHelper: '{installLocation}' content '{content}'");
+ string path = Path.Combine(content, this.muxerName);
+ EqtTrace.Verbose($"DotnetHostHelper: Muxer resolved using '{installLocation}' in '{path}'");
+ return path;
+ }
+ }
+ catch (Exception ex)
+ {
+ EqtTrace.Error($"DotnetHostHelper.GetMuxerFromGlobalRegistrationOnUnix: Exception during '{installLocation}' muxer resolution.\n{ex}");
+ }
+ }
+
+ return null;
+ }
+
+ private PlatformArchitecture? GetMuxerArchitectureByPEHeaderOnWin(string path)
+ {
+ try
+ {
+ using (Stream stream = this.fileHelper.GetStream(path, FileMode.Open, FileAccess.Read))
+ using (PEReader peReader = new PEReader(stream))
+ {
+ switch (peReader.PEHeaders.CoffHeader.Machine)
+ {
+ case Machine.Amd64:
+ return PlatformArchitecture.X64;
+ case Machine.IA64:
+ return PlatformArchitecture.X64;
+ case Machine.Arm64:
+ return PlatformArchitecture.ARM64;
+ case Machine.Arm:
+ return PlatformArchitecture.ARM;
+ case Machine.I386:
+ return PlatformArchitecture.X86;
+ default:
+ break;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ EqtTrace.Error($"DotnetHostHelper.GetMuxerArchitectureByPEHeaderOnWin: Failed to get architecture from PEHeader for '{path}'\n{ex}");
+ }
+
+ return null;
+ }
+
+ // See https://opensource.apple.com/source/xnu/xnu-2050.18.24/EXTERNAL_HEADERS/mach-o/loader.h
+ // https://opensource.apple.com/source/xnu/xnu-4570.41.2/osfmk/mach/machine.h.auto.html
+ private PlatformArchitecture? GetMuxerArchitectureByMachoOnMac(string path)
+ {
+ try
+ {
+ PlatformArchitecture? architecture;
+ using (var headerReader = this.fileHelper.GetStream(path, FileMode.Open, FileAccess.Read))
+ {
+ var magicBytes = new byte[4];
+ var cpuInfoBytes = new byte[4];
+ headerReader.Read(magicBytes, 0, magicBytes.Length);
+ headerReader.Read(cpuInfoBytes, 0, cpuInfoBytes.Length);
+
+ var magic = BitConverter.ToUInt32(magicBytes, 0);
+ var cpuInfo = BitConverter.ToUInt32(cpuInfoBytes, 0);
+ switch ((MacOsCpuType)cpuInfo)
+ {
+ case MacOsCpuType.Arm64Magic:
+ case MacOsCpuType.Arm64Cigam:
+ architecture = PlatformArchitecture.ARM64;
+ break;
+ case MacOsCpuType.X64Magic:
+ case MacOsCpuType.X64Cigam:
+ architecture = PlatformArchitecture.X64;
+ break;
+ case MacOsCpuType.X86Magic:
+ case MacOsCpuType.X86Cigam:
+ architecture = PlatformArchitecture.X86;
+ break;
+ default:
+ architecture = null;
+ break;
+ }
+
+ return architecture;
+ }
+ }
+ catch (Exception ex)
+ {
+ // In case of failure during header reading we must fallback to the next place(default installation path)
+ EqtTrace.Error($"DotnetHostHelper.GetMuxerArchitectureByMachoOnMac: Failed to get architecture from Mach-O for '{path}'\n{ex}");
+ }
+
+ return null;
+ }
+
+ internal enum MacOsCpuType : uint
+ {
+ Arm64Magic = 0x0100000c,
+ Arm64Cigam = 0x0c000001,
+ X64Magic = 0x01000007,
+ X64Cigam = 0x07000001,
+ X86Magic = 0x00000007,
+ X86Cigam = 0x07000000
+ }
+
+ private bool IsValidArchitectureMuxer(PlatformArchitecture targetArchitecture, string path)
+ {
+ PlatformArchitecture? muxerPlatform = null;
+ if (this.environment.OperatingSystem == PlatformOperatingSystem.Windows)
+ {
+ muxerPlatform = GetMuxerArchitectureByPEHeaderOnWin(path);
+ }
+ else if (this.environment.OperatingSystem == PlatformOperatingSystem.OSX)
+ {
+ muxerPlatform = GetMuxerArchitectureByMachoOnMac(path);
+ }
+
+ if (targetArchitecture != muxerPlatform)
+ {
+ EqtTrace.Verbose($"DotnetHostHelper.IsValidArchitectureMuxer: Incompatible architecture muxer, target architecture '{targetArchitecture}', actual '{muxerPlatform}'");
+ return false;
+ }
+
+ EqtTrace.Verbose($"DotnetHostHelper.IsValidArchitectureMuxer: Compatible architecture muxer, target architecture '{targetArchitecture}', actual '{muxerPlatform}'");
+ return true;
+ }
}
}
diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/EnvironmentVariableHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/EnvironmentVariableHelper.cs
new file mode 100644
index 0000000000..db75f3d704
--- /dev/null
+++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/EnvironmentVariableHelper.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#if !NETSTANDARD1_0
+
+using System;
+using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces;
+
+namespace Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers
+{
+ internal class EnvironmentVariableHelper : IEnvironmentVariableHelper
+ {
+ public string GetEnvironmentVariable(string variable)
+ => Environment.GetEnvironmentVariable(variable);
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IDotnetHostHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IDotnetHostHelper.cs
index 6793748a4b..6f166f36a6 100644
--- a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IDotnetHostHelper.cs
+++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IDotnetHostHelper.cs
@@ -1,6 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+using System;
+using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions;
+
namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces
{
///
@@ -20,5 +23,13 @@ public interface IDotnetHostHelper
///
/// Full path to mono executable
string GetMonoPath();
+
+ ///
+ /// Try to locate muxer of specific architecture
+ ///
+ /// Specific architecture
+ /// Path to the muxer
+ /// True if native muxer is found
+ bool TryGetDotnetPathByArchitecture(PlatformArchitecture targetArchitecture, out string muxerPath);
}
}
diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IEnvironmentVariableHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IEnvironmentVariableHelper.cs
new file mode 100644
index 0000000000..7734fa2198
--- /dev/null
+++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IEnvironmentVariableHelper.cs
@@ -0,0 +1,10 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces
+{
+ internal interface IEnvironmentVariableHelper
+ {
+ string GetEnvironmentVariable(string variable);
+ }
+}
diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IRunsettingsHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IRunsettingsHelper.cs
new file mode 100644
index 0000000000..21064568a2
--- /dev/null
+++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IRunsettingsHelper.cs
@@ -0,0 +1,10 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces
+{
+ internal interface IRunSettingsHelper
+ {
+ bool IsDefaultTargetArchitecture { get; set; }
+ }
+}
diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IWindowsRegistryHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IWindowsRegistryHelper.cs
new file mode 100644
index 0000000000..27328f76c4
--- /dev/null
+++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IWindowsRegistryHelper.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+
+#if !NETSTANDARD1_0
+
+using System;
+using Microsoft.Win32;
+
+namespace Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces
+{
+ internal interface IWindowsRegistryHelper
+ {
+ IRegistryKey OpenBaseKey(RegistryHive hKey, RegistryView view);
+ }
+
+ internal interface IRegistryKey : IDisposable
+ {
+ IRegistryKey OpenSubKey(string name);
+
+ object GetValue(string name);
+
+ string[] GetSubKeyNames();
+ }
+}
+
+#endif
diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/RunSettingsHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/RunSettingsHelper.cs
new file mode 100644
index 0000000000..8b2387a845
--- /dev/null
+++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/RunSettingsHelper.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces;
+
+namespace Microsoft.VisualStudio.TestPlatform.Utilities.Helpers
+{
+ ///
+ /// RunSettingsHelper is used to globally share additional informations about the state of runsettings.
+ ///
+ internal class RunSettingsHelper : IRunSettingsHelper
+ {
+ private static IRunSettingsHelper runSettings = new RunSettingsHelper();
+
+ public static IRunSettingsHelper Instance = runSettings;
+
+ ///
+ /// If false user updated the RunConfiguration.TargetPlatform using
+ /// --arch or runsettings file or -- RunConfiguration.TargetPlatform=arch
+ ///
+ public bool IsDefaultTargetArchitecture { get; set; } = true;
+ }
+}
diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/WindowsRegistryHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/WindowsRegistryHelper.cs
new file mode 100644
index 0000000000..82c0ed59f1
--- /dev/null
+++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/WindowsRegistryHelper.cs
@@ -0,0 +1,50 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#if !NETSTANDARD1_0
+
+using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces;
+using Microsoft.Win32;
+
+namespace Microsoft.VisualStudio.TestPlatform.Utilities.Helpers
+{
+ internal class WindowsRegistryHelper : IWindowsRegistryHelper
+ {
+ public IRegistryKey OpenBaseKey(RegistryHive hKey, RegistryView view)
+ {
+ var keyRegistry = RegistryKey.OpenBaseKey(hKey, view);
+ return keyRegistry is null ? null : new RegistryKeyWrapper(keyRegistry);
+ }
+ }
+
+ internal class RegistryKeyWrapper : IRegistryKey
+ {
+ private readonly RegistryKey registryKey;
+
+ public RegistryKeyWrapper(RegistryKey registryKey)
+ {
+ this.registryKey = registryKey;
+ }
+
+ public object GetValue(string name)
+ {
+ return registryKey?.GetValue(name)?.ToString();
+ }
+
+ public IRegistryKey OpenSubKey(string name)
+ {
+ var keyRegistry = this.registryKey.OpenSubKey(name);
+ return keyRegistry is null ? null : new RegistryKeyWrapper(keyRegistry);
+ }
+
+ public string[] GetSubKeyNames()
+ => this.registryKey?.GetSubKeyNames();
+
+ public void Dispose()
+ {
+ this.registryKey?.Dispose();
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj b/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj
index f1df0c9839..7d8551dd92 100644
--- a/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj
+++ b/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj
@@ -35,11 +35,19 @@
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Properties/AssemblyInfo.cs b/src/Microsoft.TestPlatform.CoreUtilities/Properties/AssemblyInfo.cs
index 909f61bc48..167c461f4c 100644
--- a/src/Microsoft.TestPlatform.CoreUtilities/Properties/AssemblyInfo.cs
+++ b/src/Microsoft.TestPlatform.CoreUtilities/Properties/AssemblyInfo.cs
@@ -3,6 +3,7 @@
using System.Reflection;
using System.Resources;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
@@ -20,6 +21,13 @@
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
+[assembly: InternalsVisibleTo("Microsoft.TestPlatform.TestHostRuntimeProvider, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
+[assembly: InternalsVisibleTo("vstest.console, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
+[assembly: InternalsVisibleTo("Microsoft.TestPlatform.CoreUtilities.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
+[assembly: InternalsVisibleTo("Microsoft.TestPlatform.VsTestConsole.TranslationLayer, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
+[assembly: InternalsVisibleTo("Microsoft.TestPlatform.CrossPlatEngine.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
+[assembly: InternalsVisibleTo("Microsoft.TestPlatform.TestHostProvider.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
# if !NETSTANDARD1_0
// The following GUID is for the ID of the typelib if this project is exposed to COM
diff --git a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net45/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net45/PublicAPI.Shipped.txt
index 4498a90580..52ee5ef482 100644
--- a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net45/PublicAPI.Shipped.txt
+++ b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net45/PublicAPI.Shipped.txt
@@ -111,9 +111,11 @@ Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.Dot
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.DotnetHostHelper(Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces.IFileHelper fileHelper, Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces.IEnvironment environment) -> void
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.GetDotnetPath() -> string
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.GetMonoPath() -> string
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.TryGetDotnetPathByArchitecture(Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformArchitecture targetArchitecture, out string muxerPath) -> bool
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.GetDotnetPath() -> string
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.GetMonoPath() -> string
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.TryGetDotnetPathByArchitecture(Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformArchitecture targetArchitecture, out string muxerPath) -> bool
Microsoft.VisualStudio.TestPlatform.ObjectModel.EqtTrace
Microsoft.VisualStudio.TestPlatform.ObjectModel.ValidateArg
Microsoft.VisualStudio.TestPlatform.ObjectModel.ValidateArgProperty
diff --git a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net451/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net451/PublicAPI.Shipped.txt
index 4498a90580..52ee5ef482 100644
--- a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net451/PublicAPI.Shipped.txt
+++ b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net451/PublicAPI.Shipped.txt
@@ -111,9 +111,11 @@ Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.Dot
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.DotnetHostHelper(Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces.IFileHelper fileHelper, Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces.IEnvironment environment) -> void
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.GetDotnetPath() -> string
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.GetMonoPath() -> string
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.TryGetDotnetPathByArchitecture(Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformArchitecture targetArchitecture, out string muxerPath) -> bool
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.GetDotnetPath() -> string
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.GetMonoPath() -> string
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.TryGetDotnetPathByArchitecture(Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformArchitecture targetArchitecture, out string muxerPath) -> bool
Microsoft.VisualStudio.TestPlatform.ObjectModel.EqtTrace
Microsoft.VisualStudio.TestPlatform.ObjectModel.ValidateArg
Microsoft.VisualStudio.TestPlatform.ObjectModel.ValidateArgProperty
diff --git a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/netstandard1.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/netstandard1.0/PublicAPI.Shipped.txt
index 31e312d743..789b698947 100644
--- a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/netstandard1.0/PublicAPI.Shipped.txt
+++ b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/netstandard1.0/PublicAPI.Shipped.txt
@@ -62,6 +62,7 @@ Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces.ITestPlatfo
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.GetDotnetPath() -> string
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.GetMonoPath() -> string
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.TryGetDotnetPathByArchitecture(Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformArchitecture targetArchitecture, out string muxerPath) -> bool
Microsoft.VisualStudio.TestPlatform.ObjectModel.EqtTrace
Microsoft.VisualStudio.TestPlatform.ObjectModel.ValidateArg
Microsoft.VisualStudio.TestPlatform.ObjectModel.ValidateArgProperty
diff --git a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/netstandard1.3/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/netstandard1.3/PublicAPI.Shipped.txt
index d345a76261..03f5caa93d 100644
--- a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/netstandard1.3/PublicAPI.Shipped.txt
+++ b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/netstandard1.3/PublicAPI.Shipped.txt
@@ -111,9 +111,11 @@ Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.Dot
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.DotnetHostHelper(Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces.IFileHelper fileHelper, Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces.IEnvironment environment) -> void
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.GetDotnetPath() -> string
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.GetMonoPath() -> string
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.TryGetDotnetPathByArchitecture(Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformArchitecture targetArchitecture, out string muxerPath) -> bool
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.GetDotnetPath() -> string
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.GetMonoPath() -> string
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.TryGetDotnetPathByArchitecture(Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformArchitecture targetArchitecture, out string muxerPath) -> bool
Microsoft.VisualStudio.TestPlatform.ObjectModel.EqtTrace
Microsoft.VisualStudio.TestPlatform.ObjectModel.ValidateArg
Microsoft.VisualStudio.TestPlatform.ObjectModel.ValidateArgProperty
diff --git a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt
index d345a76261..03f5caa93d 100644
--- a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt
+++ b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt
@@ -111,9 +111,11 @@ Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.Dot
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.DotnetHostHelper(Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces.IFileHelper fileHelper, Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces.IEnvironment environment) -> void
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.GetDotnetPath() -> string
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.GetMonoPath() -> string
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.TryGetDotnetPathByArchitecture(Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformArchitecture targetArchitecture, out string muxerPath) -> bool
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.GetDotnetPath() -> string
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.GetMonoPath() -> string
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.TryGetDotnetPathByArchitecture(Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformArchitecture targetArchitecture, out string muxerPath) -> bool
Microsoft.VisualStudio.TestPlatform.ObjectModel.EqtTrace
Microsoft.VisualStudio.TestPlatform.ObjectModel.ValidateArg
Microsoft.VisualStudio.TestPlatform.ObjectModel.ValidateArgProperty
diff --git a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/uap10.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/uap10.0/PublicAPI.Shipped.txt
index e1399c79ca..476777e8bd 100644
--- a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/uap10.0/PublicAPI.Shipped.txt
+++ b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/uap10.0/PublicAPI.Shipped.txt
@@ -111,9 +111,11 @@ Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.Dot
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.DotnetHostHelper(Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces.IFileHelper fileHelper, Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces.IEnvironment environment) -> void
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.GetDotnetPath() -> string
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.GetMonoPath() -> string
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.DotnetHostHelper.TryGetDotnetPathByArchitecture(Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformArchitecture targetArchitecture, out string muxerPath) -> bool
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.GetDotnetPath() -> string
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.GetMonoPath() -> string
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces.IDotnetHostHelper.TryGetDotnetPathByArchitecture(Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformArchitecture targetArchitecture, out string muxerPath) -> bool
Microsoft.VisualStudio.TestPlatform.ObjectModel.EqtTrace
Microsoft.VisualStudio.TestPlatform.ObjectModel.ValidateArg
Microsoft.VisualStudio.TestPlatform.ObjectModel.ValidateArgProperty
diff --git a/src/Microsoft.TestPlatform.Execution.Shared/DebuggerBreakpoint.cs b/src/Microsoft.TestPlatform.Execution.Shared/DebuggerBreakpoint.cs
index 4fd2e4e0ea..d99ed1419e 100644
--- a/src/Microsoft.TestPlatform.Execution.Shared/DebuggerBreakpoint.cs
+++ b/src/Microsoft.TestPlatform.Execution.Shared/DebuggerBreakpoint.cs
@@ -1,10 +1,8 @@
using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions;
using Microsoft.VisualStudio.TestPlatform.Utilities;
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpDumper.cs
index 7c79bb586d..14d362d5c0 100644
--- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpDumper.cs
+++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpDumper.cs
@@ -281,7 +281,7 @@ private bool TryGetProcDumpExecutable(int processId, out string path)
Constants.Procdump64Process : Constants.ProcdumpProcess;
}
}
- else if (this.environment.OperatingSystem == PlatformOperatingSystem.Unix)
+ else if (this.environment.OperatingSystem == PlatformOperatingSystem.Unix || this.environment.OperatingSystem == PlatformOperatingSystem.OSX)
{
filename = Constants.ProcdumpUnixProcess;
}
diff --git a/src/Microsoft.TestPlatform.ObjectModel/Constants.cs b/src/Microsoft.TestPlatform.ObjectModel/Constants.cs
index 8e3439535f..8b1d57bbb8 100644
--- a/src/Microsoft.TestPlatform.ObjectModel/Constants.cs
+++ b/src/Microsoft.TestPlatform.ObjectModel/Constants.cs
@@ -153,11 +153,7 @@ public static class Constants
public const string EmptyRunSettings = @"";
- public static readonly Architecture DefaultPlatform = XmlRunSettingsUtilities.OSArchitecture == Architecture.ARM
- ? Architecture.ARM :
- XmlRunSettingsUtilities.OSArchitecture == Architecture.X64
- ? Architecture.X64
- : Architecture.X86;
+ public static readonly Architecture DefaultPlatform = XmlRunSettingsUtilities.OSArchitecture;
///
/// Adding this for compatibility
diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/XmlRunSettingsUtilities.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/XmlRunSettingsUtilities.cs
index ec42feed69..c0e21ac443 100644
--- a/src/Microsoft.TestPlatform.ObjectModel/Utilities/XmlRunSettingsUtilities.cs
+++ b/src/Microsoft.TestPlatform.ObjectModel/Utilities/XmlRunSettingsUtilities.cs
@@ -37,8 +37,12 @@ public static ObjectModel.Architecture OSArchitecture
return ObjectModel.Architecture.X64;
case PlatformArchitecture.X86:
return ObjectModel.Architecture.X86;
- default:
+ case PlatformArchitecture.ARM64:
+ return ObjectModel.Architecture.ARM64;
+ case PlatformArchitecture.ARM:
return ObjectModel.Architecture.ARM;
+ default:
+ return ObjectModel.Architecture.X64;
}
}
}
diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/netcore/System/PlatformEnvironment.cs b/src/Microsoft.TestPlatform.PlatformAbstractions/netcore/System/PlatformEnvironment.cs
index ed2f902844..e96c3aaf9b 100644
--- a/src/Microsoft.TestPlatform.PlatformAbstractions/netcore/System/PlatformEnvironment.cs
+++ b/src/Microsoft.TestPlatform.PlatformAbstractions/netcore/System/PlatformEnvironment.cs
@@ -50,6 +50,11 @@ public PlatformOperatingSystem OperatingSystem
return PlatformOperatingSystem.Windows;
}
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ return PlatformOperatingSystem.OSX;
+ }
+
return PlatformOperatingSystem.Unix;
}
}
diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs
index c2a49fba1d..65a4c52eaf 100644
--- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs
+++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs
@@ -6,6 +6,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting
using System;
using System.Collections.Generic;
using System.Diagnostics;
+ using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -14,7 +15,9 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyModel;
using Microsoft.TestPlatform.TestHostProvider.Hosting;
+ using Microsoft.TestPlatform.TestHostProvider.Resources;
using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Extensions;
+ using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
@@ -27,6 +30,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting
using Microsoft.VisualStudio.TestPlatform.Utilities;
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers;
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces;
+ using Microsoft.Win32;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -48,8 +52,10 @@ public class DotnetTestHostManager : ITestRuntimeProvider2
private IDotnetHostHelper dotnetHostHelper;
private IEnvironment platformEnvironment;
private IProcessHelper processHelper;
-
+ private IRunSettingsHelper runsettingHelper;
private IFileHelper fileHelper;
+ private IWindowsRegistryHelper windowsRegistryHelper;
+ private IEnvironmentVariableHelper environmentVariableHelper;
private ITestHostLauncher customTestHostLauncher;
@@ -73,7 +79,14 @@ public class DotnetTestHostManager : ITestRuntimeProvider2
/// Initializes a new instance of the class.
///
public DotnetTestHostManager()
- : this(new ProcessHelper(), new FileHelper(), new DotnetHostHelper(), new PlatformEnvironment())
+ : this(
+ new ProcessHelper(),
+ new FileHelper(),
+ new DotnetHostHelper(),
+ new PlatformEnvironment(),
+ RunSettingsHelper.Instance,
+ new WindowsRegistryHelper(),
+ new EnvironmentVariableHelper())
{
}
@@ -84,16 +97,25 @@ public DotnetTestHostManager()
/// File helper instance.
/// DotnetHostHelper helper instance.
/// Platform Environment
+ /// RunsettingHelper instance
+ /// WindowsRegistryHelper instance
+ /// EnvironmentVariableHelper instance
internal DotnetTestHostManager(
IProcessHelper processHelper,
IFileHelper fileHelper,
IDotnetHostHelper dotnetHostHelper,
- IEnvironment platformEnvironment)
+ IEnvironment platformEnvironment,
+ IRunSettingsHelper runsettingHelper,
+ IWindowsRegistryHelper windowsRegistryHelper,
+ IEnvironmentVariableHelper environmentVariableHelper)
{
this.processHelper = processHelper;
this.fileHelper = fileHelper;
this.dotnetHostHelper = dotnetHostHelper;
this.platformEnvironment = platformEnvironment;
+ this.runsettingHelper = runsettingHelper;
+ this.windowsRegistryHelper = windowsRegistryHelper;
+ this.environmentVariableHelper = environmentVariableHelper;
}
///
@@ -185,6 +207,8 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo(
IDictionary environmentVariables,
TestRunnerConnectionInfo connectionInfo)
{
+ EqtTrace.Verbose($"DotnetTestHostmanager.GetTestHostProcessStartInfo: Platform environment '{this.platformEnvironment.Architecture}' target architecture '{this.architecture}' framework '{this.targetFramework}' OS '{this.platformEnvironment.OperatingSystem}'");
+
var startInfo = new TestProcessStartInfo();
// .NET core host manager is not a shared host. It will expect a single test source to be provided.
@@ -233,9 +257,15 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo(
// Try find testhost.exe (or the architecture specific version). We ship those ngened executables for Windows because they have faster startup time. We ship them only for some platforms.
// When user specified path to dotnet.exe don't try to find the exexutable, because we will always use the testhost.dll together with their dotnet.exe.
+ // We use dotnet.exe on Windows/ARM.
+ // TODO: Check if we're on ARM64 win using env var PROCESSARCHITECTURE
bool testHostExeFound = false;
if (!useCustomDotnetHostpath
- && this.platformEnvironment.OperatingSystem.Equals(PlatformOperatingSystem.Windows))
+ && this.platformEnvironment.OperatingSystem.Equals(PlatformOperatingSystem.Windows)
+
+ // testhost*.exe are build for netcoreapp2.1 and are not able to search for the correct runtime in case of x64/x86 on arm because the new logic(registry lookup)
+ // was added in since netcoreapp3.0. On arm we cannot rely on apphost and we'll use dotnet.exe muxer
+ && !IsWinOnArm())
{
// testhost.exe is 64-bit and has no suffix other versions have architecture suffix.
var exeName = this.architecture == Architecture.X64 || this.architecture == Architecture.Default || this.architecture == Architecture.AnyCPU
@@ -374,30 +404,44 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo(
throw new TestPlatformException("Could not find testhost");
}
+ // We silently force x64 only if the target architecture is the default one and is not specified by user
+ // through --arch or runsettings or -- RunConfiguration.TargetPlatform=arch
+ bool forceToX64 = SilentlyForceToX64() && this.runsettingHelper.IsDefaultTargetArchitecture;
+ bool isSameArchitecture = IsSameArchitecture(this.architecture, this.platformEnvironment.Architecture);
var currentProcessPath = this.processHelper.GetCurrentProcessFileName();
+ bool isRunningWithDotnetMuxer = IsRunningWithDotnetMuxer(currentProcessPath);
if (useCustomDotnetHostpath)
{
startInfo.FileName = this.dotnetHostPath;
}
- // If already running with the dotnet executable, use it; otherwise pick up the dotnet available on path.
- //
- // This allows us to pick up dotnet even when it is not present on PATH, or when we are running in custom
- // portable installation, and DOTNET_ROOT is overridden and MULTILEVEL_LOOKUP is set to 0, which would
- // normally prevent us from finding the dotnet executable.
- //
- // Wrap the paths with quotes in case dotnet executable is installed on a path with whitespace.
- else if (currentProcessPath.EndsWith("dotnet", StringComparison.OrdinalIgnoreCase)
- || currentProcessPath.EndsWith("dotnet.exe", StringComparison.OrdinalIgnoreCase))
+ // If already running with the dotnet executable and the architecture is compatible, use it; otherwise search the correct muxer architecture on disk.
+ else if (isRunningWithDotnetMuxer && isSameArchitecture && !forceToX64)
{
+ EqtTrace.Verbose("DotnetTestHostmanager.LaunchTestHostAsync: Compatible muxer architecture of running process '{0}'", this.platformEnvironment.Architecture);
startInfo.FileName = currentProcessPath;
}
else
{
- startInfo.FileName = this.dotnetHostHelper.GetDotnetPath();
+ PlatformArchitecture targetArchitecture = TranslateToPlatformArchitecture(this.architecture);
+ EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Searching muxer for the architecture '{targetArchitecture}', OS '{this.platformEnvironment.OperatingSystem}' framework '{this.targetFramework}' SDK platform architecture '{this.platformEnvironment.Architecture}'");
+ if (forceToX64)
+ {
+ EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Forcing the search to x64 architecure, IsDefaultTargetArchitecture '{this.runsettingHelper.IsDefaultTargetArchitecture}' OS '{this.platformEnvironment.OperatingSystem}' framework '{this.targetFramework}'");
+ }
+
+ PlatformArchitecture finalTargetArchitecture = forceToX64 ? PlatformArchitecture.X64 : targetArchitecture;
+ if (!this.dotnetHostHelper.TryGetDotnetPathByArchitecture(finalTargetArchitecture, out string muxerPath))
+ {
+ string message = string.Format(Resources.NoDotnetMuxerFoundForArchitecture, $"dotnet{(this.platformEnvironment.OperatingSystem == PlatformOperatingSystem.Windows ? ".exe" : string.Empty)}", finalTargetArchitecture.ToString());
+ EqtTrace.Error(message);
+ throw new TestPlatformException(message);
+ }
+
+ startInfo.FileName = muxerPath;
}
- EqtTrace.Verbose("DotnetTestHostmanager: Full path of testhost.dll is {0}", testHostPath);
+ EqtTrace.Verbose("DotnetTestHostmanager.LaunchTestHostAsync: Full path of testhost.dll is {0}", testHostPath);
args = "exec" + args;
args += " " + testHostPath.AddDoubleQuote();
}
@@ -419,9 +463,132 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo(
// G:\tmp\netcore-test\bin\Debug\netcoreapp1.0\netcore-test.dll
startInfo.Arguments = args;
startInfo.EnvironmentVariables = environmentVariables ?? new Dictionary();
+
+ // If we're running using custom apphost we need to set DOTNET_ROOT/DOTNET_ROOT(x86)
+ // We're setting it inside SDK to support private install scenario.
+ // i.e. I've got only private install and no global installation, in this case apphost needs to use env var to locate runtime.
+ if (testHostExeFound)
+ {
+ string prefix = "VSTEST_WINAPPHOST_";
+ string dotnetRootEnvName = $"{prefix}DOTNET_ROOT(x86)";
+ var dotnetRoot = this.environmentVariableHelper.GetEnvironmentVariable(dotnetRootEnvName);
+ if (dotnetRoot is null)
+ {
+ dotnetRootEnvName = $"{prefix}DOTNET_ROOT";
+ dotnetRoot = this.environmentVariableHelper.GetEnvironmentVariable(dotnetRootEnvName);
+ }
+
+ if (dotnetRoot != null)
+ {
+ EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Found '{dotnetRootEnvName}' in env variables, value '{dotnetRoot}'");
+ startInfo.EnvironmentVariables.Add(dotnetRootEnvName.Replace(prefix, string.Empty), dotnetRoot);
+ }
+ else
+ {
+ EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Prefix '{prefix}*' not found in env variables");
+ }
+ }
+
startInfo.WorkingDirectory = sourceDirectory;
return startInfo;
+
+ bool IsRunningWithDotnetMuxer(string currentProcessPath)
+ => currentProcessPath.EndsWith("dotnet", StringComparison.OrdinalIgnoreCase) ||
+ currentProcessPath.EndsWith("dotnet.exe", StringComparison.OrdinalIgnoreCase);
+
+ bool IsWinOnArm()
+ {
+ bool isWinOnArm = false;
+ if (this.platformEnvironment.OperatingSystem == PlatformOperatingSystem.Windows)
+ {
+ using (IRegistryKey hklm = this.windowsRegistryHelper.OpenBaseKey(RegistryHive.LocalMachine, Environment.Is64BitProcess ? RegistryView.Registry64 : RegistryView.Registry32))
+ {
+ if (hklm != null)
+ {
+ using (IRegistryKey environment = hklm.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Session Manager\Environment"))
+ {
+ if (environment != null)
+ {
+ var processorArchitecture = environment.GetValue("PROCESSOR_ARCHITECTURE");
+ if (processorArchitecture != null)
+ {
+ EqtTrace.Verbose($"DotnetTestHostmanager.IsWinOnArm: Current PROCESSOR_ARCHITECTURE from registry '{processorArchitecture}'");
+ isWinOnArm = processorArchitecture.ToString().ToLowerInvariant().Contains("arm");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ EqtTrace.Verbose($"DotnetTestHostmanager.IsWinOnArm: Is Windows on ARM '{isWinOnArm}'");
+ return isWinOnArm;
+ }
+
+ PlatformArchitecture TranslateToPlatformArchitecture(Architecture targetArchitecture)
+ {
+ switch (targetArchitecture)
+ {
+ case Architecture.X86:
+ return PlatformArchitecture.X86;
+ case Architecture.X64:
+ return PlatformArchitecture.X64;
+ case Architecture.ARM:
+ return PlatformArchitecture.ARM;
+ case Architecture.ARM64:
+ return PlatformArchitecture.ARM64;
+ case Architecture.AnyCPU:
+ case Architecture.Default:
+ default:
+ break;
+ }
+
+ throw new TestPlatformException($"Invalid target architecture '{targetArchitecture}'");
+ }
+
+ bool IsSameArchitecture(Architecture targetArchitecture, PlatformArchitecture platformAchitecture)
+ {
+ switch (targetArchitecture)
+ {
+ case Architecture.X86:
+ return platformAchitecture == PlatformArchitecture.X86;
+ case Architecture.X64:
+ return platformAchitecture == PlatformArchitecture.X64;
+ case Architecture.ARM:
+ return platformAchitecture == PlatformArchitecture.ARM;
+ case Architecture.ARM64:
+ return platformAchitecture == PlatformArchitecture.ARM64;
+ case Architecture.AnyCPU:
+ case Architecture.Default:
+ default:
+ throw new TestPlatformException($"Invalid target architecture '{targetArchitecture}'");
+ }
+ }
+
+ bool SilentlyForceToX64()
+ {
+ // We need to force x64 in some scenario
+ // https://github.com/dotnet/sdk/blob/main/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.RuntimeIdentifierInference.targets#L140-L143
+
+ // If we are running on an M1 with a native SDK and the TFM is < 6.0, we have to use a x64 apphost since there are no osx-arm64 apphosts previous to .NET 6.0.
+ if (this.platformEnvironment.OperatingSystem == PlatformOperatingSystem.OSX &&
+ this.platformEnvironment.Architecture == PlatformArchitecture.ARM64 &&
+ new Version(this.targetFramework.Version).Major < 6)
+ {
+ return true;
+ }
+
+ // If we are running on win-arm64 and the TFM is < 5.0, we have to use a x64 apphost since there are no win-arm64 apphosts previous to .NET 5.0.
+ if (this.platformEnvironment.OperatingSystem == PlatformOperatingSystem.Windows &&
+ this.platformEnvironment.Architecture == PlatformArchitecture.ARM64 &&
+ new Version(this.targetFramework.Version).Major < 5)
+ {
+ return true;
+ }
+
+ return false;
+ }
}
///
diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.Designer.cs b/src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.Designer.cs
index 1a7c2c8f52..746b193bc5 100644
--- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.Designer.cs
+++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.Designer.cs
@@ -88,5 +88,17 @@ internal static string SuggestPublishTestProject {
return ResourceManager.GetString("SuggestPublishTestProject", resourceCulture);
}
}
+
+ ///
+ /// '{0}' is the placeholder for 'dotnet.exe' or 'dotnet' value and depends on platform Windows/Unix, '{1}' is the placeholder for the architeture name like ARM64, X64 etc...
+ ///
+ internal static string NoDotnetMuxerFoundForArchitecture
+ {
+ get
+ {
+ return ResourceManager.GetString("NoDotnetMuxerFoundForArchitecture", resourceCulture);
+ }
+ }
+
}
}
diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.resx b/src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.resx
index a037ce6a01..8ea0055bda 100644
--- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.resx
+++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.resx
@@ -127,4 +127,14 @@
Unable to find {0}. Please publish your test project and retry.
+
+ Could not find '{0}' host for the '{1}' architecture.
+
+You can resolve the problem by installing the '{1}' .NET.
+
+The specified framework can be found at:
+ - https://aka.ms/dotnet-download
+
+ '{0}' is the placeholder for 'dotnet.exe' or 'dotnet' value and depends on platform Windows/Unix, '{1}' is the placeholder for the architeture name like ARM64, X64 etc...
+
\ No newline at end of file
diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.cs.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.cs.xlf
index 52a857de0e..539017944b 100644
--- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.cs.xlf
+++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.cs.xlf
@@ -19,6 +19,17 @@
Nejde najít {0}. Publikujte prosím svůj testovací projekt a zkuste to znovu.
+
+ Could not find '{0}' host for the '{1}' architecture.
+
+You can resolve the problem by installing the '{1}' .NET.
+
+The specified framework can be found at:
+ - https://aka.ms/dotnet-download
+
+ Could not find '{0}' host for the '{1}' architecture. Make sure that '{0}' for '{1}' architecture is installed on the machine.
+ '{0}' is the placeholder for 'dotnet.exe' or 'dotnet' value and depends on platform Windows/Unix, '{1}' is the placeholder for the architeture name like ARM64, X64 etc...
+