Skip to content
Merged
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 @@ -16,6 +16,7 @@
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine;
using Microsoft.VisualStudio.TestPlatform.Utilities;

namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP;

Expand Down Expand Up @@ -79,6 +80,13 @@ public int StartTestRun(TestRunCriteria testRunCriteria, IInternalTestRunEventsH
int processId = 0;
bool aborted = false;

// Inject environment variables declared in the runsettings RunConfiguration/EnvironmentVariables
// into the MTP application launch. On the classic path ProxyOperationManager reads these from the
// runsettings and passes them to the testhost process; the MTP application is its own host, so we
// apply them here. Done before BeforeTestRun so datacollector-provided profiler variables merge on
// top and win on collision (matching the classic ordering).
ApplyRunSettingsEnvironmentVariables(testRunCriteria.TestRunSettings);
Comment thread
nohwnd marked this conversation as resolved.

BeforeTestRun(eventHandler);

foreach (var (source, tests) in BuildWork(testRunCriteria))
Expand Down Expand Up @@ -371,6 +379,27 @@ private int RunSource(
.Select(source => (source, (List<TestCase>?)null));
}

/// <summary>
/// Reads the environment variables declared in the runsettings
/// <c>RunConfiguration/EnvironmentVariables</c> and merges them into <see cref="EnvironmentVariables"/>
/// so they are applied to the MTP application launch.
/// </summary>
private void ApplyRunSettingsEnvironmentVariables(string? runSettings)
{
Dictionary<string, string?>? runSettingsEnvironmentVariables = InferRunSettingsHelper.GetEnvironmentVariables(runSettings);
if (runSettingsEnvironmentVariables is null || runSettingsEnvironmentVariables.Count == 0)
{
return;
}

EnvironmentVariables ??= new Dictionary<string, string?>(
Environment.OSVersion.Platform == PlatformID.Win32NT ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
foreach (KeyValuePair<string, string?> variable in runSettingsEnvironmentVariables)
{
EnvironmentVariables[variable.Key] = variable.Value;
}
}

private static List<Dictionary<string, object?>> BuildTestsFilter(List<TestCase> tests)
=> tests
.Select(test => new Dictionary<string, object?>
Expand Down
Original file line number Diff line number Diff line change
@@ -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.Collections.Generic;
using System.IO;

using Microsoft.TestPlatform.TestUtilities;
Expand Down Expand Up @@ -45,7 +46,7 @@ public void RunMtpApplicationExecutesTestsOverMtpProtocol(RunnerInfo runnerInfo)

InvokeVsTest(arguments);

ValidateSummaryStatus(2, 1, 1);
ValidateSummaryStatus(3, 1, 1);
}

[TestMethod]
Expand All @@ -67,13 +68,13 @@ public void RunMixedClassicAndMtpApplicationsInSingleRun(RunnerInfo runnerInfo)

InvokeVsTest(arguments);

// Classic 1/1/1 + MTP 2/1/1 aggregated into one run summary.
ValidateSummaryStatus(3, 2, 2);
// Classic 1/1/1 + MTP 3/1/1 aggregated into one run summary.
ValidateSummaryStatus(4, 2, 2);
}

[TestMethod]
// Prove a TRX logger aggregates results from both the classic and the MTP source in a mixed run into a
// single .trx with all seven tests.
// single .trx with all eight tests.
[TestMatrix(testHost: Target.Net)]
public void RunMixedClassicAndMtpApplicationsWritesSingleTrx(RunnerInfo runnerInfo)
{
Expand All @@ -91,7 +92,7 @@ public void RunMixedClassicAndMtpApplicationsWritesSingleTrx(RunnerInfo runnerIn

InvokeVsTest(arguments);

ValidateSummaryStatus(3, 2, 2);
ValidateSummaryStatus(4, 2, 2);

var trxPath = Path.Combine(TempDirectory.Path, trxFileName);
Assert.IsTrue(File.Exists(trxPath), "Expected a single TRX to be written for the mixed run at '{0}'.", trxPath);
Expand Down Expand Up @@ -120,6 +121,47 @@ public void RunMtpApplicationWithBlameCompletesRun(RunnerInfo runnerInfo)

InvokeVsTest(arguments);

ValidateSummaryStatus(2, 1, 1);
ValidateSummaryStatus(3, 1, 1);
}

[TestMethod]
// Environment variables declared in a runsettings RunConfiguration/EnvironmentVariables block must be
// injected into the self-hosted MTP process. There is no testhost here, so vstest.console applies them
// to the MTP application launch. The guarded RunSettingsEnvironmentVariableIsInjected test asserts the
// injected value; CHECK_RUNSETTINGS_VAR is passed as a process env var (inherited by the host) to opt
// the check in, so the run passes only when runsettings injection actually delivered the value. If the
// value did not reach the host that test fails and the summary would be 2/2/1 instead of 3/1/1.
[TestMatrix(testHost: Target.Net)]
public void RunMtpApplicationInjectsRunSettingsEnvironmentVariables(RunnerInfo runnerInfo)
{
SetTestEnvironment(_testEnvironment, runnerInfo);

var runsettingsXml = @"<RunSettings>
<RunConfiguration>
<EnvironmentVariables>
<MTP_FROM_RUNSETTINGS>mtp-runsettings-value</MTP_FROM_RUNSETTINGS>
</EnvironmentVariables>
</RunConfiguration>
</RunSettings>";
var runsettingsPath = Path.Combine(TempDirectory.Path, "mtp_env_" + System.Guid.NewGuid() + ".runsettings");
File.WriteAllText(runsettingsPath, runsettingsXml);

var arguments = PrepareArguments(
GetAssetFullPath(MtpApp),
testAdapterPath: null,
runSettings: runsettingsPath,
FrameworkArgValue,
runnerInfo.InIsolationValue,
resultsDirectory: TempDirectory.Path);

var env = new Dictionary<string, string?>
{
["CHECK_RUNSETTINGS_VAR"] = "1",
};

InvokeVsTest(arguments, env);

// The guarded test passes only if MTP_FROM_RUNSETTINGS reached the host with the runsettings value.
ValidateSummaryStatus(3, 1, 1);
}
}
18 changes: 18 additions & 0 deletions test/TestAssets/MtpMSTestProject/UnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,22 @@ public void TestSkipped()
{
Assert.Fail("should never run");
}

// Verifies that environment variables declared in a runsettings RunConfiguration/EnvironmentVariables
// block are injected into the self-hosted MTP process. The check is opted into by the
// CHECK_RUNSETTINGS_VAR control variable, which the env-var acceptance test passes as a *process*
// environment variable (inherited by the host regardless of the fix), so the guard is decisive: when
// runsettings injection works MTP_FROM_RUNSETTINGS carries the expected value and the test passes; when
// it is broken the variable is absent and the assert fails. In every other run the control variable is
// unset, so the test is a no-op and stays green.
[TestMethod]
public void RunSettingsEnvironmentVariableIsInjected()
{
if (System.Environment.GetEnvironmentVariable("CHECK_RUNSETTINGS_VAR") != "1")
{
return;
}

Assert.AreEqual("mtp-runsettings-value", System.Environment.GetEnvironmentVariable("MTP_FROM_RUNSETTINGS"));
}
}