Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix DOTNET_ROOT env var for .NET 6.0+ #3715

Merged
merged 9 commits into from
Jun 13, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions scripts/common.lib.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function Write-Log {
[string]
$Level = "Success"
)

if ($message)
{
$color = if ("Success" -eq $Level) { "Green" } else { "Red" }
Expand Down Expand Up @@ -116,7 +116,13 @@ 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}" -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"

Expand Down Expand Up @@ -260,10 +266,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.
Expand All @@ -278,7 +284,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")
Expand Down Expand Up @@ -387,4 +393,4 @@ function Start-InlineProcess {
$process.remove_ErrorDataReceived($errorDataReceived)
$process.Dispose()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -469,24 +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)
{
string prefix = "VSTEST_WINAPPHOST_";
string dotnetRootEnvName = $"{prefix}DOTNET_ROOT(x86)";
var dotnetRoot = _environmentVariableHelper.GetEnvironmentVariable(dotnetRootEnvName);
if (dotnetRoot is null)
{
dotnetRootEnvName = $"{prefix}DOTNET_ROOT";
dotnetRoot = _environmentVariableHelper.GetEnvironmentVariable(dotnetRootEnvName);
}

if (dotnetRoot != 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);
}
else
{
EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Prefix '{prefix}*' not found in env variables");
}
ForwardDotnetRootEnvironmentVariable(startInfo);
}

startInfo.WorkingDirectory = sourceDirectory;
Expand Down Expand Up @@ -565,6 +550,53 @@ bool SilentlyForceToX64()
}
}

internal /* for testing purposes */ void ForwardDotnetRootEnvironmentVariable(TestProcessStartInfo startInfo)
Evangelink marked this conversation as resolved.
Show resolved Hide resolved
{
const string prefix = "VSTEST_WINAPPHOST_";
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");
return;
}
}

// 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.");
MarcoRossignoli marked this conversation as resolved.
Show resolved Hide resolved
MarcoRossignoli marked this conversation as resolved.
Show resolved Hide resolved
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);
}

/// <inheritdoc/>
public IEnumerable<string> GetTestPlatformExtensions(IEnumerable<string> sources, IEnumerable<string> extensions)
{
Expand Down
23 changes: 23 additions & 0 deletions test/Microsoft.TestPlatform.AcceptanceTests/AcceptanceTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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<object[]> 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" };
Evangelink marked this conversation as resolved.
Show resolved Hide resolved
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<string, string>
{
["EXPECTED_ENV_VAR_NAME"] = dotnetRootEnvVarName,
["EXPECTED_ENV_VAR_VALUE"] = dotnetX86LocalPath,
};
InvokeDotnetTest(dllPath, env, useDotnetFromTools: true, workingDirectory: isolatedDirectory);

ExitCodeEquals(0);
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,143 @@ 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 = """
<RunSettings>
<RunConfiguration>
<TargetFrameworkVersion>net5.0</TargetFrameworkVersion>
</RunConfiguration>
</RunSettings>
""";
_dotnetHostManager.Initialize(_mockMessageLogger.Object, runSettingsXml);

var startInfo = new TestProcessStartInfo { EnvironmentVariables = new Dictionary<string, string>() };
// 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")]
Evangelink marked this conversation as resolved.
Show resolved Hide resolved
[DataRow("DOTNET_ROOT_ARM64", "net5.0")]
[DataRow("DOTNET_ROOT_ARM64", "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 = $"""
<RunSettings>
<RunConfiguration>
<TargetFrameworkVersion>{framework}</TargetFrameworkVersion>
</RunConfiguration>
</RunSettings>
""";
_dotnetHostManager.Initialize(_mockMessageLogger.Object, runSettingsXml);

var startInfo = new TestProcessStartInfo { EnvironmentVariables = new Dictionary<string, string>() };

// 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)]
Evangelink marked this conversation as resolved.
Show resolved Hide resolved
[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 = """
<RunSettings>
<RunConfiguration>
<TargetFrameworkVersion>net6.0</TargetFrameworkVersion>
</RunConfiguration>
</RunSettings>
""";
_dotnetHostManager.Initialize(_mockMessageLogger.Object, runSettingsXml);

var startInfo = new TestProcessStartInfo { EnvironmentVariables = new Dictionary<string, string>() };
// 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);
}

[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 = """
<RunSettings>
<RunConfiguration>
<TargetFrameworkVersion>net6.0</TargetFrameworkVersion>
</RunConfiguration>
</RunSettings>
""";
_dotnetHostManager.Initialize(_mockMessageLogger.Object, runSettingsXml);

var startInfo = new TestProcessStartInfo { EnvironmentVariables = new Dictionary<string, string>() };
// 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());
Expand Down
Loading