[fix] Fix: use MSBuild output mode automatically with dotnet msbuild /t:VSTest when terminal logger is active#16058
Conversation
…est when terminal logger is active When running 'dotnet msbuild /t:VSTest' with the terminal logger enabled, test output from parallel MSBuild nodes was silently lost because VSTestTask wrote directly to stdout rather than through MSBuild's task logging system. Fix: default VsTestUseMSBuildOutput to True when the terminal logger is active (_MSBUILDTLENABLED == '1') so that VSTestTask2 (ToolTask-based) is used automatically. This routes test output through MSBuild's logging infrastructure, ensuring it is properly attributed and displayed. Users who need the old console-colorized behavior can opt out with /p:VsTestUseMSBuildOutput=false. The _VSTestMSBuild condition also now excludes the MSBUILDENSURESTDOUTFORTASKPROCESSES == '1' case (set by dotnet test) to avoid running both paths simultaneously. Fixes #5156 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the MSBuild VSTest target selection logic so that when the MSBuild terminal logger is enabled, VSTest defaults to using MSBuild task-based logging output (instead of spawning vstest.console directly), preventing test output loss from parallel MSBuild nodes when running dotnet msbuild /t:VSTest.
Changes:
- Default
VsTestUseMSBuildOutputtoTruewhen the MSBuild terminal logger is active and the user hasn’t set the property explicitly. - Add a guard to keep using the console path when
MSBUILDENSURESTDOUTFORTASKPROCESSES=1(e.g.,dotnet testscenario). - Add an acceptance test plus a new
dotnet msbuildinvocation helper for acceptance tests.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.targets |
Adjusts defaulting/target-selection logic to favor MSBuild task logging when terminal logger is active. |
test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs |
Adds a helper to invoke dotnet msbuild via the patched dotnet used by acceptance tests. |
test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs |
Adds an acceptance test intended to validate the new default behavior under terminal logger. |
| <CallTarget Targets="_VSTestConsole" Condition="$(_MSBUILDTLENABLED) == '0' OR !$(VsTestUseMSBuildOutput) OR $(MSBUILDENSURESTDOUTFORTASKPROCESSES) == '1'" /> | ||
| <!-- Proper MSBuild integration, but no custom colorization --> | ||
| <!-- Use the new view whe terminal logger is enabled (_MSBUILDTLENABLED==0), and user did not opt out (VsTestUseMSBuildOutput == true) --> | ||
| <CallTarget Targets="_VSTestMSBuild" Condition="$(_MSBUILDTLENABLED) == '1' AND $(VsTestUseMSBuildOutput)" /> | ||
| <!-- Use the new view when terminal logger is enabled (_MSBUILDTLENABLED==1), user did not opt out (VsTestUseMSBuildOutput == true), | ||
| and MSBUILDENSURESTDOUTFORTASKPROCESSES is not set (set by dotnet test, which uses the console path instead). --> | ||
| <CallTarget Targets="_VSTestMSBuild" Condition="$(_MSBUILDTLENABLED) == '1' AND $(VsTestUseMSBuildOutput) AND $(MSBUILDENSURESTDOUTFORTASKPROCESSES) != '1'" /> |
| public void MSBuildLoggerIsUsedAutomaticallyWithDotnetMSBuildWhenTerminalLoggerIsActive(RunnerInfo runnerInfo) | ||
| { | ||
| SetTestEnvironment(_testEnvironment, runnerInfo); | ||
|
|
||
| var projectPath = GetIsolatedTestAsset("TerminalLoggerTestProject.csproj", runnerInfo.TargetFramework); | ||
| // Invoke via dotnet msbuild /t:VSTest with terminal logger on and no explicit VsTestUseMSBuildOutput property. | ||
| // The MSBuild output path should be used automatically when the terminal logger is active. | ||
| InvokeDotnetMSBuildTest($@"-t:VSTest {projectPath} -tl:on -nodereuse:false /p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}", workingDirectory: Path.GetDirectoryName(projectPath)); | ||
|
|
nohwnd
left a comment
There was a problem hiding this comment.
Expert Review — PR #16058
The fix is small, focused, and the logic is correct. The two CallTarget conditions remain mutually exclusive across all relevant scenarios (TL on/off, dotnet test vs dotnet msbuild, user opt-out).
Key checks passed:
- ✅ No double-invocation possible — the
_VSTestConsoleand_VSTestMSBuildconditions are mutually exclusive - ✅ Backward compatibility preserved —
VsTestUseMSBuildOutput=Falseexplicit opt-out still works; users on non-TL MSBuild are unaffected - ✅
dotnet testisolation — theMSBUILDENSURESTDOUTFORTASKPROCESSES == '1'guard correctly routesdotnet testto_VSTestConsole - ✅ Two-line property default ordering is correct — the
_MSBUILDTLENABLED == '1'check setsTruefirst, the unconditional fallback setsFalseonly when still unset - ✅ Acceptance test covers the exact scenario described in the fix
One minor finding (see inline): VSTEST_CONSOLE_PATH env var in InvokeDotnetMSBuildTest appears to be dead code for the dotnet msbuild path.
🧠 Reviewed by Expert Code Reviewer
🧠 Reviewed by Expert Code Reviewer 🧠
|
|
||
| debugEnvironmentVariables["VSTEST_CONSOLE_PATH"] = vstestConsolePath; | ||
|
|
||
| IntegrationTestBase.ExecutePatchedDotnet("msbuild", arguments, out _standardTestOutput, out _standardTestError, out _runnerExitCode, debugEnvironmentVariables, workingDirectory); |
There was a problem hiding this comment.
[Environment Variable & Feature Flag Contracts] VSTEST_CONSOLE_PATH appears to be used by the dotnet test / _VSTestConsole path, not the dotnet msbuild / _VSTestMSBuild path (which reads VsTestConsolePath from the MSBuild property already appended as -p:VsTestConsolePath=...). Setting this env var here looks like dead code.
If ExecutePatchedDotnet/_VSTestMSBuild doesn't read VSTEST_CONSOLE_PATH, removing the line would be a clean-up. If it is read somewhere in the MSBuild task, a comment explaining why it's needed would help future readers.
This doesn't affect correctness (the MSBuild property path already ensures the right vstest.console is used), but it adds confusion.
There was a problem hiding this comment.
Good catch — removed the dead VSTEST_CONSOLE_PATH assignment. The MSBuild property -p:VsTestConsolePath already handles routing the correct console path.
🔧 Iterated by PR Iteration Agent 🔧
The MSBuild path already uses -p:VsTestConsolePath to pass the console path as an MSBuild property. Setting VSTEST_CONSOLE_PATH env var here is dead code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
The test invoked 'dotnet msbuild -t:VSTest' without building the project first, so the test DLL did not exist and vstest.console failed with a file-not-found error instead of running tests. Change to '-t:Build;VSTest' to ensure the project is compiled before the test run, matching the typical real-world usage pattern. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
nohwnd
left a comment
There was a problem hiding this comment.
Expert Review — PR #16058 (re-review after iteration)
This is a follow-up review covering commits pushed after the initial review on 2026-05-24.
New commits reviewed:
8677ca1— Removed deadVSTEST_CONSOLE_PATHenv var fromInvokeDotnetMSBuildTest(addresses the finding from the prior review ✅)07a6ff6— CI retrigger only, no code changes5911482— Fixed acceptance test to use-t:Build;VSTestinstead of-t:VSTest; the project was not compiled before test execution, causing the DLL to be missing ✅
All prior findings resolved. The PR is clean:
- ✅
.targetslogic:_VSTestConsoleand_VSTestMSBuildremain mutually exclusive across all scenarios - ✅
VsTestUseMSBuildOutputtwo-step default ordering is correct - ✅
MSBUILDENSURESTDOUTFORTASKPROCESSES == '1'guard correctly isolatesdotnet testfromdotnet msbuild - ✅
InvokeDotnetMSBuildTestpasses console path via MSBuild property (correct for theVSTestConsolePathlookup in targets) - ✅ Acceptance test now builds before running tests via
-t:Build;VSTest
🧠 Reviewed by Expert Code Reviewer 🧠
🧠 Reviewed by Expert Code Reviewer 🧠
The acceptance test was using -tl:on to activate the terminal logger, but in non-interactive (non-TTY) CI environments, the terminal logger may not set _MSBUILDTLENABLED=1 even when explicitly requested. This caused the auto-detection in Microsoft.TestPlatform.targets to not trigger, making VsTestUseMSBuildOutput remain False and the test assertions fail. Switch to explicitly setting /p:_MSBUILDTLENABLED=1 to directly simulate the terminal logger being active. This tests the auto-detection mechanism reliably regardless of TTY availability. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
|
Pushed a fix: changed the acceptance test to use Root cause: In non-interactive (non-TTY) CI environments, the MSBuild terminal logger may not set the The fix uses
|
nohwnd
left a comment
There was a problem hiding this comment.
Expert Review — PR #16058 (re-review after latest commit)
This is a follow-up review covering the new commit pushed on 2026-06-02.
New commit reviewed:
05409a9— Switch acceptance test from-tl:onto/p:_MSBUILDTLENABLED=1
Analysis:
The change is correct and well-motivated. -tl:on enables the MSBuild terminal logger, but in non-TTY CI environments MSBuild may suppress the terminal logger even when explicitly requested, leaving _MSBUILDTLENABLED unset. This would cause the auto-detection path in Microsoft.TestPlatform.targets to not trigger, making VsTestUseMSBuildOutput remain False and the test assertions fail spuriously.
By directly injecting /p:_MSBUILDTLENABLED=1, the test bypasses the TTY detection and exercises the auto-detection logic deterministically — exactly what an integration test should do.
- ✅ No code changes to
.targets— the fix is purely a test reliability improvement - ✅ The comment in the test file accurately explains why
/p:_MSBUILDTLENABLED=1is preferred over-tl:on - ✅ All prior findings remain resolved; no new issues introduced
All checks passed. The PR is clean and ready for human review/merge.
🧠 Reviewed by Expert Code Reviewer 🧠
🧠 Reviewed by Expert Code Reviewer 🧠
🤖 This is an automated fix generated by the Issue Triage agent.
Fixes #5156
Root Cause
When running
dotnet msbuild /t:VSTestwith the MSBuild terminal logger enabled (_MSBUILDTLENABLED == '1'), test output from parallel MSBuild nodes was silently lost.The cause:
VsTestUseMSBuildOutputdefaulted toFalse, so_VSTestConsolewas always used. This target invokes vstest.console directly and writes to stdout — output that the terminal logger cannot properly attribute to individual projects when running on parallel MSBuild worker nodes.Fix
Microsoft.TestPlatform.targets: When the terminal logger is active and the user has not setVsTestUseMSBuildOutputexplicitly, default it toTrue. This causes_VSTestMSBuild(usingVSTestTask2/ToolTask) to be used, which routes output through MSBuild task logging — ensuring it is attributed and displayed correctly by the terminal logger.Added
$(MSBUILDENSURESTDOUTFORTASKPROCESSES) != '1'guard to the_VSTestMSBuildcondition so it will not run when invoked fromdotnet test(which sets this env var and uses_VSTestConsolefor proper colorization).Backward compatibility: Users who want the old console-colorized behavior can opt out with
/p:VsTestUseMSBuildOutput=false.Test
Added
MSBuildLoggerIsUsedAutomaticallyWithDotnetMSBuildWhenTerminalLoggerIsActiveacceptance test inDotnetTestMSBuildOutputTeststhat invokesdotnet msbuild -t:VSTest -tl:onwithout settingVsTestUseMSBuildOutputand asserts that test error output appears in stdout.Also added
InvokeDotnetMSBuildTesthelper toIntegrationTestBaseto support invokingdotnet msbuildwith the patched dotnet in acceptance tests.