From e1082008d6627da61cb984b8dc1bc5bc3944101c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Mon, 6 Jun 2022 14:19:18 +0200 Subject: [PATCH 1/9] Fix forwarding of DOTNET_ROOT for .NET6.0+ --- .../Hosting/DotnetTestHostManager.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs index 48f0cd64d8..4ca2aa9f56 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs @@ -52,6 +52,8 @@ public class DotnetTestHostManager : ITestRuntimeProvider2 private const string TestAdapterRegexPattern = @"TestAdapter.dll"; private const string PROCESSOR_ARCHITECTURE = nameof(PROCESSOR_ARCHITECTURE); + private static readonly Version Version6_0 = new(6, 0); + private readonly IDotnetHostHelper _dotnetHostHelper; private readonly IEnvironment _platformEnvironment; private readonly IProcessHelper _processHelper; @@ -469,19 +471,24 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo( // 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 = _environmentVariableHelper.GetEnvironmentVariable(dotnetRootEnvName); - if (dotnetRoot is null) + const string prefix = "VSTEST_WINAPPHOST_"; + const string dotnetRoot = "DOTNET_ROOT"; + string vstestDotnetRootEnvName = $"{prefix}{dotnetRoot}(x86)"; + var vstestDotnetRootEnvValue = _environmentVariableHelper.GetEnvironmentVariable(vstestDotnetRootEnvName); + if (vstestDotnetRootEnvValue is null) { - dotnetRootEnvName = $"{prefix}DOTNET_ROOT"; - dotnetRoot = _environmentVariableHelper.GetEnvironmentVariable(dotnetRootEnvName); + vstestDotnetRootEnvName = $"{prefix}{dotnetRoot}"; + vstestDotnetRootEnvValue = _environmentVariableHelper.GetEnvironmentVariable(vstestDotnetRootEnvName); } - if (dotnetRoot != null) + if (vstestDotnetRootEnvValue != null) { - EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Found '{dotnetRootEnvName}' in env variables, value '{dotnetRoot}', forwarding to '{dotnetRootEnvName.Replace(prefix, string.Empty)}'"); - startInfo.EnvironmentVariables.Add(dotnetRootEnvName.Replace(prefix, string.Empty), dotnetRoot); + string dotnetRootEnvName = Version.Parse(_targetFramework.Version) >= Version6_0 + ? $"{dotnetRoot}_{_architecture.ToString().ToUpperInvariant()}" + : vstestDotnetRootEnvName.Replace(prefix, string.Empty); + + EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Found '{vstestDotnetRootEnvName}' in env variables, value '{vstestDotnetRootEnvValue}', forwarding to '{dotnetRootEnvName}' (target framework is {_targetFramework.Name}, Version={_targetFramework.Version})."); + startInfo.EnvironmentVariables.Add(dotnetRootEnvName, vstestDotnetRootEnvValue); } else { From f8a6ba9a0ef722dd4f850cd68b7f610a0d4f4ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Mon, 6 Jun 2022 17:45:15 +0200 Subject: [PATCH 2/9] Add unit tests --- .../Hosting/DotnetTestHostManager.cs | 51 +++++----- .../Hosting/DotnetTestHostManagerTests.cs | 98 +++++++++++++++++++ 2 files changed, 126 insertions(+), 23 deletions(-) diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs index 4ca2aa9f56..c4ef1c9600 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs @@ -471,29 +471,7 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo( // 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) { - const string prefix = "VSTEST_WINAPPHOST_"; - const string dotnetRoot = "DOTNET_ROOT"; - string vstestDotnetRootEnvName = $"{prefix}{dotnetRoot}(x86)"; - var vstestDotnetRootEnvValue = _environmentVariableHelper.GetEnvironmentVariable(vstestDotnetRootEnvName); - if (vstestDotnetRootEnvValue is null) - { - vstestDotnetRootEnvName = $"{prefix}{dotnetRoot}"; - vstestDotnetRootEnvValue = _environmentVariableHelper.GetEnvironmentVariable(vstestDotnetRootEnvName); - } - - if (vstestDotnetRootEnvValue != null) - { - string dotnetRootEnvName = Version.Parse(_targetFramework.Version) >= Version6_0 - ? $"{dotnetRoot}_{_architecture.ToString().ToUpperInvariant()}" - : vstestDotnetRootEnvName.Replace(prefix, string.Empty); - - EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Found '{vstestDotnetRootEnvName}' in env variables, value '{vstestDotnetRootEnvValue}', forwarding to '{dotnetRootEnvName}' (target framework is {_targetFramework.Name}, Version={_targetFramework.Version})."); - startInfo.EnvironmentVariables.Add(dotnetRootEnvName, vstestDotnetRootEnvValue); - } - else - { - EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Prefix '{prefix}*' not found in env variables"); - } + ForwardDotnetRootEnvironmentVariable(startInfo); } startInfo.WorkingDirectory = sourceDirectory; @@ -572,6 +550,33 @@ bool SilentlyForceToX64() } } + internal /* for testing purposes */ void ForwardDotnetRootEnvironmentVariable(TestProcessStartInfo startInfo) + { + const string prefix = "VSTEST_WINAPPHOST_"; + const string dotnetRoot = "DOTNET_ROOT"; + string vstestDotnetRootEnvName = $"{prefix}{dotnetRoot}(x86)"; + + var vstestDotnetRootEnvValue = _environmentVariableHelper.GetEnvironmentVariable(vstestDotnetRootEnvName); + if (vstestDotnetRootEnvValue is null) + { + vstestDotnetRootEnvName = $"{prefix}{dotnetRoot}"; + vstestDotnetRootEnvValue = _environmentVariableHelper.GetEnvironmentVariable(vstestDotnetRootEnvName); + + if (vstestDotnetRootEnvValue is null) + { + EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Prefix '{prefix}*' not found in env variables"); + return; + } + } + + string dotnetRootEnvName = Version.Parse(_targetFramework.Version) >= Version6_0 + ? $"{dotnetRoot}_{_processHelper.GetCurrentProcessArchitecture().ToString().ToUpperInvariant()}" + : vstestDotnetRootEnvName.Replace(prefix, string.Empty); + + EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Found '{vstestDotnetRootEnvName}' in env variables, value '{vstestDotnetRootEnvValue}', forwarding to '{dotnetRootEnvName}' (target framework is {_targetFramework.Name}, Version={_targetFramework.Version})."); + startInfo.EnvironmentVariables.Add(dotnetRootEnvName, vstestDotnetRootEnvValue); + } + /// public IEnumerable GetTestPlatformExtensions(IEnumerable sources, IEnumerable extensions) { diff --git a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs index 581c26ad93..596ef57814 100644 --- a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs +++ b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs @@ -987,6 +987,104 @@ public async Task CleanTestHostAsyncShouldNotThrowIfTestHostIsNotStarted() Assert.IsTrue(isVerified); } + [TestMethod] + [DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT(x86)", "DOTNET_ROOT(x86)")] + [DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT", "DOTNET_ROOT")] + public void ForwardDotnetRootEnvironmentVariableWhenTargetFrameworkIsLessThanNet6SetsCorrectEnvVariable(string envVarName, string expectedForwaredName) + { + // Arrange + const string envVarValue = "c:\\SomePath"; + _mockEnvironmentVariable.Reset(); + _mockEnvironmentVariable.Setup(x => x.GetEnvironmentVariable(envVarName)).Returns(envVarValue); + string runSettingsXml = """ + + + net5.0 + + + """; + _dotnetHostManager.Initialize(_mockMessageLogger.Object, runSettingsXml); + + var startInfo = new TestProcessStartInfo { EnvironmentVariables = new Dictionary() }; + // Sanity check + Assert.AreEqual(0, startInfo.EnvironmentVariables.Count); + + // Act + _dotnetHostManager.ForwardDotnetRootEnvironmentVariable(startInfo); + + // Assert + Assert.AreEqual(1, startInfo.EnvironmentVariables.Count); + Assert.IsTrue(startInfo.EnvironmentVariables.TryGetValue(expectedForwaredName, out var actualEnvVarValue)); + Assert.AreEqual(envVarValue, actualEnvVarValue); + } + + [TestMethod] + [DataRow("DOTNET_ROOT(x86)", "net5.0")] + [DataRow("DOTNET_ROOT(x86)", "net6.0")] + [DataRow("DOTNET_ROOT", "net5.0")] + [DataRow("DOTNET_ROOT", "net6.0")] + [DataRow("DOTNET_ROOT_X86", "net5.0")] + [DataRow("DOTNET_ROOT_X86", "net6.0")] + [DataRow("DOTNET_ROOT_X64", "net5.0")] + [DataRow("DOTNET_ROOT_X64", "net6.0")] + public void ForwardDotnetRootEnvironmentVariableWhenIncorrectEnvVarDoesNothing(string envVarName, string framework) + { + // Arrange + const string envVarValue = "c:\\SomePath"; + _mockEnvironmentVariable.Reset(); + _mockEnvironmentVariable.Setup(x => x.GetEnvironmentVariable(envVarName)).Returns(envVarValue); + string runSettingsXml = $""" + + + {framework} + + + """; + _dotnetHostManager.Initialize(_mockMessageLogger.Object, runSettingsXml); + + var startInfo = new TestProcessStartInfo { EnvironmentVariables = new Dictionary() }; + + // Act + _dotnetHostManager.ForwardDotnetRootEnvironmentVariable(startInfo); + + // Assert + Assert.AreEqual(0, startInfo.EnvironmentVariables.Count); + } + + [TestMethod] + [DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT(x86)", PlatformArchitecture.X86)] + [DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT(x86)", PlatformArchitecture.X64)] + [DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT", PlatformArchitecture.X86)] + [DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT", PlatformArchitecture.X64)] + public void ForwardDotnetRootEnvironmentVariableWhenTargetFrameworkIsGreaterOrEqualsToNet6SetsCorrectEnvVariable(string envVarName, PlatformArchitecture platformArchitecture) + { + // Arrange + const string envVarValue = "c:\\SomePath"; + _mockEnvironmentVariable.Reset(); + _mockEnvironmentVariable.Setup(x => x.GetEnvironmentVariable(envVarName)).Returns(envVarValue); + _mockProcessHelper.Setup(x => x.GetCurrentProcessArchitecture()).Returns(platformArchitecture); + string runSettingsXml = """ + + + net6.0 + + + """; + _dotnetHostManager.Initialize(_mockMessageLogger.Object, runSettingsXml); + + var startInfo = new TestProcessStartInfo { EnvironmentVariables = new Dictionary() }; + // Sanity check + Assert.AreEqual(0, startInfo.EnvironmentVariables.Count); + + // Act + _dotnetHostManager.ForwardDotnetRootEnvironmentVariable(startInfo); + + // Assert + Assert.AreEqual(1, startInfo.EnvironmentVariables.Count); + Assert.IsTrue(startInfo.EnvironmentVariables.TryGetValue($"DOTNET_ROOT_{platformArchitecture.ToString().ToUpperInvariant()}", out var actualEnvVarValue)); + Assert.AreEqual(envVarValue, actualEnvVarValue); + } + private void DotnetHostManagerExitCodeTesterHostExited(object? sender, HostProviderEventArgs e) { _errorMessage = e.Data.TrimEnd(Environment.NewLine.ToCharArray()); From db760b3601edf2ed1550d4978cd7342d383b3786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Wed, 8 Jun 2022 11:05:25 +0200 Subject: [PATCH 3/9] Add integration test --- .../DotnetArchitectureTests.cs | 26 ++++++++++++++ .../Net6Launches32BitsProcess.csproj | 22 ++++++++++++ .../Net6Launches32BitsProcess/Test32Bit.cs | 34 +++++++++++++++++++ test/TestAssets/TestAssets.sln | 32 +++++++++++++++++ test/TestAssets/TestProcess32/Program.cs | 4 +++ .../TestProcess32/TestProcess32.csproj | 11 ++++++ 6 files changed, 129 insertions(+) create mode 100644 test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureTests.cs create mode 100644 test/TestAssets/Net6Launches32BitsProcess/Net6Launches32BitsProcess.csproj create mode 100644 test/TestAssets/Net6Launches32BitsProcess/Test32Bit.cs create mode 100644 test/TestAssets/TestProcess32/Program.cs create mode 100644 test/TestAssets/TestProcess32/TestProcess32.csproj diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureTests.cs b/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureTests.cs new file mode 100644 index 0000000000..f9fff841d2 --- /dev/null +++ b/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureTests.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#if !NETFRAMEWORK + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.AcceptanceTests; + +[TestClass] +public class DotnetArchitectureTests : AcceptanceTestBase +{ + [TestMethod] + [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + public void DotnetTestProjectLaunching32BitsProcess(RunnerInfo runnerInfo) + { + SetTestEnvironment(_testEnvironment, runnerInfo); + + var projectPath = GetTestDllForFramework("Net6Launches32BitsProcess.dll", "net6.0"); + InvokeDotnetTest(projectPath); + + ExitCodeEquals(0); + } +} + +#endif diff --git a/test/TestAssets/Net6Launches32BitsProcess/Net6Launches32BitsProcess.csproj b/test/TestAssets/Net6Launches32BitsProcess/Net6Launches32BitsProcess.csproj new file mode 100644 index 0000000000..de815fc6f5 --- /dev/null +++ b/test/TestAssets/Net6Launches32BitsProcess/Net6Launches32BitsProcess.csproj @@ -0,0 +1,22 @@ + + + + + + + net6.0 + enable + enable + + + + + + + + + + + + + diff --git a/test/TestAssets/Net6Launches32BitsProcess/Test32Bit.cs b/test/TestAssets/Net6Launches32BitsProcess/Test32Bit.cs new file mode 100644 index 0000000000..4fc87921ad --- /dev/null +++ b/test/TestAssets/Net6Launches32BitsProcess/Test32Bit.cs @@ -0,0 +1,34 @@ +// 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.Diagnostics; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + + +namespace UnitTest; + +[TestClass] +public class Test32Bit +{ + [TestMethod] + public void TheTest() + { + var process = new Process(); + process.StartInfo = new ProcessStartInfo + { + FileName = "TestProcess32.exe", + RedirectStandardError = true, + RedirectStandardOutput = true + }; + + process.Start(); + var stderr = process.StandardError.ReadToEnd(); + var stdout = process.StandardOutput.ReadToEnd(); + + Console.WriteLine($"32bit stdout: {stdout}"); + Console.WriteLine($"32bit err: {stderr}"); + + Assert.IsTrue(string.IsNullOrEmpty(stderr)); + } +} diff --git a/test/TestAssets/TestAssets.sln b/test/TestAssets/TestAssets.sln index 8fae3a203e..a09619c558 100644 --- a/test/TestAssets/TestAssets.sln +++ b/test/TestAssets/TestAssets.sln @@ -124,6 +124,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tools", "Tools\Tools.csproj EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Perfy.TestAdapter", "performance\Perfy.TestAdapter\Perfy.TestAdapter.csproj", "{71BF7EC9-7BEE-4038-8F4E-87032FA4E995}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DOTNET_ROOT", "DOTNET_ROOT", "{C06EFF20-F1EA-42B7-B404-D8AB98AA78C0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestProcess32", "TestProcess32\TestProcess32.csproj", "{4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Net6Launches32BitsProcess", "Net6Launches32BitsProcess\Net6Launches32BitsProcess.csproj", "{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -806,6 +812,30 @@ Global {71BF7EC9-7BEE-4038-8F4E-87032FA4E995}.Release|x64.Build.0 = Release|Any CPU {71BF7EC9-7BEE-4038-8F4E-87032FA4E995}.Release|x86.ActiveCfg = Release|Any CPU {71BF7EC9-7BEE-4038-8F4E-87032FA4E995}.Release|x86.Build.0 = Release|Any CPU + {4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Debug|x64.ActiveCfg = Debug|Any CPU + {4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Debug|x64.Build.0 = Debug|Any CPU + {4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Debug|x86.ActiveCfg = Debug|Any CPU + {4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Debug|x86.Build.0 = Debug|Any CPU + {4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Release|Any CPU.Build.0 = Release|Any CPU + {4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Release|x64.ActiveCfg = Release|Any CPU + {4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Release|x64.Build.0 = Release|Any CPU + {4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Release|x86.ActiveCfg = Release|Any CPU + {4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Release|x86.Build.0 = Release|Any CPU + {A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Debug|x64.ActiveCfg = Debug|Any CPU + {A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Debug|x64.Build.0 = Debug|Any CPU + {A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Debug|x86.ActiveCfg = Debug|Any CPU + {A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Debug|x86.Build.0 = Debug|Any CPU + {A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Release|Any CPU.Build.0 = Release|Any CPU + {A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Release|x64.ActiveCfg = Release|Any CPU + {A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Release|x64.Build.0 = Release|Any CPU + {A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Release|x86.ActiveCfg = Release|Any CPU + {A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -826,6 +856,8 @@ Global {10AA955C-B412-41A8-899F-8609AAE19F61} = {2633D125-64A7-456C-AD37-F8A6B56C2403} {E166D337-4033-4209-863F-8F77675EAEE8} = {2633D125-64A7-456C-AD37-F8A6B56C2403} {71BF7EC9-7BEE-4038-8F4E-87032FA4E995} = {0C9CA869-32FD-4A9E-8885-E2E19786C746} + {4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506} = {C06EFF20-F1EA-42B7-B404-D8AB98AA78C0} + {A0F37C16-FB73-4538-8DAF-EBF6EB3251FA} = {C06EFF20-F1EA-42B7-B404-D8AB98AA78C0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D2334DAA-F7B2-450E-ABA4-FBC185152500} diff --git a/test/TestAssets/TestProcess32/Program.cs b/test/TestAssets/TestProcess32/Program.cs new file mode 100644 index 0000000000..3f858c1929 --- /dev/null +++ b/test/TestAssets/TestProcess32/Program.cs @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +Console.WriteLine($"Am I 32bit?: {!Environment.Is64BitProcess}"); diff --git a/test/TestAssets/TestProcess32/TestProcess32.csproj b/test/TestAssets/TestProcess32/TestProcess32.csproj new file mode 100644 index 0000000000..84f88f34b5 --- /dev/null +++ b/test/TestAssets/TestProcess32/TestProcess32.csproj @@ -0,0 +1,11 @@ + + + + x86 + Exe + net6.0 + enable + enable + + + From e55266d96031b22d0aa416d7e173f56023056483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Wed, 8 Jun 2022 11:07:53 +0200 Subject: [PATCH 4/9] Add ARM64 tests --- .../Hosting/DotnetTestHostManagerTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs index 596ef57814..9c65e5dbf5 100644 --- a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs +++ b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs @@ -1027,6 +1027,8 @@ public void ForwardDotnetRootEnvironmentVariableWhenTargetFrameworkIsLessThanNet [DataRow("DOTNET_ROOT_X86", "net6.0")] [DataRow("DOTNET_ROOT_X64", "net5.0")] [DataRow("DOTNET_ROOT_X64", "net6.0")] + [DataRow("DOTNET_ROOT_ARM64", "net5.0")] + [DataRow("DOTNET_ROOT_ARM64", "net6.0")] public void ForwardDotnetRootEnvironmentVariableWhenIncorrectEnvVarDoesNothing(string envVarName, string framework) { // Arrange From 76cfa219d2e879afa863a4f448fe00bb3fad9b88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Wed, 8 Jun 2022 13:50:53 +0200 Subject: [PATCH 5/9] Address review comments --- .../Hosting/DotnetTestHostManager.cs | 26 +++++++++++-- .../Hosting/DotnetTestHostManagerTests.cs | 37 +++++++++++++++++++ .../Net6Launches32BitsProcess/Test32Bit.cs | 14 ++++++- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs index c4ef1c9600..8cb733e6db 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs @@ -556,12 +556,15 @@ bool SilentlyForceToX64() const string dotnetRoot = "DOTNET_ROOT"; string vstestDotnetRootEnvName = $"{prefix}{dotnetRoot}(x86)"; + // Check if VSTEST_WINAPPHOST_DOTNET_ROOT(x86) is set, if not then looks for VSTEST_WINAPPHOST_DOTNET_ROOT. + // If none of these variables is set we exit as we have nothing to forward. var vstestDotnetRootEnvValue = _environmentVariableHelper.GetEnvironmentVariable(vstestDotnetRootEnvName); if (vstestDotnetRootEnvValue is null) { vstestDotnetRootEnvName = $"{prefix}{dotnetRoot}"; vstestDotnetRootEnvValue = _environmentVariableHelper.GetEnvironmentVariable(vstestDotnetRootEnvName); + // None of the forwarding environment variables are set so exit. if (vstestDotnetRootEnvValue is null) { EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Prefix '{prefix}*' not found in env variables"); @@ -569,9 +572,26 @@ bool SilentlyForceToX64() } } - string dotnetRootEnvName = Version.Parse(_targetFramework.Version) >= Version6_0 - ? $"{dotnetRoot}_{_processHelper.GetCurrentProcessArchitecture().ToString().ToUpperInvariant()}" - : vstestDotnetRootEnvName.Replace(prefix, string.Empty); + // For .NET 6.0 onward, the DOTNET_ROOT* environment variable to set was changed. + // This implementation is based on the logic defined in SDK: + // https://github.com/dotnet/sdk/blob/c3f8d746f4d5cd87f462d711a3caa7a4f6621826/src/Cli/dotnet/commands/dotnet-run/RunCommand.cs#L264-L279 + string dotnetRootEnvName; + if (Version.Parse(_targetFramework.Version) >= Version6_0) + { + dotnetRootEnvName = $"{dotnetRoot}_{_processHelper.GetCurrentProcessArchitecture().ToString().ToUpperInvariant()}"; + + // SDK side of TP is not checking for the .NET6.0+ environment variables so we want to make sure we + // are not overriding user definition. + if (_environmentVariableHelper.GetEnvironmentVariable(dotnetRootEnvName) is not null) + { + EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Found '{vstestDotnetRootEnvName}' in env variables but also found '{dotnetRootEnvName}'. Skipping forwarding."); + return; + } + } + else + { + dotnetRootEnvName = vstestDotnetRootEnvName.Replace(prefix, string.Empty); + } EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Found '{vstestDotnetRootEnvName}' in env variables, value '{vstestDotnetRootEnvValue}', forwarding to '{dotnetRootEnvName}' (target framework is {_targetFramework.Name}, Version={_targetFramework.Version})."); startInfo.EnvironmentVariables.Add(dotnetRootEnvName, vstestDotnetRootEnvValue); diff --git a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs index 9c65e5dbf5..f43901dbbe 100644 --- a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs +++ b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs @@ -1087,6 +1087,43 @@ public void ForwardDotnetRootEnvironmentVariableWhenTargetFrameworkIsGreaterOrEq Assert.AreEqual(envVarValue, actualEnvVarValue); } + [TestMethod] + [DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT(x86)", PlatformArchitecture.X86)] + [DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT(x86)", PlatformArchitecture.X64)] + [DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT(x86)", PlatformArchitecture.ARM64)] + [DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT", PlatformArchitecture.X86)] + [DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT", PlatformArchitecture.X64)] + [DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT", PlatformArchitecture.ARM64)] + public void ForwardDotnetRootEnvironmentVariableWhenTargetFrameworkIsGreaterOrEqualsToNet6DoesNotOverrideEnvVar(string envVarName, PlatformArchitecture platformArchitecture) + { + // Arrange + const string expectedEnvVarValue = "c:\\SomePath"; + const string nonForwardedEnvVarValue = "C:\\SomeOtherPath"; + var expectedForwardedEnvVarName = $"DOTNET_ROOT_{platformArchitecture.ToString().ToUpperInvariant()}"; + _mockEnvironmentVariable.Reset(); + _mockEnvironmentVariable.Setup(x => x.GetEnvironmentVariable(envVarName)).Returns(expectedEnvVarValue); + _mockEnvironmentVariable.Setup(x => x.GetEnvironmentVariable(expectedForwardedEnvVarName)).Returns(nonForwardedEnvVarValue); + _mockProcessHelper.Setup(x => x.GetCurrentProcessArchitecture()).Returns(platformArchitecture); + string runSettingsXml = """ + + + net6.0 + + + """; + _dotnetHostManager.Initialize(_mockMessageLogger.Object, runSettingsXml); + + var startInfo = new TestProcessStartInfo { EnvironmentVariables = new Dictionary() }; + // Sanity check + Assert.AreEqual(0, startInfo.EnvironmentVariables.Count); + + // Act + _dotnetHostManager.ForwardDotnetRootEnvironmentVariable(startInfo); + + // Assert + Assert.AreEqual(0, startInfo.EnvironmentVariables.Count); + } + private void DotnetHostManagerExitCodeTesterHostExited(object? sender, HostProviderEventArgs e) { _errorMessage = e.Data.TrimEnd(Environment.NewLine.ToCharArray()); diff --git a/test/TestAssets/Net6Launches32BitsProcess/Test32Bit.cs b/test/TestAssets/Net6Launches32BitsProcess/Test32Bit.cs index 4fc87921ad..db2749710d 100644 --- a/test/TestAssets/Net6Launches32BitsProcess/Test32Bit.cs +++ b/test/TestAssets/Net6Launches32BitsProcess/Test32Bit.cs @@ -5,7 +5,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; - namespace UnitTest; [TestClass] @@ -14,6 +13,8 @@ public class Test32Bit [TestMethod] public void TheTest() { + // Test is based on reproducer from following SDK issue: + // https://github.com/dotnet/sdk/issues/22647 var process = new Process(); process.StartInfo = new ProcessStartInfo { @@ -29,6 +30,15 @@ public void TheTest() Console.WriteLine($"32bit stdout: {stdout}"); Console.WriteLine($"32bit err: {stderr}"); - Assert.IsTrue(string.IsNullOrEmpty(stderr)); + Assert.IsTrue(string.IsNullOrEmpty(stderr), + $"There was some error in process run: {stderr}"); + Assert.IsTrue(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_ROOT")), + "Env var DOTNET_ROOT was found."); + Assert.IsTrue(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_ROOT(x86)")), + "Env var DOTNET_ROOT(x86) was found."); + Assert.IsTrue(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_ROOT_X86")), + "Env var DOTNET_ROOT_X86 was found."); + Assert.IsFalse(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_ROOT_X64")), + "Env var DOTNET_ROOT_X64 was not found."); } } From 494378a4bf60849e4d746dfd18f269f8597cebdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Wed, 8 Jun 2022 17:36:41 +0200 Subject: [PATCH 6/9] Update integration test - Test behavior on CI --- scripts/common.lib.ps1 | 16 ++-- .../AcceptanceTestBase.cs | 23 ++++++ .../DotnetArchitectureTests.cs | 79 ++++++++++++++++++- .../IntegrationTestBase.cs | 8 +- .../ProjectLaunch32BitsProcess.csproj} | 4 +- .../Test32Bit.cs | 15 ++-- test/TestAssets/TestAssets.sln | 2 +- test/TestAssets/TestProcess32/Program.cs | 1 + .../TestProcess32/TestProcess32.csproj | 3 +- 9 files changed, 124 insertions(+), 27 deletions(-) rename test/TestAssets/{Net6Launches32BitsProcess/Net6Launches32BitsProcess.csproj => ProjectLaunch32BitsProcess/ProjectLaunch32BitsProcess.csproj} (86%) rename test/TestAssets/{Net6Launches32BitsProcess => ProjectLaunch32BitsProcess}/Test32Bit.cs (64%) diff --git a/scripts/common.lib.ps1 b/scripts/common.lib.ps1 index bd5adaba25..0c15522af8 100644 --- a/scripts/common.lib.ps1 +++ b/scripts/common.lib.ps1 @@ -60,7 +60,7 @@ function Write-Log { [string] $Level = "Success" ) - + if ($message) { $color = if ("Success" -eq $Level) { "Green" } else { "Red" } @@ -116,7 +116,11 @@ function Install-DotNetCli & $dotnetInstallScript -InstallDir "${dotnetInstallPath}_x86" -Runtime 'dotnet' -Channel '5.0' -Architecture x86 -NoPath -Version '5.0.16' & $dotnetInstallScript -InstallDir "${dotnetInstallPath}_x86" -Runtime 'dotnet' -Channel '6.0' -Architecture x86 -NoPath -Version '6.0.4' & $dotnetInstallScript -InstallDir "${dotnetInstallPath}_x86" -Channel '7.0' -Architecture x86 -NoPath -Version $env:DOTNET_CLI_VERSION - + + # The SDKs listed below are used for some of the acceptance tests + & $dotnetInstallScript -InstallDir "${dotnetInstallPath}_x86" -Channel '5.0' -Architecture x86 -NoPath -Version '5.0.100' + & $dotnetInstallScript -InstallDir "${dotnetInstallPath}_x86" -Channel '6.0' -Architecture x86 -NoPath -Version '6.0.100' + $env:DOTNET_ROOT= $dotnetInstallPath ${env:DOTNET_ROOT(x86)} = "${dotnetInstallPath}_x86" @@ -260,10 +264,10 @@ public class ProcessOutputter AppendLine(e.Data); if (suppressOutput || e.Data == null) - { + { return; } - + // These handlers can run at the same time, // without lock they sometimes grab the color the other // one set. @@ -278,7 +282,7 @@ public class ProcessOutputter // one extra space before the word, to avoid highlighting // warnaserror and similar parameters that are not actual errors // - // The comparison is not done using the insensitive overload because that + // The comparison is not done using the insensitive overload because that // is too new for PowerShell 5 compiler var lineToLower = line.ToLowerInvariant(); Console.ForegroundColor = lineToLower.Contains(" warning") @@ -387,4 +391,4 @@ function Start-InlineProcess { $process.remove_ErrorDataReceived($errorDataReceived) $process.Dispose() } -} \ No newline at end of file +} diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/AcceptanceTestBase.cs b/test/Microsoft.TestPlatform.AcceptanceTests/AcceptanceTestBase.cs index df3e032d98..d6e944f5b6 100644 --- a/test/Microsoft.TestPlatform.AcceptanceTests/AcceptanceTestBase.cs +++ b/test/Microsoft.TestPlatform.AcceptanceTests/AcceptanceTestBase.cs @@ -187,4 +187,27 @@ protected string GetIsolatedTestAsset(string assetName) return Path.Combine(TempDirectory.Path, assetName); } + + protected string GetIsolatedTestDllForFramework(string assetName, string targetFramework) + { + var testDllPath = GetTestDllForFramework(assetName, targetFramework); + CopyFilesRecursively(Path.GetDirectoryName(testDllPath), "*", TempDirectory.Path); + return Path.Combine(TempDirectory.Path, Path.GetFileName(testDllPath)); + } + + private static void CopyFilesRecursively(string sourceDirectory, string searchPattern, string targetDirectory) + { + foreach (var subDirectory in Directory.EnumerateDirectories(sourceDirectory)) + { + var directoryName = Path.GetFileName(subDirectory); + var newTargetDirectory = Path.Combine(targetDirectory, directoryName); + Directory.CreateDirectory(newTargetDirectory); + CopyFilesRecursively(subDirectory, searchPattern, newTargetDirectory); + } + + foreach (var file in Directory.EnumerateFiles(sourceDirectory, searchPattern)) + { + File.Copy(file, Path.Combine(targetDirectory, Path.GetFileName(file))); + } + } } diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureTests.cs b/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureTests.cs index f9fff841d2..ee39bc7191 100644 --- a/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureTests.cs +++ b/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureTests.cs @@ -3,6 +3,9 @@ #if !NETFRAMEWORK +using System.Collections.Generic; +using System.IO; + using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.TestPlatform.AcceptanceTests; @@ -10,14 +13,82 @@ namespace Microsoft.TestPlatform.AcceptanceTests; [TestClass] public class DotnetArchitectureTests : AcceptanceTestBase { + public static IEnumerable GetRunnerAndDotnetRootEnvVar() + { + var runnerDataSource = new NetCoreTargetFrameworkDataSource(useDesktopRunner: false); + foreach (var entry in runnerDataSource.GetData(null)) + { + yield return new object[] { entry[0], "DOTNET_ROOT(x86)", "dotnet_x86" }; + yield return new object[] { entry[0], "DOTNET_ROOT_X86", "dotnet_x86" }; + yield return new object[] { entry[0], "DOTNET_ROOT", "dotnet" }; + yield return new object[] { entry[0], "DOTNET_ROOT_X64", "dotnet" }; + } + } + + [TestMethod] + [DynamicData(nameof(GetRunnerAndDotnetRootEnvVar), DynamicDataSourceType.Method)] + public void DotnetTestWithNet6ProjectLaunching32BitsProcess(RunnerInfo runnerInfo, string envVarName, string dotnetSubFolder) + { + SetTestEnvironment(_testEnvironment, runnerInfo); + + // We want some isolated directory because we are going to pin the SDK used to ensure + // the dotnet directory detection mechanism is the correct one. + var dllPath = GetIsolatedTestDllForFramework("ProjectLaunch32BitsProcess.dll", "net6.0"); + File.WriteAllText(Path.Combine(Path.GetDirectoryName(dllPath)!, "global.json"), @"{ + ""sdk"": { + ""version"": ""6.0.100"" + }, + ""tools"": { + ""dotnet"": ""6.0.100"" + } +}"); + + var dotnetX86LocalPath = Path.Combine(_testEnvironment.ToolsDirectory, dotnetSubFolder); + var env = new Dictionary + { + // If we don't set DOTNET_ROOT/DOTNET_ROOT(x86), the test result depends on whether or not + // there is a global x86 .NET installed. + [envVarName] = dotnetX86LocalPath, + // Used by test to assert which env var is supposed to be found. + ["EXPECTED_ENV_VAR_NAME"] = envVarName, + ["EXPECTED_ENV_VAR_VALUE"] = dotnetX86LocalPath, + }; + InvokeDotnetTest(dllPath, env, useDotnetFromTools: true); + + ExitCodeEquals(0); + } + [TestMethod] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] - public void DotnetTestProjectLaunching32BitsProcess(RunnerInfo runnerInfo) + [DynamicData(nameof(GetRunnerAndDotnetRootEnvVar), DynamicDataSourceType.Method)] + public void DotnetTestWithNet5ProjectLaunching32BitsProcess(RunnerInfo runnerInfo, string envVarName, string dotnetSubFolder) { SetTestEnvironment(_testEnvironment, runnerInfo); - var projectPath = GetTestDllForFramework("Net6Launches32BitsProcess.dll", "net6.0"); - InvokeDotnetTest(projectPath); + // We want some isolated directory because we are going to pin the SDK used to ensure + // the dotnet directory detection mechanism is the correct one. + var dllPath = GetIsolatedTestDllForFramework("ProjectLaunch32BitsProcess.dll", "net5.0"); + File.WriteAllText(Path.Combine(Path.GetDirectoryName(dllPath)!, "global.json"), @"{ + ""sdk"": { + ""version"": ""5.0.100"" + }, + ""tools"": { + ""dotnet"": ""5.0.100"" + } +}"); + + var dotnetX86LocalPath = Path.Combine(_testEnvironment.ToolsDirectory, dotnetSubFolder); + var env = new Dictionary + { + // If we don't set DOTNET_ROOT/DOTNET_ROOT(x86), the test result depends on whether or not + // there is a global x86 .NET installed. + [envVarName] = dotnetX86LocalPath, + // Used by test to assert which env var is supposed to be found. + ["EXPECTED_ENV_VAR_NAME"] = envVarName, + ["EXPECTED_ENV_VAR_VALUE"] = dotnetX86LocalPath, + //["COREHOST_TRACE"] = "1", + //["COREHOST_TRACEFILE"] = @"C:\src\temp\net5.tx", + }; + InvokeDotnetTest(dllPath, env, useDotnetFromTools: true); ExitCodeEquals(0); } diff --git a/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs b/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs index 7ff7338257..c253e607e1 100644 --- a/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs @@ -193,7 +193,7 @@ public void InvokeVsTest(string arguments, Dictionary? environme /// Invokes our local copy of dotnet that is patched with artifacts from the build with specified arguments. /// /// Arguments provided to vstest.console.exe - public void InvokeDotnetTest(string arguments, Dictionary? environmentVariables = null) + public void InvokeDotnetTest(string arguments, Dictionary? environmentVariables = null, bool useDotnetFromTools = false) { var debugEnvironmentVariables = AddDebugEnvironmentVariables(environmentVariables); @@ -208,7 +208,7 @@ public void InvokeDotnetTest(string arguments, Dictionary? envir // https://github.com/dotnet/sdk/blob/main/src/Cli/dotnet/commands/dotnet-test/VSTestForwardingApp.cs#L30-L39 debugEnvironmentVariables["VSTEST_CONSOLE_PATH"] = vstestConsolePath; - ExecutePatchedDotnet("test", arguments, out _standardTestOutput, out _standardTestError, out _runnerExitCode, debugEnvironmentVariables); + ExecutePatchedDotnet("test", arguments, out _standardTestOutput, out _standardTestError, out _runnerExitCode, debugEnvironmentVariables, useDotnetFromTools); FormatStandardOutCome(); } @@ -762,7 +762,7 @@ protected void ExecuteVsTestConsole(string args, out string stdOut, out string s /// /// private void ExecutePatchedDotnet(string command, string args, out string stdOut, out string stdError, out int exitCode, - Dictionary? environmentVariables = null) + Dictionary? environmentVariables = null, bool useDotnetFromTools = false) { if (environmentVariables is null) { @@ -772,7 +772,7 @@ private void ExecutePatchedDotnet(string command, string args, out string stdOut environmentVariables["DOTNET_MULTILEVEL_LOOKUP"] = "0"; var executablePath = IsWindows ? @"dotnet\dotnet.exe" : @"dotnet-linux/dotnet"; - var patchedDotnetPath = Path.Combine(_testEnvironment.TestArtifactsDirectory, executablePath); + var patchedDotnetPath = Path.Combine(useDotnetFromTools ? _testEnvironment.ToolsDirectory : _testEnvironment.TestArtifactsDirectory, executablePath); ExecuteApplication(patchedDotnetPath, string.Join(" ", command, args), out stdOut, out stdError, out exitCode, environmentVariables); } diff --git a/test/TestAssets/Net6Launches32BitsProcess/Net6Launches32BitsProcess.csproj b/test/TestAssets/ProjectLaunch32BitsProcess/ProjectLaunch32BitsProcess.csproj similarity index 86% rename from test/TestAssets/Net6Launches32BitsProcess/Net6Launches32BitsProcess.csproj rename to test/TestAssets/ProjectLaunch32BitsProcess/ProjectLaunch32BitsProcess.csproj index de815fc6f5..0e3ae170ab 100644 --- a/test/TestAssets/Net6Launches32BitsProcess/Net6Launches32BitsProcess.csproj +++ b/test/TestAssets/ProjectLaunch32BitsProcess/ProjectLaunch32BitsProcess.csproj @@ -4,9 +4,9 @@ - net6.0 - enable + net5.0;net6.0 enable + Preview diff --git a/test/TestAssets/Net6Launches32BitsProcess/Test32Bit.cs b/test/TestAssets/ProjectLaunch32BitsProcess/Test32Bit.cs similarity index 64% rename from test/TestAssets/Net6Launches32BitsProcess/Test32Bit.cs rename to test/TestAssets/ProjectLaunch32BitsProcess/Test32Bit.cs index db2749710d..36235a2da4 100644 --- a/test/TestAssets/Net6Launches32BitsProcess/Test32Bit.cs +++ b/test/TestAssets/ProjectLaunch32BitsProcess/Test32Bit.cs @@ -1,6 +1,7 @@ // 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.Diagnostics; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -32,13 +33,11 @@ public void TheTest() Assert.IsTrue(string.IsNullOrEmpty(stderr), $"There was some error in process run: {stderr}"); - Assert.IsTrue(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_ROOT")), - "Env var DOTNET_ROOT was found."); - Assert.IsTrue(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_ROOT(x86)")), - "Env var DOTNET_ROOT(x86) was found."); - Assert.IsTrue(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_ROOT_X86")), - "Env var DOTNET_ROOT_X86 was found."); - Assert.IsFalse(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_ROOT_X64")), - "Env var DOTNET_ROOT_X64 was not found."); + + var expectedEnvVarName = Environment.GetEnvironmentVariable("EXPECTED_ENV_VAR_NAME"); + Assert.IsNotNull(expectedEnvVarName); + var expectedEnvVarValue = Environment.GetEnvironmentVariable("EXPECTED_ENV_VAR_VALUE"); + Assert.IsNotNull(expectedEnvVarValue); + Assert.AreEqual(expectedEnvVarValue, Environment.GetEnvironmentVariable(expectedEnvVarName)); } } diff --git a/test/TestAssets/TestAssets.sln b/test/TestAssets/TestAssets.sln index a09619c558..32b4fcd42a 100644 --- a/test/TestAssets/TestAssets.sln +++ b/test/TestAssets/TestAssets.sln @@ -128,7 +128,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DOTNET_ROOT", "DOTNET_ROOT" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestProcess32", "TestProcess32\TestProcess32.csproj", "{4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Net6Launches32BitsProcess", "Net6Launches32BitsProcess\Net6Launches32BitsProcess.csproj", "{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjectLaunch32BitsProcess", "ProjectLaunch32BitsProcess\ProjectLaunch32BitsProcess.csproj", "{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/test/TestAssets/TestProcess32/Program.cs b/test/TestAssets/TestProcess32/Program.cs index 3f858c1929..90a4752468 100644 --- a/test/TestAssets/TestProcess32/Program.cs +++ b/test/TestAssets/TestProcess32/Program.cs @@ -1,4 +1,5 @@ // 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; Console.WriteLine($"Am I 32bit?: {!Environment.Is64BitProcess}"); diff --git a/test/TestAssets/TestProcess32/TestProcess32.csproj b/test/TestAssets/TestProcess32/TestProcess32.csproj index 84f88f34b5..0a5f4d7e1a 100644 --- a/test/TestAssets/TestProcess32/TestProcess32.csproj +++ b/test/TestAssets/TestProcess32/TestProcess32.csproj @@ -3,8 +3,7 @@ x86 Exe - net6.0 - enable + net5.0;net6.0 enable From 7f21f5333af4dd5e2c72d54d4558634fe9ae106c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Wed, 8 Jun 2022 20:26:31 +0200 Subject: [PATCH 7/9] Make integration using the right logic --- scripts/common.lib.ps1 | 2 + .../DotnetArchitectureTests.cs | 79 ++++++------------- .../IntegrationTestBase.cs | 8 +- .../ProjectLaunch32BitsProcess/Test32Bit.cs | 16 ++-- 4 files changed, 37 insertions(+), 68 deletions(-) diff --git a/scripts/common.lib.ps1 b/scripts/common.lib.ps1 index 0c15522af8..da29919945 100644 --- a/scripts/common.lib.ps1 +++ b/scripts/common.lib.ps1 @@ -118,7 +118,9 @@ function Install-DotNetCli & $dotnetInstallScript -InstallDir "${dotnetInstallPath}_x86" -Channel '7.0' -Architecture x86 -NoPath -Version $env:DOTNET_CLI_VERSION # The SDKs listed below are used for some of the acceptance tests + & $dotnetInstallScript -InstallDir "${dotnetInstallPath}" -Channel '5.0' -Architecture x64 -NoPath -Version '5.0.100' & $dotnetInstallScript -InstallDir "${dotnetInstallPath}_x86" -Channel '5.0' -Architecture x86 -NoPath -Version '5.0.100' + & $dotnetInstallScript -InstallDir "${dotnetInstallPath}" -Channel '6.0' -Architecture x64 -NoPath -Version '6.0.100' & $dotnetInstallScript -InstallDir "${dotnetInstallPath}_x86" -Channel '6.0' -Architecture x86 -NoPath -Version '6.0.100' $env:DOTNET_ROOT= $dotnetInstallPath diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureTests.cs b/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureTests.cs index ee39bc7191..bdd6d31e1b 100644 --- a/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureTests.cs +++ b/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureTests.cs @@ -18,77 +18,42 @@ public static IEnumerable GetRunnerAndDotnetRootEnvVar() var runnerDataSource = new NetCoreTargetFrameworkDataSource(useDesktopRunner: false); foreach (var entry in runnerDataSource.GetData(null)) { - yield return new object[] { entry[0], "DOTNET_ROOT(x86)", "dotnet_x86" }; - yield return new object[] { entry[0], "DOTNET_ROOT_X86", "dotnet_x86" }; - yield return new object[] { entry[0], "DOTNET_ROOT", "dotnet" }; - yield return new object[] { entry[0], "DOTNET_ROOT_X64", "dotnet" }; - } - } - - [TestMethod] - [DynamicData(nameof(GetRunnerAndDotnetRootEnvVar), DynamicDataSourceType.Method)] - public void DotnetTestWithNet6ProjectLaunching32BitsProcess(RunnerInfo runnerInfo, string envVarName, string dotnetSubFolder) - { - SetTestEnvironment(_testEnvironment, runnerInfo); - - // We want some isolated directory because we are going to pin the SDK used to ensure - // the dotnet directory detection mechanism is the correct one. - var dllPath = GetIsolatedTestDllForFramework("ProjectLaunch32BitsProcess.dll", "net6.0"); - File.WriteAllText(Path.Combine(Path.GetDirectoryName(dllPath)!, "global.json"), @"{ - ""sdk"": { - ""version"": ""6.0.100"" - }, - ""tools"": { - ""dotnet"": ""6.0.100"" - } -}"); + yield return new object[] { entry[0], "net6.0", "6.0.100", "DOTNET_ROOT_X86" }; + yield return new object[] { entry[0], "net6.0", "6.0.100", "DOTNET_ROOT(x86)" }; + yield return new object[] { entry[0], "net6.0", "6.0.100", "DOTNET_ROOT" }; - var dotnetX86LocalPath = Path.Combine(_testEnvironment.ToolsDirectory, dotnetSubFolder); - var env = new Dictionary - { - // If we don't set DOTNET_ROOT/DOTNET_ROOT(x86), the test result depends on whether or not - // there is a global x86 .NET installed. - [envVarName] = dotnetX86LocalPath, - // Used by test to assert which env var is supposed to be found. - ["EXPECTED_ENV_VAR_NAME"] = envVarName, - ["EXPECTED_ENV_VAR_VALUE"] = dotnetX86LocalPath, - }; - InvokeDotnetTest(dllPath, env, useDotnetFromTools: true); - - ExitCodeEquals(0); + yield return new object[] { entry[0], "net5.0", "5.0.100", "DOTNET_ROOT_X86" }; + yield return new object[] { entry[0], "net5.0", "5.0.100", "DOTNET_ROOT(x86)" }; + yield return new object[] { entry[0], "net5.0", "5.0.100", "DOTNET_ROOT" }; + } } [TestMethod] [DynamicData(nameof(GetRunnerAndDotnetRootEnvVar), DynamicDataSourceType.Method)] - public void DotnetTestWithNet5ProjectLaunching32BitsProcess(RunnerInfo runnerInfo, string envVarName, string dotnetSubFolder) + public void Run32BitsProcessFrom64BitsDotnet(RunnerInfo runnerInfo, string targetFramework, string sdkVersion, string dotnetRootEnvVarName) { SetTestEnvironment(_testEnvironment, runnerInfo); // We want some isolated directory because we are going to pin the SDK used to ensure // the dotnet directory detection mechanism is the correct one. - var dllPath = GetIsolatedTestDllForFramework("ProjectLaunch32BitsProcess.dll", "net5.0"); - File.WriteAllText(Path.Combine(Path.GetDirectoryName(dllPath)!, "global.json"), @"{ - ""sdk"": { - ""version"": ""5.0.100"" - }, - ""tools"": { - ""dotnet"": ""5.0.100"" - } -}"); - - var dotnetX86LocalPath = Path.Combine(_testEnvironment.ToolsDirectory, dotnetSubFolder); + var dllPath = GetIsolatedTestDllForFramework("ProjectLaunch32BitsProcess.dll", targetFramework); + var isolatedDirectory = Path.GetDirectoryName(dllPath)!; + File.WriteAllText(Path.Combine(isolatedDirectory, "global.json"), $@"{{ + ""sdk"": {{ + ""version"": ""{sdkVersion}"" + }}, + ""tools"": {{ + ""dotnet"": ""{sdkVersion}"" + }} +}}"); + + var dotnetX86LocalPath = Path.Combine(_testEnvironment.ToolsDirectory, "dotnet_x86"); var env = new Dictionary { - // If we don't set DOTNET_ROOT/DOTNET_ROOT(x86), the test result depends on whether or not - // there is a global x86 .NET installed. - [envVarName] = dotnetX86LocalPath, - // Used by test to assert which env var is supposed to be found. - ["EXPECTED_ENV_VAR_NAME"] = envVarName, + ["EXPECTED_ENV_VAR_NAME"] = dotnetRootEnvVarName, ["EXPECTED_ENV_VAR_VALUE"] = dotnetX86LocalPath, - //["COREHOST_TRACE"] = "1", - //["COREHOST_TRACEFILE"] = @"C:\src\temp\net5.tx", }; - InvokeDotnetTest(dllPath, env, useDotnetFromTools: true); + InvokeDotnetTest(dllPath, env, useDotnetFromTools: true, workingDirectory: isolatedDirectory); ExitCodeEquals(0); } diff --git a/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs b/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs index c253e607e1..7395967239 100644 --- a/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs @@ -193,7 +193,7 @@ public void InvokeVsTest(string arguments, Dictionary? environme /// Invokes our local copy of dotnet that is patched with artifacts from the build with specified arguments. /// /// Arguments provided to vstest.console.exe - public void InvokeDotnetTest(string arguments, Dictionary? environmentVariables = null, bool useDotnetFromTools = false) + public void InvokeDotnetTest(string arguments, Dictionary? environmentVariables = null, bool useDotnetFromTools = false, string? workingDirectory = null) { var debugEnvironmentVariables = AddDebugEnvironmentVariables(environmentVariables); @@ -208,7 +208,7 @@ public void InvokeDotnetTest(string arguments, Dictionary? envir // https://github.com/dotnet/sdk/blob/main/src/Cli/dotnet/commands/dotnet-test/VSTestForwardingApp.cs#L30-L39 debugEnvironmentVariables["VSTEST_CONSOLE_PATH"] = vstestConsolePath; - ExecutePatchedDotnet("test", arguments, out _standardTestOutput, out _standardTestError, out _runnerExitCode, debugEnvironmentVariables, useDotnetFromTools); + ExecutePatchedDotnet("test", arguments, out _standardTestOutput, out _standardTestError, out _runnerExitCode, debugEnvironmentVariables, useDotnetFromTools, workingDirectory); FormatStandardOutCome(); } @@ -762,7 +762,7 @@ protected void ExecuteVsTestConsole(string args, out string stdOut, out string s /// /// private void ExecutePatchedDotnet(string command, string args, out string stdOut, out string stdError, out int exitCode, - Dictionary? environmentVariables = null, bool useDotnetFromTools = false) + Dictionary? environmentVariables = null, bool useDotnetFromTools = false, string? workingDirectory = null) { if (environmentVariables is null) { @@ -773,7 +773,7 @@ private void ExecutePatchedDotnet(string command, string args, out string stdOut var executablePath = IsWindows ? @"dotnet\dotnet.exe" : @"dotnet-linux/dotnet"; var patchedDotnetPath = Path.Combine(useDotnetFromTools ? _testEnvironment.ToolsDirectory : _testEnvironment.TestArtifactsDirectory, executablePath); - ExecuteApplication(patchedDotnetPath, string.Join(" ", command, args), out stdOut, out stdError, out exitCode, environmentVariables); + ExecuteApplication(patchedDotnetPath, string.Join(" ", command, args), out stdOut, out stdError, out exitCode, environmentVariables, workingDirectory); } protected static void ExecuteApplication(string path, string args, out string stdOut, out string stdError, out int exitCode, diff --git a/test/TestAssets/ProjectLaunch32BitsProcess/Test32Bit.cs b/test/TestAssets/ProjectLaunch32BitsProcess/Test32Bit.cs index 36235a2da4..41f064a063 100644 --- a/test/TestAssets/ProjectLaunch32BitsProcess/Test32Bit.cs +++ b/test/TestAssets/ProjectLaunch32BitsProcess/Test32Bit.cs @@ -21,9 +21,17 @@ public void TheTest() { FileName = "TestProcess32.exe", RedirectStandardError = true, - RedirectStandardOutput = true + RedirectStandardOutput = true, }; + var envVarName = Environment.GetEnvironmentVariable("EXPECTED_ENV_VAR_NAME"); + Assert.IsNotNull(envVarName, "Calling process didn't set EXPECTED_ENV_VAR_NAME."); + var envVarValue = Environment.GetEnvironmentVariable("EXPECTED_ENV_VAR_VALUE"); + Assert.IsNotNull(envVarValue, "Calling process didn't set EXPECTED_ENV_VAR_VALUE."); + // Set the DOTNET_ROOT* env variable so that the 32bits process can locate dotnet + // even if there is no global installation. + process.StartInfo.EnvironmentVariables[envVarName] = envVarValue; + process.Start(); var stderr = process.StandardError.ReadToEnd(); var stdout = process.StandardOutput.ReadToEnd(); @@ -33,11 +41,5 @@ public void TheTest() Assert.IsTrue(string.IsNullOrEmpty(stderr), $"There was some error in process run: {stderr}"); - - var expectedEnvVarName = Environment.GetEnvironmentVariable("EXPECTED_ENV_VAR_NAME"); - Assert.IsNotNull(expectedEnvVarName); - var expectedEnvVarValue = Environment.GetEnvironmentVariable("EXPECTED_ENV_VAR_VALUE"); - Assert.IsNotNull(expectedEnvVarValue); - Assert.AreEqual(expectedEnvVarValue, Environment.GetEnvironmentVariable(expectedEnvVarName)); } } From ca8c49bfb9ed64163e89a249f860fa8ba1416f9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Thu, 9 Jun 2022 09:16:59 +0200 Subject: [PATCH 8/9] Remove integration test as there is no way to make it run on CI --- scripts/common.lib.ps1 | 6 -- .../AcceptanceTestBase.cs | 23 ------- .../DotnetArchitectureTests.cs | 62 ------------------- .../ProjectLaunch32BitsProcess/Test32Bit.cs | 27 ++++++-- 4 files changed, 21 insertions(+), 97 deletions(-) delete mode 100644 test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureTests.cs diff --git a/scripts/common.lib.ps1 b/scripts/common.lib.ps1 index da29919945..1e44242bd0 100644 --- a/scripts/common.lib.ps1 +++ b/scripts/common.lib.ps1 @@ -117,12 +117,6 @@ function Install-DotNetCli & $dotnetInstallScript -InstallDir "${dotnetInstallPath}_x86" -Runtime 'dotnet' -Channel '6.0' -Architecture x86 -NoPath -Version '6.0.4' & $dotnetInstallScript -InstallDir "${dotnetInstallPath}_x86" -Channel '7.0' -Architecture x86 -NoPath -Version $env:DOTNET_CLI_VERSION - # The SDKs listed below are used for some of the acceptance tests - & $dotnetInstallScript -InstallDir "${dotnetInstallPath}" -Channel '5.0' -Architecture x64 -NoPath -Version '5.0.100' - & $dotnetInstallScript -InstallDir "${dotnetInstallPath}_x86" -Channel '5.0' -Architecture x86 -NoPath -Version '5.0.100' - & $dotnetInstallScript -InstallDir "${dotnetInstallPath}" -Channel '6.0' -Architecture x64 -NoPath -Version '6.0.100' - & $dotnetInstallScript -InstallDir "${dotnetInstallPath}_x86" -Channel '6.0' -Architecture x86 -NoPath -Version '6.0.100' - $env:DOTNET_ROOT= $dotnetInstallPath ${env:DOTNET_ROOT(x86)} = "${dotnetInstallPath}_x86" diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/AcceptanceTestBase.cs b/test/Microsoft.TestPlatform.AcceptanceTests/AcceptanceTestBase.cs index d6e944f5b6..df3e032d98 100644 --- a/test/Microsoft.TestPlatform.AcceptanceTests/AcceptanceTestBase.cs +++ b/test/Microsoft.TestPlatform.AcceptanceTests/AcceptanceTestBase.cs @@ -187,27 +187,4 @@ protected string GetIsolatedTestAsset(string assetName) return Path.Combine(TempDirectory.Path, assetName); } - - protected string GetIsolatedTestDllForFramework(string assetName, string targetFramework) - { - var testDllPath = GetTestDllForFramework(assetName, targetFramework); - CopyFilesRecursively(Path.GetDirectoryName(testDllPath), "*", TempDirectory.Path); - return Path.Combine(TempDirectory.Path, Path.GetFileName(testDllPath)); - } - - private static void CopyFilesRecursively(string sourceDirectory, string searchPattern, string targetDirectory) - { - foreach (var subDirectory in Directory.EnumerateDirectories(sourceDirectory)) - { - var directoryName = Path.GetFileName(subDirectory); - var newTargetDirectory = Path.Combine(targetDirectory, directoryName); - Directory.CreateDirectory(newTargetDirectory); - CopyFilesRecursively(subDirectory, searchPattern, newTargetDirectory); - } - - foreach (var file in Directory.EnumerateFiles(sourceDirectory, searchPattern)) - { - File.Copy(file, Path.Combine(targetDirectory, Path.GetFileName(file))); - } - } } diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureTests.cs b/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureTests.cs deleted file mode 100644 index bdd6d31e1b..0000000000 --- a/test/Microsoft.TestPlatform.AcceptanceTests/DotnetArchitectureTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -#if !NETFRAMEWORK - -using System.Collections.Generic; -using System.IO; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Microsoft.TestPlatform.AcceptanceTests; - -[TestClass] -public class DotnetArchitectureTests : AcceptanceTestBase -{ - public static IEnumerable GetRunnerAndDotnetRootEnvVar() - { - var runnerDataSource = new NetCoreTargetFrameworkDataSource(useDesktopRunner: false); - foreach (var entry in runnerDataSource.GetData(null)) - { - yield return new object[] { entry[0], "net6.0", "6.0.100", "DOTNET_ROOT_X86" }; - yield return new object[] { entry[0], "net6.0", "6.0.100", "DOTNET_ROOT(x86)" }; - yield return new object[] { entry[0], "net6.0", "6.0.100", "DOTNET_ROOT" }; - - yield return new object[] { entry[0], "net5.0", "5.0.100", "DOTNET_ROOT_X86" }; - yield return new object[] { entry[0], "net5.0", "5.0.100", "DOTNET_ROOT(x86)" }; - yield return new object[] { entry[0], "net5.0", "5.0.100", "DOTNET_ROOT" }; - } - } - - [TestMethod] - [DynamicData(nameof(GetRunnerAndDotnetRootEnvVar), DynamicDataSourceType.Method)] - public void Run32BitsProcessFrom64BitsDotnet(RunnerInfo runnerInfo, string targetFramework, string sdkVersion, string dotnetRootEnvVarName) - { - SetTestEnvironment(_testEnvironment, runnerInfo); - - // We want some isolated directory because we are going to pin the SDK used to ensure - // the dotnet directory detection mechanism is the correct one. - var dllPath = GetIsolatedTestDllForFramework("ProjectLaunch32BitsProcess.dll", targetFramework); - var isolatedDirectory = Path.GetDirectoryName(dllPath)!; - File.WriteAllText(Path.Combine(isolatedDirectory, "global.json"), $@"{{ - ""sdk"": {{ - ""version"": ""{sdkVersion}"" - }}, - ""tools"": {{ - ""dotnet"": ""{sdkVersion}"" - }} -}}"); - - var dotnetX86LocalPath = Path.Combine(_testEnvironment.ToolsDirectory, "dotnet_x86"); - var env = new Dictionary - { - ["EXPECTED_ENV_VAR_NAME"] = dotnetRootEnvVarName, - ["EXPECTED_ENV_VAR_VALUE"] = dotnetX86LocalPath, - }; - InvokeDotnetTest(dllPath, env, useDotnetFromTools: true, workingDirectory: isolatedDirectory); - - ExitCodeEquals(0); - } -} - -#endif diff --git a/test/TestAssets/ProjectLaunch32BitsProcess/Test32Bit.cs b/test/TestAssets/ProjectLaunch32BitsProcess/Test32Bit.cs index 41f064a063..82aa90af1a 100644 --- a/test/TestAssets/ProjectLaunch32BitsProcess/Test32Bit.cs +++ b/test/TestAssets/ProjectLaunch32BitsProcess/Test32Bit.cs @@ -14,8 +14,23 @@ public class Test32Bit [TestMethod] public void TheTest() { - // Test is based on reproducer from following SDK issue: - // https://github.com/dotnet/sdk/issues/22647 + // Test is based on reproducer from following SDK issue: https://github.com/dotnet/sdk/issues/22647 + // We cannot run the test in an automatic/coded way because we need to have .NET 5 and 6 SDKs installed + // which leads to full disk capacity on CI. + // As this change of behavior is quite important we want to make sure we can rerun such manual test + // later on so we are keeping it here. + // Repro steps: + // 1. Build project + // 2. Set $env:DOTNET_ROOT_ENV_VAR_NAME = "DOTNET_ROOT(x86)" + // 3. Set $env:DOTNET_ROOT_ENV_VAR_VALUE to a 32bits dotnet installation directory for the matching TFM + // 4. Add a global.json pinning the .NET version to 5 or 6 + // 5. Run 'dotnet test ./bin///ProjectLaunch32BitsProcess.dll' + // 6. Test should be succesful + // 7. Repeat steps 2 to 5 with $env:DOTNET_ROOT_ENV_VAR_NAME = "DOTNET_ROOT". Although we are running a + // 32 bits process, the dotnet detection logic is set to fallback to this variable. + // 8. Repeat steps 2 to 5 with $env:DOTNET_ROOT_ENV_VAR_NAME = "DOTNET_ROOT_X86". + // This should work for .NET 6 but fail for .NET 5 (if you don't have a global .NET 5 SDK installed), + // as this new variable is not understood by the .NET 5 detection algorithm. var process = new Process(); process.StartInfo = new ProcessStartInfo { @@ -24,10 +39,10 @@ public void TheTest() RedirectStandardOutput = true, }; - var envVarName = Environment.GetEnvironmentVariable("EXPECTED_ENV_VAR_NAME"); - Assert.IsNotNull(envVarName, "Calling process didn't set EXPECTED_ENV_VAR_NAME."); - var envVarValue = Environment.GetEnvironmentVariable("EXPECTED_ENV_VAR_VALUE"); - Assert.IsNotNull(envVarValue, "Calling process didn't set EXPECTED_ENV_VAR_VALUE."); + var envVarName = Environment.GetEnvironmentVariable("DOTNET_ROOT_ENV_VAR_NAME"); + Assert.IsNotNull(envVarName, "Calling process didn't set DOTNET_ROOT_ENV_VAR_NAME."); + var envVarValue = Environment.GetEnvironmentVariable("DOTNET_ROOT_ENV_VAR_VALUE"); + Assert.IsNotNull(envVarValue, "Calling process didn't set DOTNET_ROOT_ENV_VAR_VALUE."); // Set the DOTNET_ROOT* env variable so that the 32bits process can locate dotnet // even if there is no global installation. process.StartInfo.EnvironmentVariables[envVarName] = envVarValue; From a2ef77b75cdc015cfa2988c8dd666e54d23435fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Mon, 13 Jun 2022 13:59:58 +0200 Subject: [PATCH 9/9] Address review comments --- .../Hosting/DotnetTestHostManager.cs | 4 +-- .../ProjectLaunch32BitsProcess/Test32Bit.cs | 8 +++++ test/TestAssets/TestProcess32/Program.cs | 29 ++++++++++++++++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs index 8cb733e6db..3b7c9a11bf 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs @@ -582,9 +582,9 @@ bool SilentlyForceToX64() // SDK side of TP is not checking for the .NET6.0+ environment variables so we want to make sure we // are not overriding user definition. - if (_environmentVariableHelper.GetEnvironmentVariable(dotnetRootEnvName) is not null) + if (_environmentVariableHelper.GetEnvironmentVariable(dotnetRootEnvName) is string dotnetRootEnvValue) { - EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Found '{vstestDotnetRootEnvName}' in env variables but also found '{dotnetRootEnvName}'. Skipping forwarding."); + EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Found '{vstestDotnetRootEnvName}' in env variables but also found '{dotnetRootEnvName}' with value '{dotnetRootEnvValue}'. Skipping forwarding."); return; } } diff --git a/test/TestAssets/ProjectLaunch32BitsProcess/Test32Bit.cs b/test/TestAssets/ProjectLaunch32BitsProcess/Test32Bit.cs index 82aa90af1a..e6bcc1bffe 100644 --- a/test/TestAssets/ProjectLaunch32BitsProcess/Test32Bit.cs +++ b/test/TestAssets/ProjectLaunch32BitsProcess/Test32Bit.cs @@ -23,6 +23,7 @@ public void TheTest() // 1. Build project // 2. Set $env:DOTNET_ROOT_ENV_VAR_NAME = "DOTNET_ROOT(x86)" // 3. Set $env:DOTNET_ROOT_ENV_VAR_VALUE to a 32bits dotnet installation directory for the matching TFM + // (e.g. C:\src\vstest\tools\dotnet_x86) // 4. Add a global.json pinning the .NET version to 5 or 6 // 5. Run 'dotnet test ./bin///ProjectLaunch32BitsProcess.dll' // 6. Test should be succesful @@ -31,6 +32,10 @@ public void TheTest() // 8. Repeat steps 2 to 5 with $env:DOTNET_ROOT_ENV_VAR_NAME = "DOTNET_ROOT_X86". // This should work for .NET 6 but fail for .NET 5 (if you don't have a global .NET 5 SDK installed), // as this new variable is not understood by the .NET 5 detection algorithm. + // Debugging tips: + // Use the following environment variables to understand how is .NET being resolved. + // COREHOST_TRACE = 1 + // COREHOST_TRACEFILE = "C:\fxr.tx" var process = new Process(); process.StartInfo = new ProcessStartInfo { @@ -46,6 +51,9 @@ public void TheTest() // Set the DOTNET_ROOT* env variable so that the 32bits process can locate dotnet // even if there is no global installation. process.StartInfo.EnvironmentVariables[envVarName] = envVarValue; + process.StartInfo.EnvironmentVariables["DOTNET_ROOT_ENV_VAR_NAME"] = envVarName; + // Ensure multi-level lookup is disabled so that we don't fallback to machine-wide installation + process.StartInfo.EnvironmentVariables["DOTNET_MULTILEVEL_LOOKUP"] = "0"; process.Start(); var stderr = process.StandardError.ReadToEnd(); diff --git a/test/TestAssets/TestProcess32/Program.cs b/test/TestAssets/TestProcess32/Program.cs index 90a4752468..107b3f8908 100644 --- a/test/TestAssets/TestProcess32/Program.cs +++ b/test/TestAssets/TestProcess32/Program.cs @@ -2,4 +2,31 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -Console.WriteLine($"Am I 32bit?: {!Environment.Is64BitProcess}"); +if (Environment.Is64BitProcess) +{ + throw new InvalidOperationException("Process is supposed to be 32bits."); +} + +var envVarName = Environment.GetEnvironmentVariable("DOTNET_ROOT_ENV_VAR_NAME"); +if (string.IsNullOrEmpty(envVarName)) +{ + throw new InvalidOperationException("Could not find 'DOTNET_ROOT_ENV_VAR_NAME' which is supposed to tell us which DOTNET_ROOTXXX to look up."); +} + +var dotnetRootPath = Environment.GetEnvironmentVariable(envVarName); +if (string.IsNullOrEmpty(dotnetRootPath)) +{ + throw new InvalidOperationException($"{dotnetRootPath} was not set."); +} + +Console.WriteLine("DOTNET_ROOT(x86)={0}", Environment.GetEnvironmentVariable("DOTNET_ROOT(x86)")); +Console.WriteLine("DOTNET_ROOT={0}", Environment.GetEnvironmentVariable("DOTNET_ROOT")); +Console.WriteLine("DOTNET_ROOT_X86={0}", Environment.GetEnvironmentVariable("DOTNET_ROOT_X86")); +Console.WriteLine("DOTNET_ROOT_X64={0}", Environment.GetEnvironmentVariable("DOTNET_ROOT_X64")); + +if (!typeof(object).Assembly.Location.StartsWith(dotnetRootPath, StringComparison.OrdinalIgnoreCase)) +{ + throw new InvalidOperationException($"{typeof(object).Assembly.Location} was not found in {dotnetRootPath}. .NET was not resolved from the correct path."); +} + +Console.WriteLine("Process and DOTNET_ROOT* were correctly set.");