Summary
When running dotnet test (MTP) in AzDO (and any non-ANSI CI), users currently have a binary choice in the raw console log:
| Mode |
Per-test "passed" lines |
Progress in raw log |
--output normal (default) |
❌ none (good) |
❌ silent until summary (bad) |
--output detailed |
✅ one per test (noisy) |
❌ still no time-based progress |
--report-azdo-progress only updates the AzDO timeline view via ##vso[task.logdetail …] commands; it does not print anything human-readable to the raw build log, which is what you stare at when investigating a CI run.
We currently work around this in our own pipelines with --output detailed, which produces hundreds of lines like:
passed UnsupportedRunSettingsEntriesAreFlagged ("net10.0") (3s 768ms)
from D:\a\_work\1\s\artifacts\bin\MSTest.Acceptance.IntegrationTests\Debug\net11.0\MSTest.Acceptance.IntegrationTests.dll (net11.0|x64)
passed UnsupportedRunSettingsEntriesAreFlagged_Localization ("fr-FR","it-IT",null,"fr-FR") (3s 756ms)
…
That's a lot of noise (failures get buried) and it still does not convey time-based progress — only completion order.
Motivation
The maintainer experience in CI is:
- "Did the test run hang?" requires scrolling 10k lines of passed-test names, or staring at the timeline view in a separate panel.
- Dropping
--output detailed gives a clean log but no signal at all between the banner and the summary — for a 15‑minute test run that feels broken.
- The cursor-redraw progress (
SimpleAnsi/AnsiIfPossible) is intentionally disabled in CI because raw log files can't display cursor movement, so today there is no progress at all in raw logs.
Proposed feature: CI-friendly textual progress heartbeat
Emit a single-line plain-text status line periodically (default ~30s) in SimpleAnsi and NoAnsi modes. Each line is appended to the log (no cursor magic), so it's readable in AzDO/GitHub Actions/Jenkins raw logs.
Example shape (subject to bikeshed):
[00:30] running … 124/? completed, 2 failed (active: MyClass.MyTest, MyOther.OtherTest)
[01:00] running … 312/? completed, 2 failed (active: SlowTest.Foo)
[01:30] running … 451/? completed, 5 failed (active: SlowTest.Foo, Bar.Baz)
Behavior:
- Default: enabled in CI (
SimpleAnsi) and when --no-progress is not specified.
- Configurable:
--ci-progress-interval <seconds> (or env var); --no-progress continues to suppress everything.
- Independent of
--output: works whether or not passed tests are shown.
- Single line per emission so log filters / regex grep stay clean.
- Per-assembly variant when running multiple assemblies (matches existing
ShowAssemblyStartAndComplete pattern).
Scope
This needs coordinated changes in two repos:
- microsoft/testfx — MTP's
TerminalTestReporter / SimpleAnsiTerminal / TerminalOutputDevice for the single-exe --no-build and dotnet run scenarios. Touchpoints:
src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs:212-216 (currently forces ShowProgress=false for SimpleAnsi/NoAnsi).
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporterOptions.cs (new option).
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/SimpleAnsiTerminal.cs (heartbeat timer + line emission).
TerminalTestReporterCommandLineOptionsProvider + acceptance-test expectations (HelpInfoTests, HelpInfoAllExtensionsTests, MSBuild.KnownExtensionRegistration).
- dotnet/sdk — the forked reporter under
src/Cli/dotnet/Commands/Test/MTP/Terminal/ for the dotnet test (build → MTP) path. The SDK's TerminalTestReporter ctor force-disables showProgress when AnsiMode == SimpleAnsi; same logic gap.
Short-term mitigation (this repo only)
We can drop --output detailed from _CommonReleaseTestArgs in eng/pipelines/variables/test-args.yml:67 (plus the inline copies in azure-pipelines.yml:100 and eng/pipelines/steps/test-non-windows.yml:19) after the heartbeat lands, so our CI logs immediately benefit from the new signal without losing visibility.
Related work / prior art
Open questions
- Default emission interval: 15s, 30s, 60s?
- Include active-test names in the line? (Privacy/security implications minimal here, but worth confirming.)
- New CLI flag name vs. reusing
--no-progress semantics: a single --no-progress should still suppress heartbeats.
- Should the heartbeat be enabled by default outside CI when ANSI cursor progress is unavailable (e.g., output redirected to a file)? Probably yes.
- Should each assembly emit its own heartbeat in multi-assembly runs (SDK path), or one aggregate heartbeat?
cc @Evangelink @nohwnd
Summary
When running
dotnet test(MTP) in AzDO (and any non-ANSI CI), users currently have a binary choice in the raw console log:--output normal(default)--output detailed--report-azdo-progressonly updates the AzDO timeline view via##vso[task.logdetail …]commands; it does not print anything human-readable to the raw build log, which is what you stare at when investigating a CI run.We currently work around this in our own pipelines with
--output detailed, which produces hundreds of lines like:That's a lot of noise (failures get buried) and it still does not convey time-based progress — only completion order.
Motivation
The maintainer experience in CI is:
--output detailedgives a clean log but no signal at all between the banner and the summary — for a 15‑minute test run that feels broken.SimpleAnsi/AnsiIfPossible) is intentionally disabled in CI because raw log files can't display cursor movement, so today there is no progress at all in raw logs.Proposed feature: CI-friendly textual progress heartbeat
Emit a single-line plain-text status line periodically (default ~30s) in
SimpleAnsiandNoAnsimodes. Each line is appended to the log (no cursor magic), so it's readable in AzDO/GitHub Actions/Jenkins raw logs.Example shape (subject to bikeshed):
Behavior:
SimpleAnsi) and when--no-progressis not specified.--ci-progress-interval <seconds>(or env var);--no-progresscontinues to suppress everything.--output: works whether or not passed tests are shown.ShowAssemblyStartAndCompletepattern).Scope
This needs coordinated changes in two repos:
TerminalTestReporter/SimpleAnsiTerminal/TerminalOutputDevicefor the single-exe--no-buildanddotnet runscenarios. Touchpoints:src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs:212-216(currently forcesShowProgress=falseforSimpleAnsi/NoAnsi).src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporterOptions.cs(new option).src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/SimpleAnsiTerminal.cs(heartbeat timer + line emission).TerminalTestReporterCommandLineOptionsProvider+ acceptance-test expectations (HelpInfoTests,HelpInfoAllExtensionsTests,MSBuild.KnownExtensionRegistration).src/Cli/dotnet/Commands/Test/MTP/Terminal/for thedotnet test(build → MTP) path. The SDK'sTerminalTestReporterctor force-disablesshowProgresswhenAnsiMode == SimpleAnsi; same logic gap.Short-term mitigation (this repo only)
We can drop
--output detailedfrom_CommonReleaseTestArgsineng/pipelines/variables/test-args.yml:67(plus the inline copies inazure-pipelines.yml:100andeng/pipelines/steps/test-non-windows.yml:19) after the heartbeat lands, so our CI logs immediately benefit from the new signal without losing visibility.Related work / prior art
no-progressandno-ANSIenv vars (closed).--report-azdo-progressextension only writes##vso[task.logdetail …](timeline panel), not a readable line.Open questions
--no-progresssemantics: a single--no-progressshould still suppress heartbeats.cc @Evangelink @nohwnd