Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,20 @@ internal static void WaitForErrorStreamToDrain(ManualResetEventSlim? errorStream
/// <inheritdoc/>
public string? GetCurrentProcessFileName()
{
#if NET
// Environment.ProcessPath is more reliable than MainModule under sandboxes such as
// proot, which is commonly used to run Linux distributions on Android (e.g. Termux).
// proot starts the real executable through an injected loader, and it cannot rewrite
// the contents of /proc/self/maps that MainModule is built from. When the loader is
// mapped below the executable (as it is on ARM64) MainModule reports the loader
// instead of the running dotnet host, which breaks test host resolution.
// See https://github.com/microsoft/vstest/issues/16446.
if (Environment.ProcessPath is { } processPath)
{
return processPath;
}
#endif

return _currentProcess.MainModule?.FileName;
}

Expand Down
34 changes: 32 additions & 2 deletions test/vstest.console.UnitTests/ProcessHelperTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#if NET
using System;
#endif
using System.Diagnostics;
using System.Threading;

Expand All @@ -11,17 +14,44 @@
namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests;

/// <summary>
/// Tests for <see cref="ProcessHelper.WaitForErrorStreamToDrain"/>, the bounded wait that lets the process
/// Tests for <see cref="ProcessHelper"/>.
/// </summary>
/// <remarks>
/// <see cref="ProcessHelper.WaitForErrorStreamToDrain"/> is the bounded wait that lets the process
/// exit callback observe the complete standard error output of a crashed test host. Without it, the exit
/// callback could read the asynchronously-collected stderr before all ErrorDataReceived callbacks had run,
/// dropping a crash callstack such as "Stack overflow." (the cause of the flaky
/// RunTestsShouldThrowOnStackOverflowException test).
/// </summary>
/// </remarks>
[TestClass]
public class ProcessHelperTests
{
private const int BudgetMs = 500;

[TestMethod]
public void GetCurrentProcessFileNameShouldReturnThePathOfTheRunningExecutable()
{
var processHelper = new ProcessHelper();

string? currentProcessFileName = processHelper.GetCurrentProcessFileName();

using var currentProcess = Process.GetCurrentProcess();
string? mainModuleFileName = currentProcess.MainModule?.FileName;

#if NET
// On .NET we prefer Environment.ProcessPath, because MainModule can report an injected loader
// rather than the running executable under sandboxes such as proot. See issue #16446.
// ProcessPath is nullable, so mirror the fallback the production code performs instead of
// assuming it is always set.
string? expectedFileName = Environment.ProcessPath ?? mainModuleFileName;
#else
// .NET Framework has no Environment.ProcessPath, so the MainModule behavior must be preserved.
string? expectedFileName = mainModuleFileName;
#endif

Assert.AreEqual(expectedFileName, currentProcessFileName);
}

[TestMethod]
public void WaitForErrorStreamToDrainShouldReturnOnceTheErrorStreamCloses()
{
Expand Down
Loading