diff --git a/scripts/build/TestPlatform.Dependencies.props b/scripts/build/TestPlatform.Dependencies.props index 815cdfe5bc..5190258c1c 100644 --- a/scripts/build/TestPlatform.Dependencies.props +++ b/scripts/build/TestPlatform.Dependencies.props @@ -1,4 +1,4 @@ - + 17.0.1600 diff --git a/scripts/common.lib.ps1 b/scripts/common.lib.ps1 index d5fbbfcd5a..75f45f8f07 100644 --- a/scripts/common.lib.ps1 +++ b/scripts/common.lib.ps1 @@ -111,7 +111,9 @@ function Install-DotNetCli $dotnetInstallPath = Join-Path $env:TP_TOOLS_DIR "dotnet" New-Item -ItemType directory -Path $dotnetInstallPath -Force | Out-Null & $dotnetInstallScript -Channel 6.0 -InstallDir $dotnetInstallPath -Version $env:DOTNET_CLI_VERSION - + + & $dotnetInstallScript -Channel 6.0 -InstallDir "${dotnetInstallPath}_x86" -Version $env:DOTNET_CLI_VERSION -Architecture x86 -NoPath + & $dotnetInstallScript -InstallDir "$dotnetInstallPath" -Runtime 'dotnet' -Version '2.1.30' -Channel '2.1' -Architecture x64 -NoPath $env:DOTNET_ROOT= $dotnetInstallPath diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureSwitchTests.Windows.cs b/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureSwitchTests.Windows.cs new file mode 100644 index 0000000000..8ab191cac0 --- /dev/null +++ b/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureSwitchTests.Windows.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#if !NET451 + +namespace Microsoft.TestPlatform.AcceptanceTests +{ + using Microsoft.TestPlatform.TestUtilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Newtonsoft.Json.Linq; + using System.Collections.Generic; + using System.IO; + using System.Linq; + + [TestClass] + [TestCategory("Windows-Review")] + public class DotnetArchitectureSwitchTestsWindowsOnly : AcceptanceTestBase + { + [TestMethod] + [DataRow("X64", "X86")] + [DataRow("X86", "X64")] + public void Use_EnvironmentVariables(string architectureFrom, string architectureTo) + { + using (Workspace workSpace = new Workspace(GetResultsDirectory())) + { + string dotnetPath = GetDownloadedDotnetMuxerFromTools(architectureFrom); + string dotnetPathTo = GetDownloadedDotnetMuxerFromTools(architectureTo); + var vstestConsolePath = GetDotnetRunnerPath(); + var dotnetRunnerPath = workSpace.CreateDirectory("dotnetrunner"); + workSpace.CopyAll(new DirectoryInfo(Path.GetDirectoryName(vstestConsolePath)), dotnetRunnerPath); + + // Patch the runner + string sdkVersion = GetLatestSdkVersion(dotnetPath); + string runtimeConfigFile = Path.Combine(dotnetRunnerPath.FullName, "vstest.console.runtimeconfig.json"); + JObject patchRuntimeConfig = JObject.Parse(File.ReadAllText(runtimeConfigFile)); + patchRuntimeConfig["runtimeOptions"]["framework"]["version"] = sdkVersion; + File.WriteAllText(runtimeConfigFile, patchRuntimeConfig.ToString()); + + var environmentVariables = new Dictionary + { + ["DOTNET_MULTILEVEL_LOOKUP"] = "0", + [$"DOTNET_ROOT_{architectureTo}"] = Path.GetDirectoryName(dotnetPathTo), + ["ExpectedArchitecture"] = architectureTo + }; + this.ExecuteApplication(dotnetPath, "new mstest", out string stdOut, out string stdError, out int exitCode, environmentVariables, workSpace.Path); + + // Patch test file + File.WriteAllText(Path.Combine(workSpace.Path, "UnitTest1.cs"), +@" +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace cfebbc5339cf4c22854e79824e938c74; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void TestMethod1() + { + Assert.AreEqual(Environment.GetEnvironmentVariable(""ExpectedArchitecture""), System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString()); + } +}"); + + this.ExecuteApplication(dotnetPath, $"test -p:VsTestConsolePath=\"{Path.Combine(dotnetRunnerPath.FullName, Path.GetFileName(vstestConsolePath))}\" --arch {architectureTo.ToLower()} --diag:log.txt", out stdOut, out stdError, out exitCode, environmentVariables, workSpace.Path); + Assert.AreEqual(0, exitCode, stdOut); + + environmentVariables = new Dictionary + { + ["DOTNET_MULTILEVEL_LOOKUP"] = "0", + ["DOTNET_ROOT"] = Path.GetDirectoryName(dotnetPathTo), + ["ExpectedArchitecture"] = architectureTo + }; + this.ExecuteApplication(dotnetPath, $"test -p:VsTestConsolePath=\"{Path.Combine(dotnetRunnerPath.FullName, Path.GetFileName(vstestConsolePath))}\" --arch {architectureTo.ToLower()} --diag:log.txt", out stdOut, out stdError, out exitCode, environmentVariables, workSpace.Path); + Assert.AreEqual(0, exitCode, stdOut); + + environmentVariables = new Dictionary + { + ["DOTNET_MULTILEVEL_LOOKUP"] = "0", + [$"DOTNET_ROOT_{architectureTo}"] = Path.GetDirectoryName(dotnetPathTo), + ["DOTNET_ROOT"] = "WE SHOULD PICK THE ABOVE ONE BEFORE FALLBACK TO DOTNET_ROOT", + ["ExpectedArchitecture"] = architectureTo + }; + this.ExecuteApplication(dotnetPath, $"test -p:VsTestConsolePath=\"{Path.Combine(dotnetRunnerPath.FullName, Path.GetFileName(vstestConsolePath))}\" --arch {architectureTo.ToLower()} --diag:log.txt", out stdOut, out stdError, out exitCode, environmentVariables, workSpace.Path); + Assert.AreEqual(0, exitCode, stdOut); + } + } + + private string GetLatestSdkVersion(string dotnetPath) + => Path.GetFileName(Directory.GetDirectories(Path.Combine(Path.GetDirectoryName(dotnetPath), @"shared/Microsoft.NETCore.App")).OrderByDescending(x => x).First()); + } +} + +#endif diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureSwitchTests.cs b/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureSwitchTests.cs new file mode 100644 index 0000000000..a242f7685b --- /dev/null +++ b/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureSwitchTests.cs @@ -0,0 +1,329 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#if !NET451 + +namespace Microsoft.TestPlatform.AcceptanceTests +{ + using System.IO; + using System.Text.RegularExpressions; + using System.Collections.Generic; + using System.Runtime.InteropServices; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System; + + // This tests need specific sdks to be installed on arm machine + // >= ARM 6.0.2xx + // >= x64 6.0.2xx + // x64 5.0.4xx for Mac + // x64 3.1.4XX for Win + // Manual test './tools/.../dotnet test ./test/Microsoft.TestPlatform.AcceptanceTests/bin/Debug/netcoreapp2.1/Microsoft.TestPlatform.AcceptanceTests.dll --testcasefilter:"DotnetArchitectureSwitchTests"' + [TestClass] + [Ignore("Manual tests(for now). Tests in this class need some .NET SDK global installations")] + public class DotnetArchitectureSwitchTests : AcceptanceTestBase + { + private static string privateX64Installation; + + [ClassInitialize] + public static void ClassInitialize(TestContext context) + { + privateX64Installation = Path.Combine(GetResultsDirectory(), "x64"); + CopyAll(new DirectoryInfo(GetX64InstallationFolder), new DirectoryInfo(privateX64Installation)); + } + + [ClassCleanup] + public static void ClassCleanup() + { + try + { + Directory.Delete(privateX64Installation, true); + } + catch + { + + } + } + + [TestMethod] + public void GlobalInstallation() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return; + } + + var projectName = "ArchitectureSwitch.csproj"; + var projectPath = this.GetProjectFullPath(projectName); + var projectDirectory = Path.GetDirectoryName(projectPath); + + var env = new Dictionary(); + env.Add("DOTNET_ROOT", null); + env.Add("DOTNET_MULTILEVEL_LOOKUP", "0"); + + // Verify native architecture + ExecuteApplication(GetDefaultDotnetMuxerLocation, $"test {projectPath} --framework net6.0", out string stdOut, out string stdError, out int exitCode, env, projectDirectory); + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Assert.IsTrue(stdOut.Contains("Runtime location: /usr/local/share/dotnet/shared/Microsoft.NETCore.App")); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.IsTrue(stdOut.Contains($@"Runtime location: {Environment.ExpandEnvironmentVariables("%ProgramFiles%")}\dotnet\shared\Microsoft.NETCore.App")); + } + Assert.IsTrue(stdOut.Contains("OSArchitecture: Arm64")); + Assert.IsTrue(stdOut.Contains("ProcessArchitecture: Arm64")); + + + // Verify switch using csproj + ExecuteApplication(GetDefaultDotnetMuxerLocation, $"test {projectPath} --framework net6.0 --arch x64", out stdOut, out stdError, out exitCode, env, projectDirectory); + AssertSwitch(stdOut); + + // Verify switch using test container + var buildAssemblyPath = GetAssetFullPath("ArchitectureSwitch.dll", "net6.0"); + ExecuteApplication(GetDefaultDotnetMuxerLocation, $"test {buildAssemblyPath} --arch x64", out stdOut, out stdError, out exitCode, env, projectDirectory); + AssertSwitch(stdOut); + + void AssertSwitch(string output) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Assert.IsTrue(output.Contains("Runtime location: /usr/local/share/dotnet/x64/shared/Microsoft.NETCore.App")); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.IsTrue(output.Contains($@"Runtime location: {Environment.ExpandEnvironmentVariables("%ProgramFiles%")}\dotnet\x64\shared\Microsoft.NETCore.App")); + } + Assert.IsTrue(output.Contains("OSArchitecture: X64")); + Assert.IsTrue(output.Contains("ProcessArchitecture: X64")); + } + } + + [TestMethod] + [DataRow(true, false)] + [DataRow(false, true)] + public void DOTNET_ROOTS_EnvironmentVariables(bool dotnetRoot, bool dotnetRootX64) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return; + } + + var env = new Dictionary(); + env["DOTNET_ROOT"] = null; + env["DOTNET_MULTILEVEL_LOOKUP"] = "0"; + + var projectName = "ArchitectureSwitch.csproj"; + var projectPath = this.GetProjectFullPath(projectName); + var projectDirectory = Path.GetDirectoryName(projectPath); + + // Verify native architecture + ExecuteApplication(GetDefaultDotnetMuxerLocation, $"test {projectPath} --framework net6.0", out string stdOut, out string stdError, out int exitCode, env, projectDirectory); + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Assert.IsTrue(stdOut.Contains("Runtime location: /usr/local/share/dotnet/shared/Microsoft.NETCore.App"), "Unexpected runtime location"); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.IsTrue(stdOut.Contains($@"Runtime location: {Environment.ExpandEnvironmentVariables("%ProgramFiles%")}\dotnet\shared\Microsoft.NETCore.App")); + } + Assert.IsTrue(stdOut.Contains("OSArchitecture: Arm64"), "Unexpected OSArchitecture"); + Assert.IsTrue(stdOut.Contains("ProcessArchitecture: Arm64"), "Unexpected ProcessArchitecture"); + + env.Clear(); + env["DOTNET_ROOT"] = null; + env["DOTNET_MULTILEVEL_LOOKUP"] = "0"; + if (dotnetRoot) + { + env["DOTNET_ROOT"] = privateX64Installation; + } + + if (dotnetRootX64) + { + env.Add("DOTNET_ROOT_X64", privateX64Installation); + } + + // Verify switch using csproj + ExecuteApplication($"{privateX64Installation}/dotnet", $"test {projectPath} --framework net6.0 --arch x64", out stdOut, out stdError, out exitCode, env, projectDirectory); + AssertSwitch(stdOut); + + // Verify switch using test container + var buildAssemblyPath = GetAssetFullPath("ArchitectureSwitch.dll", "net6.0"); + ExecuteApplication($"{privateX64Installation}/dotnet", $"test {buildAssemblyPath} --framework net6.0 --arch x64", out stdOut, out stdError, out exitCode, env, projectDirectory); + AssertSwitch(stdOut); + + void AssertSwitch(string output) + { + Assert.IsTrue(Regex.IsMatch(output.Replace(@"\", "/"), $"Runtime location: .*{privateX64Installation.Replace(@"\", "/")}.*shared.*Microsoft.NETCore.App"), "Unexpected runtime location"); + Assert.IsTrue(output.Contains("OSArchitecture: X64"), "Unexpected OSArchitecture"); + Assert.IsTrue(output.Contains("ProcessArchitecture: X64"), "Unexpected ProcessArchitecture"); + Assert.IsTrue(dotnetRoot ? output.Contains($"DOTNET_ROOT: {privateX64Installation}") : true, "Unexpected DOTNET_ROOT var"); + Assert.IsTrue(dotnetRootX64 ? output.Contains($"DOTNET_ROOT_X64: {privateX64Installation}") : true, "Unexpected DOTNET_ROOT_X64 var"); + } + } + + [TestMethod] + public void PrivateX64BuildToGlobalArmInstallation() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return; + } + + var env = new Dictionary(); + env["DOTNET_ROOT"] = null; + env["DOTNET_MULTILEVEL_LOOKUP"] = "0"; + string privateInstallationMuxer = Path.Combine(privateX64Installation, GetMuxerName); + + var projectName = "ArchitectureSwitch.csproj"; + var projectPath = this.GetProjectFullPath(projectName); + var projectDirectory = Path.GetDirectoryName(projectPath); + + // Verify native architecture + ExecuteApplication(privateInstallationMuxer, $"test {projectPath} --framework net6.0", out string stdOut, out string stdError, out int exitCode, env, projectDirectory); + Assert.IsTrue(Regex.IsMatch(stdOut.Replace(@"\", "/"), $"Runtime location: .*{privateX64Installation.Replace(@"\", "/")}.*shared.*Microsoft.NETCore.App"), "Unexpected runtime location"); + Assert.IsTrue(stdOut.Contains("OSArchitecture: X64"), "Unexpected OSArchitecture"); + Assert.IsTrue(stdOut.Contains("ProcessArchitecture: X64"), "Unexpected ProcessArchitecture"); + + // Verify switch using csproj + ExecuteApplication($"{privateX64Installation}/dotnet", $"test {projectPath} --framework net6.0 --arch arm64", out stdOut, out stdError, out exitCode, env, projectDirectory); + AssertSwitch(stdOut); + + // Verify switch using test container + var buildAssemblyPath = GetAssetFullPath("ArchitectureSwitch.dll", "net6.0"); + ExecuteApplication($"{privateX64Installation}/dotnet", $"test {buildAssemblyPath} --framework net6.0 --arch arm64", out stdOut, out stdError, out exitCode, env, projectDirectory); + AssertSwitch(stdOut); + + void AssertSwitch(string output) + { + Assert.IsTrue(Regex.IsMatch(output.Replace(@"\", "/"), $"Runtime location: .*{GetDefaultLocation.Replace(@"\", "/")}.*shared.*Microsoft.NETCore.App"), "Unexpected runtime location"); + Assert.IsTrue(output.Contains("OSArchitecture: Arm64"), "Unexpected OSArchitecture"); + Assert.IsTrue(output.Contains("ProcessArchitecture: Arm64"), "Unexpected ProcessArchitecture"); + } + } + + [TestMethod] + [DataRow(true, false)] + [DataRow(false, true)] + public void PrivateX64BuildToDOTNET_ROOTS_EnvironmentVariables(bool dotnetRoot, bool dotnetRootARM64) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return; + } + + var env = new Dictionary(); + env["DOTNET_ROOT"] = null; + env["DOTNET_MULTILEVEL_LOOKUP"] = "0"; + string privateInstallationMuxer = Path.Combine(privateX64Installation, GetMuxerName); + + var projectName = "ArchitectureSwitch.csproj"; + var projectPath = this.GetProjectFullPath(projectName); + var projectDirectory = Path.GetDirectoryName(projectPath); + + // Verify native architecture + ExecuteApplication(privateInstallationMuxer, $"test {projectPath} --framework net6.0", out string stdOut, out string stdError, out int exitCode, env, projectDirectory); + Assert.IsTrue(Regex.IsMatch(stdOut.Replace(@"\", "/"), $"Runtime location: .*{privateX64Installation.Replace(@"\", "/")}.*shared.*Microsoft.NETCore.App"), "Unexpected runtime location"); + Assert.IsTrue(stdOut.Contains("OSArchitecture: X64"), "Unexpected OSArchitecture"); + Assert.IsTrue(stdOut.Contains("ProcessArchitecture: X64"), "Unexpected ProcessArchitecture"); + + env.Clear(); + env["DOTNET_ROOT"] = null; + env["DOTNET_MULTILEVEL_LOOKUP"] = "0"; + if (dotnetRoot) + { + env["DOTNET_ROOT"] = GetDefaultLocation; + } + + if (dotnetRootARM64) + { + env["DOTNET_ROOT_ARM64"] = GetDefaultLocation; + } + + // Verify switch using csproj + ExecuteApplication($"{privateX64Installation}/dotnet", $"test {projectPath} --framework net6.0 --arch arm64", out stdOut, out stdError, out exitCode, env, projectDirectory); + AssertSwitch(stdOut); + + // Verify switch using test container + var buildAssemblyPath = GetAssetFullPath("ArchitectureSwitch.dll", "net6.0"); + ExecuteApplication($"{privateX64Installation}/dotnet", $"test {buildAssemblyPath} --framework net6.0 --arch arm64", out stdOut, out stdError, out exitCode, env, projectDirectory); + AssertSwitch(stdOut); + + void AssertSwitch(string output) + { + Assert.IsTrue(Regex.IsMatch(output.Replace(@"\", "/"), $"Runtime location: .*{GetDefaultLocation.Replace(@"\", "/")}.*shared.*Microsoft.NETCore.App"), "Unexpected runtime location"); + Assert.IsTrue(output.Contains("OSArchitecture: Arm64"), "Unexpected OSArchitecture"); + Assert.IsTrue(output.Contains("ProcessArchitecture: Arm64"), "Unexpected ProcessArchitecture"); + Assert.IsTrue(dotnetRoot ? output.Contains($"DOTNET_ROOT: {GetDefaultLocation}") : true, "Unexpected DOTNET_ROOT var"); + Assert.IsTrue(dotnetRootARM64 ? output.Contains($"DOTNET_ROOT_ARM64: {GetDefaultLocation}") : true, "Unexpected DOTNET_ROOT_ARM64 var"); + } + } + + [TestMethod] + public void SilentlyForceX64() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return; + } + + var projectName = "ArchitectureSwitch.csproj"; + var projectPath = this.GetProjectFullPath(projectName); + var projectDirectory = Path.GetDirectoryName(projectPath); + + var env = new Dictionary(); + env["DOTNET_ROOT"] = null; + env["DOTNET_MULTILEVEL_LOOKUP"] = "0"; + + ExecuteApplication(GetDefaultDotnetMuxerLocation, $"test {projectPath} --framework {GetFrameworkVersionToForceToX64}", out string stdOut, out string stdError, out int exitCode, env, projectDirectory); + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Assert.IsTrue(stdOut.Contains("Runtime location: /usr/local/share/dotnet/x64/shared/Microsoft.NETCore.App")); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.IsTrue(stdOut.Contains($@"Runtime location: {Environment.ExpandEnvironmentVariables("%ProgramFiles%")}\dotnet\x64\shared\Microsoft.NETCore.App")); + } + Assert.IsTrue(stdOut.Contains("OSArchitecture: X64")); + Assert.IsTrue(stdOut.Contains("ProcessArchitecture: X64")); + } + + private static string GetMuxerName => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "dotnet" : "dotnet.exe"; + + private static string GetX64InstallationFolder => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? + "/usr/local/share/dotnet/x64" : + $@"{Environment.ExpandEnvironmentVariables("%ProgramFiles%")}\dotnet\x64"; + + private static string GetFrameworkVersionToForceToX64 => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? + "net5.0" : + "netcoreapp3.1"; + + private static string GetDefaultLocation => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? + $"/usr/local/share/dotnet" : + $@"{Environment.ExpandEnvironmentVariables("%ProgramFiles%")}\dotnet"; + + private static string GetDefaultDotnetMuxerLocation => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? + $"{GetDefaultLocation}/{GetMuxerName}" : + $@"{GetDefaultLocation}\{GetMuxerName}"; + + private static void CopyAll(DirectoryInfo source, DirectoryInfo target) + { + Directory.CreateDirectory(target.FullName); + + // Copy each file into the new directory. + foreach (FileInfo fi in source.GetFiles()) + { + fi.CopyTo(Path.Combine(target.FullName, fi.Name), true); + } + + // Copy each subdirectory using recursion. + foreach (DirectoryInfo diSourceSubDir in source.GetDirectories()) + { + DirectoryInfo nextTargetSubDir = + target.CreateSubdirectory(diSourceSubDir.Name); + CopyAll(diSourceSubDir, nextTargetSubDir); + } + } + } +} + +#endif diff --git a/test/Microsoft.TestPlatform.SmokeTests/DotnetHostArchitectureVerifierTests.cs b/test/Microsoft.TestPlatform.SmokeTests/DotnetHostArchitectureVerifierTests.cs new file mode 100644 index 0000000000..24896d47f2 --- /dev/null +++ b/test/Microsoft.TestPlatform.SmokeTests/DotnetHostArchitectureVerifierTests.cs @@ -0,0 +1,71 @@ +// 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.SmokeTests +{ + using Microsoft.TestPlatform.TestUtilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Newtonsoft.Json.Linq; + using System.Collections.Generic; + using System.IO; + using System.Linq; + + [TestClass] + // On Linux/Mac we don't download the same .NET SDK bundles + [TestCategory("Windows-Review")] + public class DotnetHostArchitectureVerifierTests : IntegrationTestBase + { + [TestMethod] + [DataRow("X64")] + [DataRow("X86")] + public void VerifyHostArchitecture(string architecture) + { + using (Workspace workSpace = new Workspace(GetResultsDirectory())) + { + string dotnetPath = GetDownloadedDotnetMuxerFromTools(architecture); + var vstestConsolePath = GetDotnetRunnerPath(); + var dotnetRunnerPath = workSpace.CreateDirectory("dotnetrunner"); + workSpace.CopyAll(new DirectoryInfo(Path.GetDirectoryName(vstestConsolePath)), dotnetRunnerPath); + + // Patch the runner + string sdkVersion = GetLatestSdkVersion(dotnetPath); + string runtimeConfigFile = Path.Combine(dotnetRunnerPath.FullName, "vstest.console.runtimeconfig.json"); + JObject patchRuntimeConfig = JObject.Parse(File.ReadAllText(runtimeConfigFile)); + patchRuntimeConfig["runtimeOptions"]["framework"]["version"] = sdkVersion; + File.WriteAllText(runtimeConfigFile, patchRuntimeConfig.ToString()); + + var environmentVariables = new Dictionary + { + ["DOTNET_MULTILEVEL_LOOKUP"] = "0", + ["ExpectedArchitecture"] = architecture + }; + + this.ExecuteApplication(dotnetPath, "new mstest", out string stdOut, out string stdError, out int exitCode, environmentVariables, workSpace.Path); + + // Patch test file + File.WriteAllText(Path.Combine(workSpace.Path, "UnitTest1.cs"), +@" +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace cfebbc5339cf4c22854e79824e938c74; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void TestMethod1() + { + Assert.AreEqual(Environment.GetEnvironmentVariable(""ExpectedArchitecture""), System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString()); + } +}"); + + this.ExecuteApplication(dotnetPath, $"test -p:VsTestConsolePath=\"{Path.Combine(dotnetRunnerPath.FullName, Path.GetFileName(vstestConsolePath))}\"", out stdOut, out stdError, out exitCode, environmentVariables, workSpace.Path); + Assert.AreEqual(0, exitCode, stdOut); + } + } + + private string GetLatestSdkVersion(string dotnetPath) + => Path.GetFileName(Directory.GetDirectories(Path.Combine(Path.GetDirectoryName(dotnetPath), @"shared/Microsoft.NETCore.App")).OrderByDescending(x => x).First()); + } +} diff --git a/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs b/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs index 3921f84c96..4154f06f72 100644 --- a/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs @@ -47,7 +47,7 @@ public class IntegrationTestBase private readonly string XUnitTestAdapterRelativePath = @"xunit.runner.visualstudio\{0}\build\_common".Replace('\\', Path.DirectorySeparatorChar); private readonly string ChutzpahTestAdapterRelativePath = @"chutzpah\{0}\tools".Replace('\\', Path.DirectorySeparatorChar); - protected readonly bool IsWindows = System.Environment.OSVersion.Platform.ToString().StartsWith("Win"); + protected static readonly bool IsWindows = System.Environment.OSVersion.Platform.ToString().StartsWith("Win"); public enum UnitTestFramework { @@ -143,7 +143,6 @@ public void InvokeVsTest(string arguments) this.FormatStandardOutCome(); } - /// /// Invokes our local copy of dotnet that is patched with artifacts from the build with specified arguments. /// @@ -596,7 +595,7 @@ private void ExecutePatchedDotnet(string command, string args, out string stdOut this.ExecuteApplication(patchedDotnetPath, string.Join(" ", command, args), out stdOut, out stdError, out exitCode, environmentVariables); } - private void ExecuteApplication(string path, string args, out string stdOut, out string stdError, out int exitCode, Dictionary environmentVariables = null) + protected void ExecuteApplication(string path, string args, out string stdOut, out string stdError, out int exitCode, Dictionary environmentVariables = null, string workingDirectory = null) { if (string.IsNullOrWhiteSpace(path)) { @@ -611,12 +610,17 @@ private void ExecuteApplication(string path, string args, out string stdOut, out process.StartInfo.FileName = path; process.StartInfo.Arguments = args; process.StartInfo.UseShellExecute = false; - //vstestconsole.StartInfo.WorkingDirectory = testEnvironment.PublishDirectory; process.StartInfo.RedirectStandardError = true; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.CreateNoWindow = true; process.StartInfo.StandardOutputEncoding = Encoding.UTF8; process.StartInfo.StandardErrorEncoding = Encoding.UTF8; + + if (workingDirectory != null) + { + process.StartInfo.WorkingDirectory = workingDirectory; + } + if (environmentVariables != null) { foreach (var variable in environmentVariables) @@ -756,13 +760,35 @@ protected static string GetResultsDirectory() // AGENT_TEMPDIRECTORY is AzureDevops variable, which is set to path // that is cleaned up after every job. This is preferable to use over // just the normal temp. - var temp = Environment.GetEnvironmentVariable("AGENT_TEMPDIRECTORY") ?? Path.GetTempPath(); + var temp = GetTempPath(); var directoryPath = Path.Combine(temp, Guid.NewGuid().ToString("n")); Directory.CreateDirectory(directoryPath); return directoryPath; } + protected static string GetTempPath() => Environment.GetEnvironmentVariable("AGENT_TEMPDIRECTORY") ?? Path.GetTempPath(); + + protected static string GetDownloadedDotnetMuxerFromTools(string architecture) + { + if (architecture != "X86" && architecture != "X64") + { + throw new NotSupportedException(nameof(architecture)); + } + + string path = Path.Combine(IntegrationTestEnvironment.TestPlatformRootDirectory, "tools", + architecture == "X86" ? + "dotnet_x86" : + $"dotnet", + $"dotnet{(IsWindows ? ".exe" : "")}"); + + Assert.IsTrue(File.Exists(path)); + + return path; + } + + protected static string GetDotnetRunnerPath() => Path.Combine(IntegrationTestEnvironment.TestPlatformRootDirectory, "artifacts", IntegrationTestEnvironment.BuildConfiguration, "netcoreapp2.1", "vstest.console.dll"); + protected static void TryRemoveDirectory(string directory) { if (Directory.Exists(directory)) diff --git a/test/Microsoft.TestPlatform.TestUtilities/Workspace.cs b/test/Microsoft.TestPlatform.TestUtilities/Workspace.cs new file mode 100644 index 0000000000..37a0a651f5 --- /dev/null +++ b/test/Microsoft.TestPlatform.TestUtilities/Workspace.cs @@ -0,0 +1,55 @@ +// 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 System.IO; + +namespace Microsoft.TestPlatform.TestUtilities +{ + public class Workspace : IDisposable + { + public Workspace(string path) + { + Path = path; + } + + public string Path { get; } + + public void Dispose() + { + if (!string.IsNullOrEmpty(Path)) + { + try + { + if (Directory.Exists(Path)) + Directory.Delete(Path, true); + } + catch + { + // ignore + } + } + } + + public DirectoryInfo CreateDirectory(string dir) => Directory.CreateDirectory(System.IO.Path.Combine(Path, dir)); + + public void CopyAll(DirectoryInfo source, DirectoryInfo target) + { + Directory.CreateDirectory(target.FullName); + + // Copy each file into the new directory. + foreach (FileInfo fi in source.GetFiles()) + { + fi.CopyTo(System.IO.Path.Combine(target.FullName, fi.Name), true); + } + + // Copy each subdirectory using recursion. + foreach (DirectoryInfo diSourceSubDir in source.GetDirectories()) + { + DirectoryInfo nextTargetSubDir = + target.CreateSubdirectory(diSourceSubDir.Name); + CopyAll(diSourceSubDir, nextTargetSubDir); + } + } + } +} diff --git a/test/TestAssets/ArchitectureSwitch/ArchitectureSwitch.csproj b/test/TestAssets/ArchitectureSwitch/ArchitectureSwitch.csproj new file mode 100644 index 0000000000..4d423c4575 --- /dev/null +++ b/test/TestAssets/ArchitectureSwitch/ArchitectureSwitch.csproj @@ -0,0 +1,13 @@ + + + net6.0;net5.0 + net6.0;netcoreapp3.1 + false + + + + + + + + diff --git a/test/TestAssets/ArchitectureSwitch/UnitTest1.cs b/test/TestAssets/ArchitectureSwitch/UnitTest1.cs new file mode 100644 index 0000000000..80aaa5fa73 --- /dev/null +++ b/test/TestAssets/ArchitectureSwitch/UnitTest1.cs @@ -0,0 +1,31 @@ +// 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 System.Collections; +using System.Runtime.InteropServices; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace TestProjectNetcore +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void TestMethod1() + { + foreach(DictionaryEntry envVar in Environment.GetEnvironmentVariables()) + { + if(envVar.Key.ToString().StartsWith("DOTNET_ROOT")) + { + Console.WriteLine($"{envVar.Key}: {envVar.Value.ToString()}"); + } + } + + Console.WriteLine("OSArchitecture: " + RuntimeInformation.OSArchitecture.ToString()); + Console.WriteLine("ProcessArchitecture: " + RuntimeInformation.ProcessArchitecture.ToString()); + Console.WriteLine("Runtime location: " + typeof(object).Assembly.Location); + Assert.IsTrue(false); + } + } +} diff --git a/test/TestAssets/ArchitectureSwitch/global.json b/test/TestAssets/ArchitectureSwitch/global.json new file mode 100644 index 0000000000..c8c7401e65 --- /dev/null +++ b/test/TestAssets/ArchitectureSwitch/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "6.0.200-preview" + } +} \ No newline at end of file diff --git a/test/TestAssets/TestAssets.sln b/test/TestAssets/TestAssets.sln index 883397e994..643a9a0f74 100644 --- a/test/TestAssets/TestAssets.sln +++ b/test/TestAssets/TestAssets.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29102.190 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32024.52 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnitAdapterPerfTestProject", "PerfAssets\NUnitAdapterPerfTestProject\NUnitAdapterPerfTestProject.csproj", "{F22A8D65-0581-4CC7-9C1C-9BC9F9E80DA4}" EndProject @@ -92,6 +92,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParametrizedTestProject", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AttachmentProcessorDataCollector", "AttachmentProcessorDataCollector\AttachmentProcessorDataCollector.csproj", "{16F51720-29D0-472A-93FA-2604D61991B7}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArchitectureSwitch", "ArchitectureSwitch\ArchitectureSwitch.csproj", "{452352E1-71CA-436E-8165-F284EE36C924}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -606,6 +608,18 @@ Global {16F51720-29D0-472A-93FA-2604D61991B7}.Release|x64.Build.0 = Release|Any CPU {16F51720-29D0-472A-93FA-2604D61991B7}.Release|x86.ActiveCfg = Release|Any CPU {16F51720-29D0-472A-93FA-2604D61991B7}.Release|x86.Build.0 = Release|Any CPU + {452352E1-71CA-436E-8165-F284EE36C924}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {452352E1-71CA-436E-8165-F284EE36C924}.Debug|Any CPU.Build.0 = Debug|Any CPU + {452352E1-71CA-436E-8165-F284EE36C924}.Debug|x64.ActiveCfg = Debug|Any CPU + {452352E1-71CA-436E-8165-F284EE36C924}.Debug|x64.Build.0 = Debug|Any CPU + {452352E1-71CA-436E-8165-F284EE36C924}.Debug|x86.ActiveCfg = Debug|Any CPU + {452352E1-71CA-436E-8165-F284EE36C924}.Debug|x86.Build.0 = Debug|Any CPU + {452352E1-71CA-436E-8165-F284EE36C924}.Release|Any CPU.ActiveCfg = Release|Any CPU + {452352E1-71CA-436E-8165-F284EE36C924}.Release|Any CPU.Build.0 = Release|Any CPU + {452352E1-71CA-436E-8165-F284EE36C924}.Release|x64.ActiveCfg = Release|Any CPU + {452352E1-71CA-436E-8165-F284EE36C924}.Release|x64.Build.0 = Release|Any CPU + {452352E1-71CA-436E-8165-F284EE36C924}.Release|x86.ActiveCfg = Release|Any CPU + {452352E1-71CA-436E-8165-F284EE36C924}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE