[fix] Fix tilde/exclamation characters corrupted in TerminalLogger test output#16046
[fix] Fix tilde/exclamation characters corrupted in TerminalLogger test output#16046nohwnd wants to merge 3 commits into
Conversation
When test output contained sequences of 4+ tilde characters (~~~~) or
4+ exclamation marks ( { echo ___BEGIN___COMMAND_OUTPUT_MARKER___; PS1=;PS2=;unset HISTFILE; EC=0; echo ___BEGIN___COMMAND_DONE_MARKER___0; } { echo ___BEGIN___COMMAND_OUTPUT_MARKER___; PS1=;PS2=;unset HISTFILE; EC=0; echo ___BEGIN___COMMAND_DONE_MARKER___0; }), the MSBuildLogger.Escape method would
replace them with underscores (____) before applying the actual CR/LF
encoding. This caused any ~~ or { echo ___BEGIN___COMMAND_OUTPUT_MARKER___; PS1=;PS2=;unset HISTFILE; EC=0; echo ___BEGIN___COMMAND_DONE_MARKER___0; } sequences in test names, assertion
messages, or stack traces to appear corrupted in the terminal logger.
Root cause: the old encoding used ~~~~ as a CR placeholder and { echo ___BEGIN___COMMAND_OUTPUT_MARKER___; PS1=;PS2=;unset HISTFILE; EC=0; echo ___BEGIN___COMMAND_DONE_MARKER___0; } { echo ___BEGIN___COMMAND_OUTPUT_MARKER___; PS1=;PS2=;unset HISTFILE; EC=0; echo ___BEGIN___COMMAND_DONE_MARKER___0; } as
an LF placeholder, and sanitized input by pre-replacing those sequences
with ____. This sanitization destructively modified user data.
Fix: Use ASCII control characters STX (\x02) for CR and ETX (\x03) for
LF as the escape sequences. These control characters cannot appear in
normal test output (test names, assertion messages, stack traces), so no
sanitization of user data is needed.
Fixes #15268
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Fixes corruption of ~ and ! characters in TerminalLogger-transported test output by changing the newline escaping protocol between vstest.console and VSTestTask2.
Changes:
- Switched CR/LF encoding from printable sequences (
~~~~/!!!!) to ASCII control chars (\x02/\x03) inMSBuildLogger. - Updated
VSTestTask2decoding logic to restore CR/LF from\x02/\x03. - Added regression tests covering tilde/exclamation preservation and newline round-tripping.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| test/Microsoft.TestPlatform.Build.UnitTests/VSTestTask2RegressionTests.cs | Adds regression tests to validate decoding and prevent output corruption regressions. |
| src/vstest.console/Internal/MSBuildLogger.cs | Updates message formatting/escaping to use control characters for newline transport. |
| src/Microsoft.TestPlatform.Build/Tasks/VSTestTask2.cs | Updates message decoding to match the new control-character encoding. |
| // Cleanup characters that we are using ourselves to delimit the message. | ||
| .Replace("||||", "____") | ||
| // Replace new line characters that would change how the message is consumed. | ||
| .Replace("\r", "~~~~").Replace("\n", "!!!!"); | ||
| // Use ASCII control characters STX (\x02) and ETX (\x03) which cannot appear in normal | ||
| // test output, rather than ~~~~ and !!!! which can appear in test data and get corrupted. | ||
| .Replace("\r", "\x02") | ||
| .Replace("\n", "\x03"); |
| .Replace("\r", "\x02") | ||
| .Replace("\n", "\x03"); |
| // Use ASCII control characters STX (\x02) and ETX (\x03) which cannot appear in normal | ||
| // test output, rather than ~~~~ and !!!! which can appear in test data and get corrupted. |
nohwnd
left a comment
There was a problem hiding this comment.
Review Summary
The fix is correct and well-scoped. Using ASCII control characters \x02/\x03 (STX/ETX) as newline placeholders instead of ~~~~/!!!! eliminates the data-corruption bug: the old approach would mangle any user input containing those exact 4-character sequences.
IPC Protocol Stability note: This is a wire-protocol change between MSBuildLogger (vstest.console) and VSTestTask2. There is no version negotiation — a mismatched deployment (old VSTestTask2 + new vstest.console) would see literal \x02/\x03 characters in output instead of newlines, with no error. Since both components co-ship in the .NET SDK this is low practical risk, but it's worth being aware of for any scenario where these are installed independently.
One inline comment on test coverage (not blocking).
🧠 Reviewed by Expert Code Reviewer
🧠 Reviewed by Expert Code Reviewer 🧠
|
|
||
|
|
||
| private static TestableVSTestTask2 CreateVSTestTask2() | ||
| { |
There was a problem hiding this comment.
[Backward Compatibility & Rollback Safety / Test Coverage] The three regression tests bypass MSBuildLogger.FormatMessage() and only exercise the VSTestTask2 decoder side. They feed pre-encoded strings (with \x02/\x03 already in place) directly to TestLogEventsFromTextOutput, so they don't verify that the encoder actually produces the control characters the decoder expects.
A round-trip test along these lines would provide stronger guarantees that the encoder/decoder pair stay in sync:
// Arrange
string rawMessage = "Assert failed.\r\n at MyTest()";
string encoded = MSBuildLogger.FormatMessage("test-failed", rawMessage, "TestFile.cs", "42");
// ... feed encoded through task ...
// Assert: decoded message matches rawMessageThis is a test-coverage observation — not blocking — but a gap worth filling to guard against future encoder/decoder drift.
There was a problem hiding this comment.
The full encoder→decoder round-trip is already covered by MSBuildLoggerEncoderTests.cs (in vstest.console.UnitTests), which calls MSBuildLogger.FormatMessage() for encoding and the local Decode helper for decoding. Added a cross-reference comment to the newlines test in VSTestTask2RegressionTests.cs pointing there.
🔧 Iterated by PR Iteration Agent 🔧
Add MSBuildLoggerEncoderTests with four round-trip tests that call MSBuildLogger.FormatMessage() (encoder) and then decode the result using the same logic as VSTestTask2.TryGetMessage (decoder), verifying that tilde chars, exclamation chars, newlines, and multiple fields all survive the full encode/decode cycle. Make FormatMessage internal (was private) to allow access from the vstest.console.UnitTests project via InternalsVisibleTo. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
nohwnd
left a comment
There was a problem hiding this comment.
Review Summary (updated commit)
The new commit addresses the round-trip test gap from the prior review by adding MSBuildLoggerEncoderTests.cs and exposing FormatMessage as internal. The core fix remains correct and well-scoped.
One non-blocking observation on the new test file: the Decode helper duplicates VSTestTask2.TryGetMessage decoder logic locally rather than invoking the real decoder, which means encoder/decoder drift could go undetected. See inline comment.
Everything else looks good.
🧠 Reviewed by Expert Code Reviewer 🧠
🧠 Reviewed by Expert Code Reviewer 🧠
| { | ||
| // Mirrors VSTestTask2.TryGetMessage decode logic to keep the round-trip self-contained. | ||
| private static string?[] Decode(string encoded) | ||
| { |
There was a problem hiding this comment.
[Test Coverage] The Decode helper duplicates the decoder logic from VSTestTask2.TryGetMessage rather than calling the real decoder. If someone updates the VSTestTask2 decoder (e.g., changes \x02→\r to something else) without updating this copy, these round-trip tests will still pass — but real round-trips will break.
Consider pulling the decode logic through the actual VSTestTask2 path (e.g., via TestLogEventsFromTextOutput as done in VSTestTask2RegressionTests), or at minimum add a comment warning that this must stay in sync with VSTestTask2.TryGetMessage.
Non-blocking — the VSTestTask2RegressionTests already test the decoder directly, so the gap is partially mitigated.
There was a problem hiding this comment.
Added a warning comment to the Decode helper noting it must stay in sync with VSTestTask2.TryGetMessage.
🔧 Iterated by PR Iteration Agent 🔧
… encoder tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
| return input | ||
| // Cleanup characters that we are using ourselves to delimit the message | ||
| .Replace("||||", "____").Replace("~~~~", "____").Replace("!!!!", "____") | ||
| // Cleanup characters that we are using ourselves to delimit the message. | ||
| .Replace("||||", "____") | ||
| // Replace new line characters that would change how the message is consumed. | ||
| .Replace("\r", "~~~~").Replace("\n", "!!!!"); | ||
| // Use ASCII control characters STX (\x02) and ETX (\x03) which cannot appear in normal | ||
| // test output, rather than ~~~~ and !!!! which can appear in test data and get corrupted. | ||
| .Replace("\r", "\x02") | ||
| .Replace("\n", "\x03"); |
| /// <summary> | ||
| /// Writes message to standard output, with the name of the message followed by the number of | ||
| /// parameters. With each parameter delimited by '||||', and newlines replaced with ~~~~ and !!!!. | ||
| /// parameters. With each parameter delimited by '||||', and newlines replaced with STX (\x02) and ETX (\x03). | ||
| /// Such as: | ||
| /// ||||run-start1||||s:\t\mstest97\bin\Debug\net8.0\mstest97.dll |
nohwnd
left a comment
There was a problem hiding this comment.
Review Summary (updated commit)
Both observations from the prior reviews have been addressed:
- Round-trip test gap —
MSBuildLoggerEncoderTests.csnow callsMSBuildLogger.FormatMessage()directly for encoding, covering the full encoder→decoder path. - Decoder drift risk — The
Decodehelper in the test file now carries a clear warning comment that it must stay in sync withVSTestTask2.TryGetMessage.
The core fix remains correct and well-scoped. No new issues found.
🧠 Reviewed by Expert Code Reviewer 🧠
🧠 Reviewed by Expert Code Reviewer 🧠
Caution
Security scanning requires review for Issue Repro Triage & Auto-Fix 🔍
Details
Potential security threats were detected in the agent output. The workflow output should be reviewed before merging.
Review the workflow run logs for details.
Summary
Fixes #15268 —
~and!characters in test output (assertion messages, test display names, stack traces) were being replaced with_characters when using the TerminalLogger (dotnet testwith live logger enabled).Root Cause
MSBuildLogger.Escape()serializes multi-line test messages to a single line for transport over stdout betweenvstest.consoleand the MSBuildVSTestTask2. The protocol used:||||as a field separator~~~~(4 tildes) as a\rplaceholder!!!!(4 exclamation marks) as a\nplaceholderTo prevent user data from being mistaken for these escape sequences, the code pre-sanitized input by replacing
~~~~and!!!!with____:The problem: a string like
~~~~~(5 tildes) would have its first 4 tildes replaced with____, yielding____~. The user sees____~instead of~~~~~.Fix
Replace the escape sequences for CR and LF with ASCII control characters
\x02(STX) and\x03(ETX), which cannot appear in normal test output:The
VSTestTask2decoder is updated to match:Tests
Three regression tests added to
VSTestTask2RegressionTests.cs:LogEventsFromTextOutput_TestFailed_TildeCharsInMessage_ShouldNotBeCorrupted— 5 tilde chars survive round-tripLogEventsFromTextOutput_TestFailed_ExclamationCharsInMessage_ShouldNotBeCorrupted— 4!chars survive round-tripLogEventsFromTextOutput_TestFailed_NewlinesEncodedWithControlChars_ShouldBeRestored—\r\nnewlines are correctly round-tripped via the new control-char encoding