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... + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.de.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.de.xlf index 100490cade..4ca37746c5 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.de.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.de.xlf @@ -19,6 +19,17 @@ "{0}" wurde nicht gefunden. Veröffentlichen Sie Ihr Testprojekt, und versuchen Sie es noch mal. + + 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... + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.es.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.es.xlf index 1cfe87db50..6bdbfe5995 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.es.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.es.xlf @@ -19,6 +19,17 @@ No se encuentra {0}. Publique el proyecto de prueba y vuelva a intentarlo. + + 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... + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.fr.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.fr.xlf index c041cd085f..bc525648c2 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.fr.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.fr.xlf @@ -19,6 +19,17 @@ {0} est introuvable. Publiez votre projet de test et réessayez. + + 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... + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.it.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.it.xlf index 42d74885e5..61766a3181 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.it.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.it.xlf @@ -19,6 +19,17 @@ Non è possibile trovare {0}. Pubblicare il progetto di test e riprovare. + + 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... + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ja.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ja.xlf index 62108eca0b..e83dae787a 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ja.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ja.xlf @@ -19,6 +19,17 @@ {0} を見つけることができません。テスト プロジェクトを公開してもう一度お試しください。 + + 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... + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ko.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ko.xlf index d8e7d172d6..257830a418 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ko.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ko.xlf @@ -19,6 +19,17 @@ {0}을(를) 찾을 수 없습니다. 테스트 프로젝트를 게시하고 다시 시도하세요. + + 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... + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pl.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pl.xlf index 39b704e462..3e1628d4ca 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pl.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pl.xlf @@ -19,6 +19,17 @@ Nie można znaleźć elementu {0}. Opublikuj swój projekt testowy i spróbuj ponownie. + + 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... + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pt-BR.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pt-BR.xlf index 1944fd7115..68eb1715c1 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pt-BR.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pt-BR.xlf @@ -19,6 +19,17 @@ Não foi possível localizar {0}. Publique o projeto de teste e tente novamente. + + 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... + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ru.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ru.xlf index 4d098564ee..8ad08fab78 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ru.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ru.xlf @@ -19,6 +19,17 @@ Не удалось найти {0}. Опубликуйте тестовый проект и повторите попытку. + + 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... + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.tr.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.tr.xlf index afbaa599f0..1c166812ef 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.tr.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.tr.xlf @@ -19,6 +19,17 @@ {0} bulunamıyor. Lütfen test projenizi yayımlayın ve yeniden deneyin. + + 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... + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.xlf index 1196112a99..5e5ceea8b4 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.xlf @@ -19,6 +19,17 @@ 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 + + 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... + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hans.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hans.xlf index 0b0a602375..03b1f6d28c 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hans.xlf @@ -19,6 +19,17 @@ 无法找到 {0}。请发布测试项目并重试。 + + 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... + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hant.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hant.xlf index 02e48d5a16..f76542eee1 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hant.xlf @@ -19,6 +19,17 @@ 找不到 {0}。請發佈您的測試專案並重試。 + + 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... + \ No newline at end of file diff --git a/src/package/nuspec/TestPlatform.TestHost.nuspec b/src/package/nuspec/TestPlatform.TestHost.nuspec index 86a4edea10..fd9c485842 100644 --- a/src/package/nuspec/TestPlatform.TestHost.nuspec +++ b/src/package/nuspec/TestPlatform.TestHost.nuspec @@ -84,7 +84,18 @@ - + + diff --git a/src/vstest.console/CommandLine/AssemblyMetadataProvider.cs b/src/vstest.console/CommandLine/AssemblyMetadataProvider.cs index 8ed3b90b8f..2424097983 100644 --- a/src/vstest.console/CommandLine/AssemblyMetadataProvider.cs +++ b/src/vstest.console/CommandLine/AssemblyMetadataProvider.cs @@ -61,7 +61,7 @@ public Architecture GetArchitecture(string assemblyPath) { // AssemblyName won't load the assembly into current domain. var assemblyName = AssemblyName.GetAssemblyName(assemblyPath); - archType = MapToArchitecture(assemblyName.ProcessorArchitecture); + archType = MapToArchitecture(assemblyName.ProcessorArchitecture, assemblyPath); } catch (Exception ex) { @@ -94,6 +94,32 @@ public Architecture GetArchitecture(string assemblyPath) return archType; } + private Architecture GetArchitectureFromAssemblyMetadata(string path) + { + Architecture arch = Architecture.AnyCPU; + 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 Architecture.X64; + case Machine.IA64: + return Architecture.X64; + case Machine.Arm64: + return Architecture.ARM64; + case Machine.Arm: + return Architecture.ARM; + case Machine.I386: + return Architecture.X86; + default: + break; + } + } + + return arch; + } + private static FrameworkName GetFrameworkNameFromAssemblyMetadata(Stream assemblyStream) { FrameworkName frameworkName = new FrameworkName(Framework.DefaultFramework.Name); @@ -123,7 +149,7 @@ private static FrameworkName GetFrameworkNameFromAssemblyMetadata(Stream assembl return frameworkName; } - private Architecture MapToArchitecture(ProcessorArchitecture processorArchitecture) + private Architecture MapToArchitecture(ProcessorArchitecture processorArchitecture, string assemblyPath) { Architecture arch = Architecture.AnyCPU; // Mapping to Architecture based on https://msdn.microsoft.com/en-us/library/system.reflection.processorarchitecture(v=vs.110).aspx @@ -136,13 +162,24 @@ private Architecture MapToArchitecture(ProcessorArchitecture processorArchitectu else if (processorArchitecture.Equals(ProcessorArchitecture.X86)) { arch = Architecture.X86; - }else if (processorArchitecture.Equals(ProcessorArchitecture.MSIL)) + } + else if (processorArchitecture.Equals(ProcessorArchitecture.MSIL)) { arch = Architecture.AnyCPU; - }else if (processorArchitecture.Equals(ProcessorArchitecture.Arm)) + } + else if (processorArchitecture.Equals(ProcessorArchitecture.Arm)) { arch = Architecture.ARM; } + else if (processorArchitecture.Equals(ProcessorArchitecture.None)) + { + // In case of None we fallback to PEReader + // We don't use only PEReader for back compatibility. + // An AnyCPU returned by AssemblyName.GetAssemblyName(assemblyPath) will result in a I386 for PEReader. + // Also MSIL processor architecture is missing with PEReader. + // For now it should fix the issue for the missing ARM64 architecture. + arch = GetArchitectureFromAssemblyMetadata(assemblyPath); + } else { EqtTrace.Info("Unable to map to Architecture, using platform: {0}", arch); diff --git a/src/vstest.console/CommandLine/InferHelper.cs b/src/vstest.console/CommandLine/InferHelper.cs index a6f10552fb..5b0d6c4835 100644 --- a/src/vstest.console/CommandLine/InferHelper.cs +++ b/src/vstest.console/CommandLine/InferHelper.cs @@ -43,7 +43,8 @@ public Architecture AutoDetectArchitecture(IList sources, IDictionary /// The argument processor for runsettings passed as argument through cli @@ -139,14 +140,15 @@ private void CreateOrOverwriteRunSettings(IRunSettingsProvider runSettingsProvid var mergedArgs = new List(); var mergedArg = string.Empty; var merge = false; - + foreach (var arg in args) { // when we see that the parameter begins with TestRunParameters // but does not end with ") we start merging the params if (arg.StartsWith("TestRunParameters", StringComparison.OrdinalIgnoreCase)) { - if (arg.EndsWith("\")")) { + if (arg.EndsWith("\")")) + { // this parameter is complete mergedArgs.Add(arg); } @@ -170,7 +172,8 @@ private void CreateOrOverwriteRunSettings(IRunSettingsProvider runSettingsProvid } // once we detect the end we add the whole parameter to the args - if (merge && arg.EndsWith("\")")) { + if (merge && arg.EndsWith("\")")) + { mergedArgs.Add(mergedArg); mergedArg = string.Empty; merge = false; @@ -184,7 +187,7 @@ private void CreateOrOverwriteRunSettings(IRunSettingsProvider runSettingsProvid mergedArgs.Add(mergedArg); } - + var length = mergedArgs.Count; for (int index = 0; index < length; index++) @@ -253,6 +256,7 @@ private void UpdateFrameworkAndPlatform(string key, string value) bool success = Enum.TryParse(value, true, out var architecture); if (success) { + RunSettingsHelper.Instance.IsDefaultTargetArchitecture = false; this.commandLineOptions.TargetArchitecture = architecture; } } diff --git a/src/vstest.console/Processors/PlatformArgumentProcessor.cs b/src/vstest.console/Processors/PlatformArgumentProcessor.cs index 0bfd164ecb..0bc418de47 100644 --- a/src/vstest.console/Processors/PlatformArgumentProcessor.cs +++ b/src/vstest.console/Processors/PlatformArgumentProcessor.cs @@ -11,6 +11,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.Common.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; using CommandLineResources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources; /// @@ -149,6 +150,7 @@ public void Initialize(string argument) if (validPlatform) { + RunSettingsHelper.Instance.IsDefaultTargetArchitecture = false; this.commandLineOptions.TargetArchitecture = platform; this.runSettingsManager.UpdateRunSettingsNode(PlatformArgumentExecutor.RunSettingsPath, platform.ToString()); } diff --git a/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs b/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs index ce59e7fc13..b06ef8abf8 100644 --- a/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs +++ b/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs @@ -14,6 +14,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.Common.Utilities; + using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; using Microsoft.VisualStudio.TestPlatform.Utilities; @@ -169,6 +170,7 @@ private void ExtractFrameworkAndPlatform() var platformStr = this.runSettingsManager.QueryRunSettingsNode(PlatformArgumentExecutor.RunSettingsPath); if (Enum.TryParse(platformStr, true, out var architecture)) { + RunSettingsHelper.Instance.IsDefaultTargetArchitecture = false; this.commandLineOptions.TargetArchitecture = architecture; } } diff --git a/src/vstest.console/Program.cs b/src/vstest.console/Program.cs index 3628526f18..682aca98f2 100644 --- a/src/vstest.console/Program.cs +++ b/src/vstest.console/Program.cs @@ -3,6 +3,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine { + using System; using Microsoft.VisualStudio.TestPlatform.Execution; using Microsoft.VisualStudio.TestPlatform.Utilities; @@ -19,8 +20,47 @@ public static class Program public static int Main(string[] args) { DebuggerBreakpoint.WaitForDebugger("VSTEST_RUNNER_DEBUG"); + ForwardDotnetRootEnvVarToNewVersion(); UILanguageOverride.SetCultureSpecifiedByUser(); return new Executor(ConsoleOutput.Instance).Execute(args); } + + /// + /// Forwarding of DOTNET_ROOT/DOTNET_ROOT(x86) env vars populted by SDK, this is needed to allow to --arch feature to work + /// as expected. If we use old SDK and new TP it won't work without env vars forwarding. + /// + private static void ForwardDotnetRootEnvVarToNewVersion() + { + // TODO: remove this and the console writes before publishing + var switchVars = Environment.GetEnvironmentVariable("VSTEST_TMP_SWITCH_DOTNETROOTS_ENVVARS"); + if (switchVars != null && int.Parse(switchVars) == 1) + { + var dotnetRoot = Environment.GetEnvironmentVariable("DOTNET_ROOT"); + if (dotnetRoot != null) + { +# if DEBUG + Console.WriteLine($"Forwarding DOTNET_ROOT to VSTEST_WINAPPHOST_DOTNET_ROOT '{dotnetRoot}'"); +#endif + Environment.SetEnvironmentVariable("DOTNET_ROOT", null); + Environment.SetEnvironmentVariable("VSTEST_WINAPPHOST_DOTNET_ROOT", dotnetRoot); +#if DEBUG + Console.WriteLine($"Current DOTNET_ROOT '{Environment.GetEnvironmentVariable("DOTNET_ROOT")}'"); +#endif + } + + var dotnetRootX86 = Environment.GetEnvironmentVariable("DOTNET_ROOT(x86)"); + if(dotnetRootX86 != null) + { +#if DEBUG + Console.WriteLine($"Forwarding DOTNET_ROOT(x86) to VSTEST_WINAPPHOST_DOTNET_ROOT(x86) '{dotnetRootX86}'"); +#endif + Environment.SetEnvironmentVariable("DOTNET_ROOT(x86)", null); + Environment.SetEnvironmentVariable("VSTEST_WINAPPHOST_DOTNET_ROOT(x86)", dotnetRootX86); +#if DEBUG + Console.WriteLine($"Current DOTNET_ROOT(x86) '{Environment.GetEnvironmentVariable("DOTNET_ROOT(x86)")}'"); +#endif + } + } + } } } diff --git a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs index 8d4b377913..92ce83acaa 100644 --- a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs +++ b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs @@ -602,6 +602,7 @@ private bool UpdateRunSettingsIfRequired( Architecture defaultArchitecture = Architecture.X86; if (chosenFramework.Name.IndexOf("netstandard", StringComparison.OrdinalIgnoreCase) >= 0 || chosenFramework.Name.IndexOf("netcoreapp", StringComparison.OrdinalIgnoreCase) >= 0 + // This is a special case for 1 version of Nuget.Frameworks that was shipped with using identifier NET5 instead of NETCoreApp5 for .NET 5. || chosenFramework.Name.IndexOf("net5", StringComparison.OrdinalIgnoreCase) >= 0) { #if NETCOREAPP @@ -609,7 +610,12 @@ private bool UpdateRunSettingsIfRequired( // or via vstest.console.exe .NET Core executable. For AnyCPU dlls this // should resolve 32-bit SDK when running from 32-bit dotnet process and // 64-bit SDK when running from 64-bit dotnet process. - defaultArchitecture = Environment.Is64BitProcess ? Architecture.X64 : Architecture.X86; + // As default architecture we specify the expected test host architecture, + // it can be specified by user on the command line with --arch or through runsettings. + // If it's not specified by user will be filled by current processor architecture; + // should be the same as SDK. + defaultArchitecture = runConfiguration.TargetPlatform; + EqtTrace.Verbose($"Default architecture: {defaultArchitecture}"); #else // We are running in vstest.console.exe that was built against .NET // Framework. This console prefers 32-bit because it needs to run as 32-bit @@ -757,6 +763,8 @@ private bool UpdatePlatform( sourcePlatforms, defaultArchitecture); + EqtTrace.Info($"Infered platform '{inferedPlatform}'."); + // Get platform from runsettings. bool updatePlatform = IsAutoPlatformDetectRequired(navigator, out chosenPlatform); @@ -764,6 +772,7 @@ private bool UpdatePlatform( // ArgumentProcessor. if (updatePlatform) { + EqtTrace.Info($"Platform update to '{inferedPlatform}' required."); InferRunSettingsHelper.UpdateTargetPlatform( document, inferedPlatform.ToString(), diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/ExecutionTests.cs b/test/Microsoft.TestPlatform.AcceptanceTests/ExecutionTests.cs index 48cd16fbf2..e09721e8d9 100644 --- a/test/Microsoft.TestPlatform.AcceptanceTests/ExecutionTests.cs +++ b/test/Microsoft.TestPlatform.AcceptanceTests/ExecutionTests.cs @@ -49,6 +49,9 @@ public void RunMultipleTestAssembliesWithoutTestAdapterPath(RunnerInfo runnerInf this.ExitCodeEquals(1); // failing tests } + // We cannot run this test on Mac/Linux because we're trying to switch the arch between x64 and x86 + // and after --arch feature implementation we won't find correct muxer on CI. + [TestCategory("Windows")] [TestMethod] [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Helpers/DotnetHostHelperTest.cs b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Helpers/DotnetHostHelperTest.cs new file mode 100644 index 0000000000..10e27fa429 --- /dev/null +++ b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Helpers/DotnetHostHelperTest.cs @@ -0,0 +1,354 @@ +// 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.TestPlatform.CoreUtilities.UnitTests.Helpers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers; + using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; + using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.Win32; + using Moq; + + [TestClass] + public class DotnetHostHelperTest : IDisposable + { + private readonly Mock fileHelper = new Mock(); + private readonly Mock processHelper = new Mock(); + private readonly Mock environmentHelper = new Mock(); + private readonly Mock windowsRegistrytHelper = new Mock(); + private readonly Mock environmentVariableHelper = new Mock(); + private readonly MockMuxerHelper muxerHelper = new MockMuxerHelper(); + + [TestMethod] + public void GetDotnetPathByArchitecture_SameArchitecture() + { + // Arrange + string finalMuxerPath = muxerHelper.RenameMuxerAndReturnPath(PlatformOperatingSystem.Windows, PlatformArchitecture.X64); + var dotnetHostHelper = new DotnetHostHelper(fileHelper.Object, environmentHelper.Object, windowsRegistrytHelper.Object, environmentVariableHelper.Object, processHelper.Object); + environmentHelper.SetupGet(x => x.OperatingSystem).Returns(PlatformOperatingSystem.Windows); + environmentHelper.SetupGet(x => x.Architecture).Returns(PlatformArchitecture.X64); + processHelper.Setup(x => x.GetCurrentProcessFileName()).Returns(finalMuxerPath); + + // Act & Assert + Assert.IsTrue(dotnetHostHelper.TryGetDotnetPathByArchitecture(PlatformArchitecture.X64, out string muxerPath)); + Assert.AreEqual(finalMuxerPath, muxerPath); + } + + [DataTestMethod] + [DataRow(PlatformArchitecture.X86, PlatformArchitecture.X64, PlatformOperatingSystem.Windows, "DOTNET_ROOT(x86)")] + [DataRow(PlatformArchitecture.X86, PlatformArchitecture.X64, PlatformOperatingSystem.Windows, "DOTNET_ROOT")] + + [DataRow(PlatformArchitecture.ARM64, PlatformArchitecture.X64, PlatformOperatingSystem.Windows, "DOTNET_ROOT(x86)", false)] + [DataRow(PlatformArchitecture.ARM64, PlatformArchitecture.X64, PlatformOperatingSystem.Windows, "DOTNET_ROOT_ARM64")] + [DataRow(PlatformArchitecture.ARM64, PlatformArchitecture.X64, PlatformOperatingSystem.Windows, "DOTNET_ROOT")] + + [DataRow(PlatformArchitecture.X64, PlatformArchitecture.X64, PlatformOperatingSystem.Windows, "DOTNET_ROOT(x86)", false)] + [DataRow(PlatformArchitecture.X64, PlatformArchitecture.ARM64, PlatformOperatingSystem.Windows, "DOTNET_ROOT_X64")] + [DataRow(PlatformArchitecture.X64, PlatformArchitecture.ARM64, PlatformOperatingSystem.Windows, "DOTNET_ROOT")] + + [DataRow(PlatformArchitecture.ARM64, PlatformArchitecture.X64, PlatformOperatingSystem.OSX, "DOTNET_ROOT_ARM64")] + [DataRow(PlatformArchitecture.ARM64, PlatformArchitecture.X64, PlatformOperatingSystem.OSX, "DOTNET_ROOT")] + + [DataRow(PlatformArchitecture.X64, PlatformArchitecture.ARM64, PlatformOperatingSystem.OSX, "DOTNET_ROOT_X64")] + [DataRow(PlatformArchitecture.X64, PlatformArchitecture.ARM64, PlatformOperatingSystem.OSX, "DOTNET_ROOT")] + public void GetDotnetPathByArchitecture_EnvVars(PlatformArchitecture targetArchitecture, + PlatformArchitecture platformArchitecture, + PlatformOperatingSystem platformSystem, + string envVar, + bool found = true) + { + // Arrange + string DOTNET_ROOT_X64 = muxerHelper.RenameMuxerAndReturnPath(platformSystem, PlatformArchitecture.X64); + string DOTNET_ROOT_ARM64 = muxerHelper.RenameMuxerAndReturnPath(platformSystem, PlatformArchitecture.ARM64); + string DOTNET_ROOT_X86 = null; + if (platformSystem == PlatformOperatingSystem.Windows) + { + DOTNET_ROOT_X86 = muxerHelper.RenameMuxerAndReturnPath(platformSystem, PlatformArchitecture.X86); + } + string DOTNET_ROOT = muxerHelper.RenameMuxerAndReturnPath(platformSystem, targetArchitecture); + Dictionary envVars = new Dictionary() + { + { "DOTNET_ROOT_X64" , DOTNET_ROOT_X64}, + { "DOTNET_ROOT_ARM64" , DOTNET_ROOT_ARM64}, + { "DOTNET_ROOT(x86)" , DOTNET_ROOT_X86}, + { "DOTNET_ROOT" , DOTNET_ROOT}, + }; + + environmentHelper.SetupGet(x => x.Architecture).Returns(platformArchitecture); + environmentHelper.SetupGet(x => x.OperatingSystem).Returns(platformSystem); + environmentVariableHelper.Setup(x => x.GetEnvironmentVariable(envVar)).Returns(Path.GetDirectoryName(envVars[envVar])); + environmentVariableHelper.Setup(x => x.GetEnvironmentVariable("ProgramFiles")).Returns("notfound"); + fileHelper.Setup(x => x.DirectoryExists(Path.GetDirectoryName(envVars[envVar]))).Returns(true); + fileHelper.Setup(x => x.Exists(envVars[envVar])).Returns(true); + if (found) + { + fileHelper.Setup(x => x.GetStream(envVars[envVar], FileMode.Open, FileAccess.Read)).Returns(File.OpenRead(envVars[envVar])); + } + + // Act & Assert + var dotnetHostHelper = new DotnetHostHelper(fileHelper.Object, environmentHelper.Object, windowsRegistrytHelper.Object, environmentVariableHelper.Object, processHelper.Object); + Assert.AreEqual(found, dotnetHostHelper.TryGetDotnetPathByArchitecture(targetArchitecture, out string muxerPath)); + Assert.AreEqual(found ? envVars[envVar] : null, muxerPath); + } + + [DataTestMethod] + [DataRow("DOTNET_ROOT_ARM64", "DOTNET_ROOT", PlatformArchitecture.ARM64, PlatformArchitecture.X64)] + [DataRow("DOTNET_ROOT(x86)", "DOTNET_ROOT", PlatformArchitecture.X86, PlatformArchitecture.X64)] + public void GetDotnetPathByArchitecture_EnvVars_DirectoryNotExists_TryNext(string notExists, string nextEnv, PlatformArchitecture targetAchitecture, PlatformArchitecture platformArchitecture) + { + // Arrange + string DOTNET_ROOT_X64 = muxerHelper.RenameMuxerAndReturnPath(PlatformOperatingSystem.Windows, PlatformArchitecture.X64); + string DOTNET_ROOT_ARM64 = muxerHelper.RenameMuxerAndReturnPath(PlatformOperatingSystem.Windows, PlatformArchitecture.ARM64); + string DOTNET_ROOT_X86 = muxerHelper.RenameMuxerAndReturnPath(PlatformOperatingSystem.Windows, PlatformArchitecture.X86); + string DOTNET_ROOT = muxerHelper.RenameMuxerAndReturnPath(PlatformOperatingSystem.Windows, targetAchitecture); + Dictionary envVars = new Dictionary() + { + { "DOTNET_ROOT_X64" , DOTNET_ROOT_X64}, + { "DOTNET_ROOT_ARM64" , DOTNET_ROOT_ARM64}, + { "DOTNET_ROOT(x86)" , DOTNET_ROOT_X86}, + { "DOTNET_ROOT" , DOTNET_ROOT}, + }; + + environmentHelper.SetupGet(x => x.Architecture).Returns(platformArchitecture); + environmentHelper.SetupGet(x => x.OperatingSystem).Returns(PlatformOperatingSystem.Windows); + environmentVariableHelper.Setup(x => x.GetEnvironmentVariable(notExists)).Returns(Path.GetDirectoryName(envVars[notExists])); + environmentVariableHelper.Setup(x => x.GetEnvironmentVariable(nextEnv)).Returns(Path.GetDirectoryName(envVars[nextEnv])); + fileHelper.Setup(x => x.DirectoryExists(Path.GetDirectoryName(envVars[nextEnv]))).Returns(true); + fileHelper.Setup(x => x.Exists(envVars[nextEnv])).Returns(true); + fileHelper.Setup(x => x.GetStream(envVars[nextEnv], FileMode.Open, FileAccess.Read)).Returns(File.OpenRead(envVars[nextEnv])); + + //Act & Assert + var dotnetHostHelper = new DotnetHostHelper(fileHelper.Object, environmentHelper.Object, windowsRegistrytHelper.Object, environmentVariableHelper.Object, processHelper.Object); + Assert.IsTrue(dotnetHostHelper.TryGetDotnetPathByArchitecture(targetAchitecture, out string muxerPath)); + Assert.AreEqual(envVars[nextEnv], muxerPath); + } + + [DataTestMethod] + [DataRow(PlatformArchitecture.X64, PlatformArchitecture.X64, true)] + [DataRow(PlatformArchitecture.X64, PlatformArchitecture.X86, false)] + public void GetDotnetPathByArchitecture_GlobalInstallation_Windows(PlatformArchitecture muxerArchitecture, PlatformArchitecture targetArchitecture, bool found) + { + // Arrange + string dotnetMuxer = muxerHelper.RenameMuxerAndReturnPath(PlatformOperatingSystem.Windows, muxerArchitecture); + Mock installedVersionKey = new Mock(); + Mock architectureSubKey = new Mock(); + Mock nativeArchSubKey = new Mock(); + installedVersionKey.Setup(x => x.OpenSubKey(It.IsAny())).Returns(architectureSubKey.Object); + architectureSubKey.Setup(x => x.OpenSubKey(It.IsAny())).Returns(nativeArchSubKey.Object); + nativeArchSubKey.Setup(x => x.GetValue(It.IsAny())).Returns(Path.GetDirectoryName(dotnetMuxer)); + this.windowsRegistrytHelper.Setup(x => x.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)).Returns(installedVersionKey.Object); + this.fileHelper.Setup(x => x.Exists(dotnetMuxer)).Returns(true); + this.fileHelper.Setup(x => x.GetStream(dotnetMuxer, FileMode.Open, FileAccess.Read)).Returns(File.OpenRead(dotnetMuxer)); + + //Act & Assert + var dotnetHostHelper = new DotnetHostHelper(fileHelper.Object, environmentHelper.Object, windowsRegistrytHelper.Object, environmentVariableHelper.Object, processHelper.Object); + Assert.AreEqual(found, dotnetHostHelper.TryGetDotnetPathByArchitecture(targetArchitecture, out string muxerPath)); + Assert.AreEqual(found ? dotnetMuxer : null, muxerPath); + } + + [DataTestMethod] + [DataRow(true, false, false, false)] + [DataRow(false, true, false, false)] + [DataRow(false, false, true, false)] + [DataRow(false, false, false, true)] + public void GetDotnetPathByArchitecture_GlobalInstallation_NullSubkeys(bool nullInstalledVersion, bool nullArchitecture, bool nullNative, bool nullInstallLocation) + { + // Arrange + Mock installedVersionKey = new Mock(); + Mock architectureSubKey = new Mock(); + Mock nativeArchSubKey = new Mock(); + installedVersionKey.Setup(x => x.OpenSubKey(It.IsAny())).Returns(nullArchitecture ? null : architectureSubKey.Object); + architectureSubKey.Setup(x => x.OpenSubKey(It.IsAny())).Returns(nullNative ? null : nativeArchSubKey.Object); + nativeArchSubKey.Setup(x => x.GetValue(It.IsAny())).Returns(nullInstallLocation ? null : ""); + this.windowsRegistrytHelper.Setup(x => x.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)).Returns(nullInstalledVersion ? null : installedVersionKey.Object); + this.environmentVariableHelper.Setup(x => x.GetEnvironmentVariable("ProgramFiles")).Returns("notfound"); + + //Act & Assert + var dotnetHostHelper = new DotnetHostHelper(fileHelper.Object, environmentHelper.Object, windowsRegistrytHelper.Object, environmentVariableHelper.Object, processHelper.Object); + Assert.IsFalse(dotnetHostHelper.TryGetDotnetPathByArchitecture(PlatformArchitecture.X64, out string muxerPath)); + } + + [DataTestMethod] + [DataRow(PlatformArchitecture.ARM64, "/etc/dotnet/install_location_arm64", true, PlatformOperatingSystem.OSX)] + [DataRow(PlatformArchitecture.X64, "/etc/dotnet/install_location_x64", true, PlatformOperatingSystem.OSX)] + [DataRow(PlatformArchitecture.ARM64, "/etc/dotnet/install_location", true, PlatformOperatingSystem.OSX)] + [DataRow(PlatformArchitecture.X64, "/etc/dotnet/install_location", true, PlatformOperatingSystem.OSX)] + [DataRow(PlatformArchitecture.ARM64, "/etc/dotnet/install_location_x64", false, PlatformOperatingSystem.OSX)] + + [DataRow(PlatformArchitecture.ARM64, "/etc/dotnet/install_location_arm64", false, PlatformOperatingSystem.Unix)] + [DataRow(PlatformArchitecture.X64, "/etc/dotnet/install_location_x64", false, PlatformOperatingSystem.Unix)] + [DataRow(PlatformArchitecture.ARM64, "/etc/dotnet/install_location", false, PlatformOperatingSystem.Unix)] + [DataRow(PlatformArchitecture.X64, "/etc/dotnet/install_location", false, PlatformOperatingSystem.Unix)] + [DataRow(PlatformArchitecture.ARM64, "/etc/dotnet/install_location_x64", false, PlatformOperatingSystem.Unix)] + public void GetDotnetPathByArchitecture_GlobalInstallation_Unix(PlatformArchitecture targetArchitecture, string install_location, bool found, PlatformOperatingSystem os) + { + // Arrange + string dotnetMuxer = muxerHelper.RenameMuxerAndReturnPath(os, targetArchitecture); + this.environmentHelper.SetupGet(x => x.OperatingSystem).Returns(os); + this.fileHelper.Setup(x => x.Exists(install_location)).Returns(true); + this.fileHelper.Setup(x => x.Exists(dotnetMuxer)).Returns(true); + this.fileHelper.Setup(x => x.GetStream(install_location, FileMode.Open, FileAccess.Read)).Returns(new MemoryStream(Encoding.UTF8.GetBytes(Path.GetDirectoryName(dotnetMuxer)))); + if (found) + { + this.fileHelper.Setup(x => x.GetStream(dotnetMuxer, FileMode.Open, FileAccess.Read)).Returns(File.OpenRead(dotnetMuxer)); + } + + //Act & Assert + var dotnetHostHelper = new DotnetHostHelper(fileHelper.Object, environmentHelper.Object, windowsRegistrytHelper.Object, environmentVariableHelper.Object, processHelper.Object); + Assert.AreEqual(found, dotnetHostHelper.TryGetDotnetPathByArchitecture(targetArchitecture, out string muxerPath)); + Assert.AreEqual(found ? dotnetMuxer : null, muxerPath); + } + + [DataTestMethod] + [DataRow(PlatformArchitecture.X86, PlatformArchitecture.X64, "ProgramFiles(x86)", "dotnet", true)] + [DataRow(PlatformArchitecture.X64, PlatformArchitecture.ARM64, "ProgramFiles", @"dotnet\x64", true)] + [DataRow(PlatformArchitecture.X64, PlatformArchitecture.X64, "ProgramFiles", "dotnet", true)] + [DataRow(PlatformArchitecture.X86, PlatformArchitecture.X86, "ProgramFiles", "dotnet", true)] + [DataRow(PlatformArchitecture.X64, PlatformArchitecture.X64, "ProgramFiles", "dotnet", false)] + [TestCategory("Windows")] + public void GetDotnetPathByArchitecture_DefaultInstallation_Win(PlatformArchitecture targetArchitecture, PlatformArchitecture platformArchitecture, string envVar, string subfolder, bool found) + { + // Arrange + string dotnetMuxer = muxerHelper.RenameMuxerAndReturnPath(PlatformOperatingSystem.Windows, targetArchitecture, subfolder); + this.environmentVariableHelper.Setup(x => x.GetEnvironmentVariable(envVar)).Returns(dotnetMuxer.Replace(Path.Combine(subfolder, "dotnet.exe"), string.Empty)); + this.environmentHelper.Setup(x => x.Architecture).Returns(platformArchitecture); + if (found) + { + this.fileHelper.Setup(x => x.Exists(dotnetMuxer)).Returns(true); + this.fileHelper.Setup(x => x.GetStream(dotnetMuxer, FileMode.Open, FileAccess.Read)).Returns(new MemoryStream(Encoding.UTF8.GetBytes(Path.GetDirectoryName(dotnetMuxer)))); + this.fileHelper.Setup(x => x.GetStream(dotnetMuxer, FileMode.Open, FileAccess.Read)).Returns(File.OpenRead(dotnetMuxer)); + } + + //Act & Assert + var dotnetHostHelper = new DotnetHostHelper(fileHelper.Object, environmentHelper.Object, windowsRegistrytHelper.Object, environmentVariableHelper.Object, processHelper.Object); + Assert.AreEqual(found, dotnetHostHelper.TryGetDotnetPathByArchitecture(targetArchitecture, out string muxerPath)); + Assert.AreEqual(found ? dotnetMuxer : null, muxerPath); + } + + [DataTestMethod] + [DataRow(PlatformArchitecture.X64, PlatformArchitecture.X64, "/usr/local/share/dotnet", "", true, PlatformOperatingSystem.OSX)] + [DataRow(PlatformArchitecture.X64, PlatformArchitecture.ARM64, "/usr/local/share/dotnet/x64", "", true, PlatformOperatingSystem.OSX)] + [DataRow(PlatformArchitecture.ARM64, PlatformArchitecture.X64, "/usr/local/share/dotnet", "", true, PlatformOperatingSystem.OSX)] + [DataRow(PlatformArchitecture.X64, PlatformArchitecture.X64, "/usr/local/share/dotnet", "", false, PlatformOperatingSystem.OSX)] + + [DataRow(PlatformArchitecture.X64, PlatformArchitecture.X64, "/usr/share/dotnet", "", false, PlatformOperatingSystem.Unix)] + [DataRow(PlatformArchitecture.X64, PlatformArchitecture.ARM64, "/usr/share/dotnet/x64", "", false, PlatformOperatingSystem.Unix)] + [DataRow(PlatformArchitecture.ARM64, PlatformArchitecture.X64, "/usr/share/dotnet", "", false, PlatformOperatingSystem.Unix)] + [DataRow(PlatformArchitecture.X64, PlatformArchitecture.X64, "/usr/share/dotnet", "", false, PlatformOperatingSystem.Unix)] + public void GetDotnetPathByArchitecture_DefaultInstallation_Unix(PlatformArchitecture targetArchitecture, PlatformArchitecture platformArchitecture, string expectedFolder, string subfolder, bool found, PlatformOperatingSystem os) + { + // Arrange + string dotnetMuxer = muxerHelper.RenameMuxerAndReturnPath(os, targetArchitecture, subfolder); + this.environmentHelper.SetupGet(x => x.OperatingSystem).Returns(os); + this.environmentHelper.Setup(x => x.Architecture).Returns(platformArchitecture); + string expectedMuxerPath = Path.Combine(expectedFolder, "dotnet"); + this.fileHelper.Setup(x => x.Exists(expectedMuxerPath)).Returns(true); + this.fileHelper.Setup(x => x.GetStream(expectedMuxerPath, FileMode.Open, FileAccess.Read)).Returns(new MemoryStream(Encoding.UTF8.GetBytes(Path.GetDirectoryName(dotnetMuxer)))); + if (found) + { + this.fileHelper.Setup(x => x.GetStream(expectedMuxerPath, FileMode.Open, FileAccess.Read)).Returns(File.OpenRead(dotnetMuxer)); + } + + //Act & Assert + var dotnetHostHelper = new DotnetHostHelper(fileHelper.Object, environmentHelper.Object, windowsRegistrytHelper.Object, environmentVariableHelper.Object, processHelper.Object); + Assert.AreEqual(found, dotnetHostHelper.TryGetDotnetPathByArchitecture(targetArchitecture, out string muxerPath)); + Assert.AreEqual(found ? expectedMuxerPath : null, muxerPath); + } + + public void Dispose() => this.muxerHelper.Dispose(); + + + class MockMuxerHelper : IDisposable + { + private static string DotnetMuxerWinX86 = "TestAssets/dotnetWinX86.exe"; + private static string DotnetMuxerWinX64 = "TestAssets/dotnetWinX64.exe"; + private static string DotnetMuxerWinArm64 = "TestAssets/dotnetWinArm64.exe"; + private static string DotnetMuxerMacArm64 = "TestAssets/dotnetMacArm64"; + private static string DotnetMuxerMacX64 = "TestAssets/dotnetMacX64"; + private readonly List muxers = new List(); + + public MockMuxerHelper() + { + Assert.IsTrue(File.Exists(DotnetMuxerWinX86)); + Assert.IsTrue(File.Exists(DotnetMuxerWinX64)); + Assert.IsTrue(File.Exists(DotnetMuxerWinArm64)); + Assert.IsTrue(File.Exists(DotnetMuxerMacArm64)); + Assert.IsTrue(File.Exists(DotnetMuxerMacX64)); + } + + public string RenameMuxerAndReturnPath(PlatformOperatingSystem platform, PlatformArchitecture architecture, string subfolder = "") + { + string tmpDirectory = Path.GetTempPath(); + string muxerPath; + switch (platform) + { + case PlatformOperatingSystem.Windows: + { + muxerPath = Path.Combine(tmpDirectory, Guid.NewGuid().ToString("N"), subfolder, "dotnet.exe"); + Directory.CreateDirectory(Path.GetDirectoryName(muxerPath)); + if (architecture == PlatformArchitecture.ARM64) + { + File.Copy(DotnetMuxerWinArm64, muxerPath); + break; + } + else if (architecture == PlatformArchitecture.X64) + { + File.Copy(DotnetMuxerWinX64, muxerPath); + break; + } + else if (architecture == PlatformArchitecture.X86) + { + File.Copy(DotnetMuxerWinX86, muxerPath); + break; + } + + throw new NotSupportedException($"Unsupported architecture '{architecture}'"); + } + case PlatformOperatingSystem.OSX: + { + muxerPath = Path.Combine(tmpDirectory, Guid.NewGuid().ToString("N"), subfolder, "dotnet"); + Directory.CreateDirectory(Path.GetDirectoryName(muxerPath)); + if (architecture == PlatformArchitecture.ARM64) + { + File.Copy(DotnetMuxerMacArm64, muxerPath); + break; + } + else if (architecture == PlatformArchitecture.X64) + { + File.Copy(DotnetMuxerMacX64, muxerPath); + break; + } + + throw new NotSupportedException($"Unsupported architecture '{architecture}'"); + } + case PlatformOperatingSystem.Unix: + { + muxerPath = Path.Combine(tmpDirectory, Guid.NewGuid().ToString("N"), subfolder, "dotnet"); + Directory.CreateDirectory(Path.GetDirectoryName(muxerPath)); + File.WriteAllText(muxerPath, "not supported"); + break; + } + default: + throw new NotSupportedException($"Unsupported OS '{platform}'"); + } + + muxers.Add(muxerPath); + return muxerPath; + } + + public void Dispose() + { + foreach (var muxer in muxers) + { + Directory.Delete(Path.GetDirectoryName(muxer), true); + } + } + } + } +} diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Microsoft.TestPlatform.CoreUtilities.UnitTests.csproj b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Microsoft.TestPlatform.CoreUtilities.UnitTests.csproj index adc8ecc706..04479c1fae 100644 --- a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Microsoft.TestPlatform.CoreUtilities.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Microsoft.TestPlatform.CoreUtilities.UnitTests.csproj @@ -32,5 +32,13 @@ + + + + + + PreserveNewest + + diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/TestAssets/dotnetMacArm64 b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/TestAssets/dotnetMacArm64 new file mode 100644 index 0000000000..7219ef9aa0 Binary files /dev/null and b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/TestAssets/dotnetMacArm64 differ diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/TestAssets/dotnetMacX64 b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/TestAssets/dotnetMacX64 new file mode 100644 index 0000000000..bb83a90c89 Binary files /dev/null and b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/TestAssets/dotnetMacX64 differ diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/TestAssets/dotnetWinArm64.exe b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/TestAssets/dotnetWinArm64.exe new file mode 100644 index 0000000000..9c349be087 Binary files /dev/null and b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/TestAssets/dotnetWinArm64.exe differ diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/TestAssets/dotnetWinX64.exe b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/TestAssets/dotnetWinX64.exe new file mode 100644 index 0000000000..d8d2e59298 Binary files /dev/null and b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/TestAssets/dotnetWinX64.exe differ diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/TestAssets/dotnetWinX86.exe b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/TestAssets/dotnetWinX86.exe new file mode 100644 index 0000000000..f026a9a419 Binary files /dev/null and b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/TestAssets/dotnetWinX86.exe differ diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs index 9313a0ddb8..c1d95923f2 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs @@ -15,7 +15,6 @@ namespace TestPlatform.CrossPlatEngine.UnitTests.Client using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers; @@ -28,7 +27,6 @@ namespace TestPlatform.CrossPlatEngine.UnitTests.Client using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; - using Constants = Microsoft.VisualStudio.TestPlatform.CoreUtilities.Constants; [TestClass] public class ProxyOperationManagerTests : ProxyBaseManagerTests @@ -41,6 +39,12 @@ public class ProxyOperationManagerTests : ProxyBaseManagerTests private Mock mockProcessHelper; + private Mock mockRunsettingHelper; + + private Mock mockWindowsRegistry; + + private Mock mockEnvironmentVariableHelper; + private Mock mockFileHelper; private Mock mockEnvironment; @@ -330,7 +334,7 @@ public void SetupChannelShouldThrowExceptionIfVersionCheckFails() public void SetupChannelForDotnetHostManagerWithIsVersionCheckRequiredFalseShouldNotCheckVersionWithTestHost() { this.SetUpMocksForDotNetTestHost(); - var testHostManager = new TestableDotnetTestHostManager(false, this.mockProcessHelper.Object, this.mockFileHelper.Object, this.mockEnvironment.Object); + var testHostManager = new TestableDotnetTestHostManager(false, this.mockProcessHelper.Object, this.mockFileHelper.Object, this.mockEnvironment.Object, this.mockRunsettingHelper.Object, this.mockWindowsRegistry.Object, this.mockEnvironmentVariableHelper.Object); var operationManager = new TestableProxyOperationManager(this.mockRequestData.Object, this.mockRequestSender.Object, testHostManager); operationManager.SetupChannel(Enumerable.Empty(), this.defaultRunSettings); @@ -342,7 +346,7 @@ public void SetupChannelForDotnetHostManagerWithIsVersionCheckRequiredFalseShoul public void SetupChannelForDotnetHostManagerWithIsVersionCheckRequiredTrueShouldCheckVersionWithTestHost() { this.SetUpMocksForDotNetTestHost(); - var testHostManager = new TestableDotnetTestHostManager(true, this.mockProcessHelper.Object, this.mockFileHelper.Object, this.mockEnvironment.Object); + var testHostManager = new TestableDotnetTestHostManager(true, this.mockProcessHelper.Object, this.mockFileHelper.Object, this.mockEnvironment.Object, this.mockRunsettingHelper.Object, this.mockWindowsRegistry.Object, this.mockEnvironmentVariableHelper.Object); var operationManager = new TestableProxyOperationManager(this.mockRequestData.Object, this.mockRequestSender.Object, testHostManager); operationManager.SetupChannel(Enumerable.Empty(), this.defaultRunSettings); @@ -482,7 +486,11 @@ private void SetUpMocksForDotNetTestHost() this.mockProcessHelper = new Mock(); this.mockFileHelper = new Mock(); this.mockEnvironment = new Mock(); + this.mockRunsettingHelper = new Mock(); + this.mockWindowsRegistry = new Mock(); + this.mockEnvironmentVariableHelper = new Mock(); + this.mockRunsettingHelper.SetupGet(r => r.IsDefaultTargetArchitecture).Returns(true); this.mockProcessHelper.Setup( ph => ph.LaunchProcess( @@ -529,7 +537,17 @@ public TestableDotnetTestHostManager( bool checkRequired, IProcessHelper processHelper, IFileHelper fileHelper, - IEnvironment environment) : base(processHelper, fileHelper, new DotnetHostHelper(fileHelper, environment), environment) + IEnvironment environment, + IRunSettingsHelper runsettingHelper, + IWindowsRegistryHelper windowsRegistryHelper, + IEnvironmentVariableHelper environmentVariableHelper) : base( + processHelper, + fileHelper, + new DotnetHostHelper(fileHelper, environment, windowsRegistryHelper, environmentVariableHelper, processHelper), + environment, + runsettingHelper, + windowsRegistryHelper, + environmentVariableHelper) { this.isVersionCheckRequired = checkRequired; } diff --git a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs index 8e74cdd863..ff2b077199 100644 --- a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs +++ b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs @@ -9,7 +9,6 @@ namespace TestPlatform.TestHostProvider.UnitTests.Hosting using System.IO; using System.Linq; using System.Reflection; - using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -39,19 +38,26 @@ public class DotnetTestHostManagerTests private readonly Mock mockFileHelper; + private readonly Mock mockWindowsRegistry; + private readonly Mock mockMessageLogger; private readonly Mock mockEnvironment; + private readonly Mock mockRunsettingHelper; + private readonly TestRunnerConnectionInfo defaultConnectionInfo; private readonly string[] testSource = { "test.dll" }; private readonly string defaultTestHostPath; + private readonly TestProcessStartInfo defaultTestProcessStartInfo; private readonly TestableDotnetTestHostManager dotnetHostManager; + private Mock mockEnvironmentVariable; + private string errorMessage; private int exitCode; @@ -67,15 +73,23 @@ public DotnetTestHostManagerTests() this.mockFileHelper = new Mock(); this.mockMessageLogger = new Mock(); this.mockEnvironment = new Mock(); + this.mockWindowsRegistry = new Mock(); + this.mockEnvironmentVariable = new Mock(); + this.mockRunsettingHelper = new Mock(); this.defaultConnectionInfo = new TestRunnerConnectionInfo { Port = 123, ConnectionInfo = new TestHostConnectionInfo { Endpoint = "127.0.0.1:123", Role = ConnectionRole.Client }, RunnerProcessId = 0 }; + this.mockEnvironment.SetupGet(e => e.Architecture).Returns((PlatformArchitecture)Enum.Parse(typeof(PlatformArchitecture), Constants.DefaultPlatform.ToString())); + this.mockRunsettingHelper.SetupGet(r => r.IsDefaultTargetArchitecture).Returns(true); string defaultSourcePath = Path.Combine(this.temp, "test.dll"); this.defaultTestHostPath = Path.Combine(this.temp, "testhost.dll"); this.dotnetHostManager = new TestableDotnetTestHostManager( this.mockProcessHelper.Object, this.mockFileHelper.Object, - new DotnetHostHelper(this.mockFileHelper.Object, this.mockEnvironment.Object), - this.mockEnvironment.Object); + new DotnetHostHelper(this.mockFileHelper.Object, this.mockEnvironment.Object, this.mockWindowsRegistry.Object, this.mockEnvironmentVariable.Object, this.mockProcessHelper.Object), + this.mockEnvironment.Object, + this.mockRunsettingHelper.Object, + this.mockWindowsRegistry.Object, + this.mockEnvironmentVariable.Object); this.dotnetHostManager.Initialize(this.mockMessageLogger.Object, string.Empty); this.dotnetHostManager.HostExited += this.DotnetHostManagerHostExited; @@ -83,7 +97,9 @@ public DotnetTestHostManagerTests() // Setup a dummy current process for tests this.mockProcessHelper.Setup(ph => ph.GetCurrentProcessFileName()).Returns(DefaultDotnetPath); this.mockProcessHelper.Setup(ph => ph.GetTestEngineDirectory()).Returns(DefaultDotnetPath); + this.mockEnvironmentVariable.Setup(ev => ev.GetEnvironmentVariable(It.IsAny())).Returns(Path.GetDirectoryName(DefaultDotnetPath)); this.mockFileHelper.Setup(ph => ph.Exists(this.defaultTestHostPath)).Returns(true); + this.mockFileHelper.Setup(ph => ph.Exists(DefaultDotnetPath)).Returns(true); this.mockTestHostLauncher .Setup(th => th.LaunchTestHost(It.IsAny(), It.IsAny())) @@ -476,41 +492,15 @@ public void DotnetTestHostManagerShouldNotBeShared() Assert.IsFalse(this.dotnetHostManager.Shared); } - [TestMethod] - [TestCategory("Windows")] - public void GetTestHostProcessStartInfoOnWindowsForValidPathReturnsFullPathOfDotnetHost() - { - // To validate the else part, set current process to exe other than dotnet - this.mockProcessHelper.Setup(ph => ph.GetCurrentProcessFileName()).Returns("vstest.console.exe"); - - char separator = ';'; - var dotnetExeName = "dotnet.exe"; -#if !NET451 - if (!System.Environment.OSVersion.Platform.ToString().StartsWith("Win")) - { - separator = ':'; - dotnetExeName = "dotnet"; - } -#endif - - // Setup the first directory on PATH to return true for existence check for dotnet - var paths = Environment.GetEnvironmentVariable("PATH").Split(separator); - var acceptablePath = Path.Combine(paths[0], dotnetExeName); - this.mockFileHelper.Setup(fh => fh.Exists(acceptablePath)).Returns(true); - this.mockFileHelper.Setup(ph => ph.Exists("testhost.dll")).Returns(true); - - var startInfo = this.GetDefaultStartInfo(); - - // The full path should be wrapped in quotes (in case it may contain whitespace) - Assert.AreEqual(acceptablePath, startInfo.FileName); - } - [TestMethod] public void GetTestHostProcessStartInfoShouldThrowExceptionWhenDotnetIsNotInstalled() { // To validate the else part, set current process to exe other than dotnet this.mockProcessHelper.Setup(ph => ph.GetCurrentProcessFileName()).Returns("vstest.console.exe"); + // Reset the muxer + this.mockEnvironmentVariable.Setup(ev => ev.GetEnvironmentVariable(It.IsAny())).Returns(string.Empty); + char separator = ';'; var dotnetExeName = "dotnet.exe"; if (!System.Environment.OSVersion.Platform.ToString().StartsWith("Win")) @@ -531,7 +521,7 @@ public void GetTestHostProcessStartInfoShouldThrowExceptionWhenDotnetIsNotInstal Action action = () => this.GetDefaultStartInfo(); - Assert.ThrowsException(action); + Assert.ThrowsException(action); } [TestMethod] @@ -890,6 +880,31 @@ public void GetTestHostProcessStartInfoShouldSkipInvalidAdditionalProbingPaths() Assert.IsTrue(startInfo.Arguments.Contains(testHostFullPath)); } + [TestMethod] + [DataRow("DOTNET_ROOT(x86)", "x86")] + [DataRow("DOTNET_ROOT", "x64")] + [DataRow("DOTNET_ROOT_WRONG", "")] + [TestCategory("Windows")] + public void GetTestHostProcessStartInfoShouldForwardDOTNET_ROOTEnvVarsForAppHost(string envVar, string expectedValue) + { + this.mockFileHelper.Setup(ph => ph.Exists("testhost.exe")).Returns(true); + this.mockEnvironment.Setup(ev => ev.OperatingSystem).Returns(PlatformOperatingSystem.Windows); + this.mockEnvironmentVariable.Reset(); + this.mockEnvironmentVariable.Setup(x => x.GetEnvironmentVariable($"VSTEST_WINAPPHOST_{envVar}")).Returns(expectedValue); + + var startInfo = this.dotnetHostManager.GetTestHostProcessStartInfo(this.testSource, null, this.defaultConnectionInfo); + if (!string.IsNullOrEmpty(expectedValue)) + { + Assert.AreEqual(1, startInfo.EnvironmentVariables.Count); + Assert.IsNotNull(startInfo.EnvironmentVariables[envVar]); + Assert.AreEqual(startInfo.EnvironmentVariables[envVar], expectedValue); + } + else + { + Assert.AreEqual(0, startInfo.EnvironmentVariables.Count); + } + } + [TestMethod] public async Task DotNetCoreErrorMessageShouldBeReadAsynchronouslyAsync() { @@ -1052,8 +1067,11 @@ public TestableDotnetTestHostManager( IProcessHelper processHelper, IFileHelper fileHelper, IDotnetHostHelper dotnetTestHostHelper, - IEnvironment environment) - : base(processHelper, fileHelper, dotnetTestHostHelper, environment) + IEnvironment environment, + IRunSettingsHelper runsettingsHelper, + IWindowsRegistryHelper windowsRegistryHelper, + IEnvironmentVariableHelper environmentVariableHelper) + : base(processHelper, fileHelper, dotnetTestHostHelper, environment, runsettingsHelper, windowsRegistryHelper, environmentVariableHelper) { } }