Skip to content

Commit

Permalink
Fix DOTNET_ROOT env var for .NET 6.0+ (#3715)
Browse files Browse the repository at this point in the history
  • Loading branch information
Evangelink authored Jun 13, 2022
1 parent 0bea333 commit 77627fb
Show file tree
Hide file tree
Showing 9 changed files with 362 additions and 29 deletions.
12 changes: 6 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,7 @@ 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

$env:DOTNET_ROOT= $dotnetInstallPath
${env:DOTNET_ROOT(x86)} = "${dotnetInstallPath}_x86"

Expand Down Expand Up @@ -260,10 +260,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 +278,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 +387,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)
{
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 string dotnetRootEnvValue)
{
EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Found '{vstestDotnetRootEnvName}' in env variables but also found '{dotnetRootEnvName}' with value '{dotnetRootEnvValue}'. 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);
}

/// <inheritdoc/>
public IEnumerable<string> GetTestPlatformExtensions(IEnumerable<string> sources, IEnumerable<string> extensions)
{
Expand Down
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")]
[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)]
[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
10 changes: 5 additions & 5 deletions test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ public void InvokeVsTest(string arguments, Dictionary<string, string>? environme
/// Invokes our local copy of dotnet that is patched with artifacts from the build with specified arguments.
/// </summary>
/// <param name="arguments">Arguments provided to <c>vstest.console</c>.exe</param>
public void InvokeDotnetTest(string arguments, Dictionary<string, string>? environmentVariables = null)
public void InvokeDotnetTest(string arguments, Dictionary<string, string>? environmentVariables = null, bool useDotnetFromTools = false, string? workingDirectory = null)
{
var debugEnvironmentVariables = AddDebugEnvironmentVariables(environmentVariables);

Expand All @@ -207,7 +207,7 @@ public void InvokeDotnetTest(string arguments, Dictionary<string, string>? 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, workingDirectory);
FormatStandardOutCome();
}

Expand Down Expand Up @@ -761,7 +761,7 @@ protected void ExecuteVsTestConsole(string args, out string stdOut, out string s
/// <param name="stdError"></param>
/// <param name="exitCode"></param>
private void ExecutePatchedDotnet(string command, string args, out string stdOut, out string stdError, out int exitCode,
Dictionary<string, string>? environmentVariables = null)
Dictionary<string, string>? environmentVariables = null, bool useDotnetFromTools = false, string? workingDirectory = null)
{
if (environmentVariables is null)
{
Expand All @@ -771,8 +771,8 @@ 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);
ExecuteApplication(patchedDotnetPath, string.Join(" ", command, args), out stdOut, out stdError, out exitCode, environmentVariables);
var patchedDotnetPath = Path.Combine(useDotnetFromTools ? _testEnvironment.ToolsDirectory : _testEnvironment.TestArtifactsDirectory, executablePath);
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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<!-- Imports Common TestAssets props. -->
<Import Project="..\..\..\scripts\build\TestAssets.props" />

<PropertyGroup>
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
<Nullable>enable</Nullable>
<LangVersion>Preview</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MSTest.TestFramework" Version="$(MSTestFrameworkVersion)" />
<PackageReference Include="MSTest.TestAdapter" Version="$(MSTestAdapterVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(NETTestSdkVersion)" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\TestProcess32\TestProcess32.csproj" />
</ItemGroup>

</Project>
Loading

0 comments on commit 77627fb

Please sign in to comment.