Summary
Implement the silence-driven progress heartbeat renderer for the SimpleAnsi and NoAnsi terminal modes. This is the core implementation of #9125 and the natural successor to #9138.
Background
Today, TerminalOutputDevice.cs:212-216 force-disables progress for SimpleAnsi/NoAnsi, leaving CI / piped / file-redirected runs with zero progress signal between the banner and the summary. Previously the platform emitted a per-DLL summary every 3 seconds, which was rejected as spam (#6753). The fix is event-driven, not time-driven.
Design
The terminal reporter picks a renderer based on capability:
| AnsiMode |
Renderer |
AnsiIfPossible / ForceAnsi |
existing cursor-redraw renderer (unchanged) |
SimpleAnsi / NoAnsi |
NEW silence-driven heartbeat renderer |
Three independent emission rules. Each produces single lines, no cursor tricks, no duplicate timestamps (CIs already prepend their own):
E1 — silence heartbeat
Emit one summary line only when no test completion event has occurred for N seconds (default 30s). A healthy fast suite emits zero heartbeat lines.
running … 312/3200 completed, 2 failed | active: SlowTest.Foo
E2 — slow-test surfacing
When a single test exceeds a per-test threshold (default 60s), emit one line with exponential backoff (60s → 2m → 4m → 8m …):
[slow] still running after 60s: SlowTest.IntegrationFoo (MyAcceptance, net9.0)
E2 fires in all modes (including cursor) because it leaves a durable scrollback line; cursor frames are transient.
E3 — failures inline
Verify failures continue to be printed at the moment of failure for SimpleAnsi/NoAnsi. Already true in cursor mode; mostly a verification item.
Gating
Knobs (env vars only, no new CLI flags)
MTP_PROGRESS_SILENCE_SECONDS (default 30; 0 disables E1)
MTP_PROGRESS_SLOW_TEST_SECONDS (default 60; 0 disables E2)
Extension hook
Introduce a minimal IProgressEnricher (name TBD) hook with:
OnAssemblyStart(asm) / OnAssemblyEnd(asm) — per-CI extensions emit group markers here.
OnFailure(test, …) — per-CI extensions emit native annotations.
OnSlowTestThreshold(test) — extensions can lower/raise per-test E2 threshold (powers the AzDO-history extension).
OnSlowTestEmit(test, currentDuration) — extensions can decorate the emitted line (e.g. with historical p95 = 2s).
The hook contract is shipped here so per-CI extensions can iterate independently.
Touchpoints
src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs — collapse the if (noProgress || ansiMode is NoAnsi or SimpleAnsi) branch to just if (noProgress); renderer picked by AnsiMode.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.cs — extract renderer abstraction.
- New:
SilenceDrivenHeartbeatRenderer.cs (or equivalent).
- New:
IProgressEnricher hook + glue.
- Tests: heartbeat fires only on silence, slow-test backoff math, failure inline,
--progress off suppression, controller suppression, env-var overrides.
- Acceptance: re-run
HelpInfoTests / HelpInfoAllExtensionsTests (no new flag here, but verify nothing regressed).
Out of scope (deliberately)
Related
Summary
Implement the silence-driven progress heartbeat renderer for the
SimpleAnsiandNoAnsiterminal modes. This is the core implementation of #9125 and the natural successor to #9138.Background
Today,
TerminalOutputDevice.cs:212-216force-disables progress forSimpleAnsi/NoAnsi, leaving CI / piped / file-redirected runs with zero progress signal between the banner and the summary. Previously the platform emitted a per-DLL summary every 3 seconds, which was rejected as spam (#6753). The fix is event-driven, not time-driven.Design
The terminal reporter picks a renderer based on capability:
AnsiIfPossible/ForceAnsiSimpleAnsi/NoAnsiThree independent emission rules. Each produces single lines, no cursor tricks, no duplicate timestamps (CIs already prepend their own):
E1 — silence heartbeat
Emit one summary line only when no test completion event has occurred for N seconds (default 30s). A healthy fast suite emits zero heartbeat lines.
running … 312/3200 completed, 2 failed | active: SlowTest.FooE2 — slow-test surfacing
When a single test exceeds a per-test threshold (default 60s), emit one line with exponential backoff (60s → 2m → 4m → 8m …):
[slow] still running after 60s: SlowTest.IntegrationFoo (MyAcceptance, net9.0)E2 fires in all modes (including cursor) because it leaves a durable scrollback line; cursor frames are transient.
E3 — failures inline
Verify failures continue to be printed at the moment of failure for
SimpleAnsi/NoAnsi. Already true in cursor mode; mostly a verification item.Gating
--progress(Add --progress {auto|on|off} and deprecate --no-progress in MTP terminal reporter #9138) resolves toonANDAnsiModeisSimpleAnsi/NoAnsi.--progress off(or legacy--no-progress) suppresses everything.--list-tests/ server mode continue to suppress.Knobs (env vars only, no new CLI flags)
MTP_PROGRESS_SILENCE_SECONDS(default30;0disables E1)MTP_PROGRESS_SLOW_TEST_SECONDS(default60;0disables E2)Extension hook
Introduce a minimal
IProgressEnricher(name TBD) hook with:OnAssemblyStart(asm)/OnAssemblyEnd(asm)— per-CI extensions emit group markers here.OnFailure(test, …)— per-CI extensions emit native annotations.OnSlowTestThreshold(test)— extensions can lower/raise per-test E2 threshold (powers the AzDO-history extension).OnSlowTestEmit(test, currentDuration)— extensions can decorate the emitted line (e.g. withhistorical p95 = 2s).The hook contract is shipped here so per-CI extensions can iterate independently.
Touchpoints
src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs— collapse theif (noProgress || ansiMode is NoAnsi or SimpleAnsi)branch to justif (noProgress); renderer picked byAnsiMode.src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.cs— extract renderer abstraction.SilenceDrivenHeartbeatRenderer.cs(or equivalent).IProgressEnricherhook + glue.--progress offsuppression, controller suppression, env-var overrides.HelpInfoTests/HelpInfoAllExtensionsTests(no new flag here, but verify nothing regressed).Out of scope (deliberately)
GITHUB_STEP_SUMMARY) — separate issues per package.Related
--progressflag rename (prerequisite).