Add silence-driven progress heartbeat for SimpleAnsi/NoAnsi modes (#9139) - #9147

Merged
Amaury Levé (Evangelink) merged 7 commits into
mainfrom
dev/amauryleve/heartbeat-9139
Jun 16, 2026
Merged

Add silence-driven progress heartbeat for SimpleAnsi/NoAnsi modes (#9139)#9147
Amaury Levé (Evangelink) merged 7 commits into
mainfrom
dev/amauryleve/heartbeat-9139

Conversation

@Evangelink

@EvangelinkAmaury Levé (Evangelink) commented Jun 15, 2026

Copy link
Copy Markdown
Member

Implements the silence-driven progress heartbeat renderer for the SimpleAnsi and NoAnsi terminal modes. Fixes#9139.

Stacked on #9145 (the --progress flag rename, #9138). Base is dev/amauryleve/progress-option-9138; review the heartbeat diff here and merge after #9145.

Problem

Today TerminalOutputDevice force-disables progress for SimpleAnsi/NoAnsi, so CI / piped / file-redirected runs get zero progress signal between the banner and the summary. The old fix (a per-DLL summary every 3s) was rejected as spam (#6753). This design is silence-driven, not time-driven.

What changed

Renderer abstraction (OutputDevice/Terminal/)

  • IProgressRenderer — strategy for per-tick render, write-wrapping, and completion notification.
  • CursorProgressRenderer — the existing in-place redraw, extracted verbatim (no behavior change for ANSI cursor modes).
  • SilenceDrivenHeartbeatRenderernew, emits single durable lines only when needed:
    • E1 silence heartbeatrunning... N completed, M failed | active: X after N seconds of no completion; repeats once per interval during prolonged silence. A healthy fast suite emits nothing.
    • E2 slow-test[slow] still running after …: <test> (<asm>|<tfm>|<arch>) per running test over the threshold, with exponential backoff (60s → 2m → 4m …). Durable scrollback line.
    • E3 failures inline — verified: failures still print at the moment of failure.

Gating (TerminalOutputDevice.cs)

  • Collapse noProgress || ansiMode is NoAnsi or SimpleAnsi to just noProgress; renderer is chosen by resolved terminal capability. Test-host controller / --list-tests / server mode still suppress.

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)

Localization — 3 new strings in PlatformResources.resx with all 13 .xlf files regenerated via /t:UpdateXlf.

Before / After

The feature currently doesn't work with dotnet test and requires a change there but when running the exe itself (or via dotnet or dotnet run) we can see the change.

Before: no info during the 20s of the run.

image

After (the screenshot belows sets progress silence as 2s and slow tests as 2s too):

image

For a more real use case, on this pipeline with progress disabled and default output level, we have a gap of ~16.5min dead-air window. With this feature (assuming dotnet test handling is done), we would have about:

  • 40 lines of silence heartbeat - with default 30s threshold
  • 15 lines of slow tests

Tests

  • 8 new deterministic unit tests in SilenceDrivenHeartbeatRendererTests.cs (silence fire / no-fire, repeat-per-interval, completion reset, disabled, slow-test backoff, below-threshold, disabled, write-through).
  • Updated 2 existing tests for the new TestProgressStateAwareTerminal ctor signature.
  • Core platform builds clean (0 warnings); Microsoft.Testing.Platform.UnitTests green. No new CLI flag, so --help/--info acceptance expectations are unaffected.

Out of scope (deliberate)

…nal reporter
Fixes#9138
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements the silence-driven heartbeat renderer (#9139) so CI / piped /
file-redirected runs get a progress signal between banner and summary,
without the rejected fixed-cadence per-DLL summary (#6753).
- Extract IProgressRenderer strategy; keep existing in-place redraw as
CursorProgressRenderer (ANSI cursor modes, unchanged behavior).
- Add SilenceDrivenHeartbeatRenderer for SimpleAnsi/NoAnsi:
- E1 silence heartbeat (one line after N seconds of no completion),
- E2 slow-test surfacing with exponential backoff,
- E3 failures continue to print inline.
- Collapse the SimpleAnsi/NoAnsi progress gating in TerminalOutputDevice.
- Knobs (env vars only): MTP_PROGRESS_SILENCE_SECONDS (default 30),
MTP_PROGRESS_SLOW_TEST_SECONDS (default 60); 0 disables.
- Localized resources + regenerated xlf; unit tests for all rules.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Evangelink

This comment has been minimized.

@Youssef1313

Copy link
Copy Markdown
Member

Amaury Levé (@Evangelink) Can you please add some before/after screenshots showing the behavior difference?

…etailed
The CI test steps passed --no-progress (and --output detailed), which force-disabled progress so the new SilenceDrivenHeartbeatRenderer added in this PR was never exercised on CI (SimpleAnsi mode). Dropping these flags makes the testfx CI fall into the heartbeat path and dogfood the feature.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Base automatically changed from dev/amauryleve/progress-option-9138 to mainJune 15, 2026 13:43
@Evangelink

Copy link
Copy Markdown
MemberAuthor

🧪 Test quality grade — PR #9147

10 tests graded (8 new, 2 modified) across 2 files. All tests score A. The new SilenceDrivenHeartbeatRendererTests suite is a model of deterministic unit-test design: FakeClock/FakeStopwatch eliminate all wall-clock coupling, assertions cover both positive and negative outcomes, and method names precisely describe scenario and expected behavior. The two modified tests in TerminalTestReporterTests needed only a constructor-signature update and retain their original quality. No actionable issues found.

ΔTestGradeBandNotes
newSilenceDrivenHeartbeatRendererTests.
OnWrite_
DoesNotEraseOrRenderProgress_
JustWrites
A90–100Three assertions verify pass-through semantics: content correct, erase not called, render not called.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
DuringProlongedSilence_
RepeatsOncePerInterval
A90–100Verifies repeat-per-interval behavior with three equality assertions at precise clock positions.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenCompletionsKeepHappening_
DoesNotEmit
A90–100Clean negative test: verifies silence is maintained when completions reset the timer.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenNoCompletionForThreshold_
EmitsSingleSummaryLine
A90–100Comprehensive boundary test: verifies no output below threshold and correct multi-field content above it.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenThresholdIsZero_
NeverEmits
A90–100No issues found.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenBelowThreshold_
EmitsNothing
A90–100No issues found.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenExceedingThreshold_
EmitsWithExponentialBackoff
A90–100Multi-phase verification of exponential backoff with content, count, and duration assertions.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenThresholdIsZero_
NeverEmits
A90–100DoesNotContain("[slow]") precisely scopes the assertion to slow-test output, correctly allowing heartbeat lines.
modTerminalTestReporterTests.
TestProgressStateAwareTerminal_
CanStopProgressAcrossMultipleSessions
A90–100No issues found.
modTerminalTestReporterTests.
TestProgressStateAwareTerminal_
WriteToTerminal_
ShouldEraseProgressThenRenderProgress
A90–100Event-ordering assertions with descriptive messages confirm the correct progress lifecycle sequence.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Re-run with
/grade-tests.

🤖 Automated content by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account — the account owner did not write or approve this content personally. Generated by the Grade Tests on PR (on open / sync) workflow. · 612.3 AIC · ⌖ 25.4 AIC · [◷]( · )

…eat-9139
# Conflicts:
#	test/IntegrationTests/MSTest.Acceptance.IntegrationTests/ShowOutputOptionTests.cs
CopilotAI review requested due to automatic review settings June 15, 2026 14:22

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends Microsoft.Testing.Platform’s terminal progress reporting to work in SimpleAnsi/NoAnsi scenarios (CI, redirected output) by introducing a renderer strategy abstraction and adding a new silence-driven “heartbeat” renderer that emits durable progress lines only when needed. It also wires in environment-variable knobs for heartbeat thresholds, adds localized resource strings, and updates unit tests and CI scripts accordingly.

Changes:

  • Introduces IProgressRenderer with CursorProgressRenderer (existing in-place redraw) and SilenceDrivenHeartbeatRenderer (new durable-line heartbeat/slow-test output).
  • Enables progress in non-cursor terminal modes by selecting an appropriate renderer rather than force-disabling progress.
  • Adds new localized resource strings + unit tests, and updates CI pipeline invocations.
Show a summary per file
FileDescription
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.csUpdates tests to pass the new renderer dependency into TestProgressStateAwareTerminal.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/SilenceDrivenHeartbeatRendererTests.csAdds deterministic unit tests covering silence heartbeat, slow-test backoff, and write-through behavior.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hant.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hans.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.tr.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ru.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pt-BR.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pl.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ko.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ja.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.it.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.fr.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.es.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.de.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.cs.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/PlatformResources.resxAdds 3 new localized strings used by the heartbeat renderer.
src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.csRemoves forced progress-disable for SimpleAnsi/NoAnsi and adds env-var threshold parsing into options.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.csRoutes tick/write behavior through IProgressRenderer and adds NotifyTestCompleted() hook.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporterOptions.csAdds options for heartbeat silence threshold and slow-test threshold.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.TestCompletion.csNotifies the progress renderer on each test completion to reset silence timing.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.csSelects cursor vs heartbeat renderer based on terminal capability and passes it into the progress-aware terminal wrapper.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/SilenceDrivenHeartbeatRenderer.csImplements silence heartbeat + slow-test durable line emission logic.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/IProgressRenderer.csDefines the rendering strategy interface and thread-safety expectations.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/CursorProgressRenderer.csExtracts the existing in-place redraw behavior behind IProgressRenderer.
eng/pipelines/steps/test-non-windows.ymlRemoves --no-progress/--output detailed from CI dotnet test invocations.
azure-pipelines.ymlRemoves --no-progress/--output detailed from Windows CI dotnet test invocations.

Copilot's findings

  • Files reviewed: 26/26 changed files
  • Comments generated: 8

Comment threadeng/pipelines/steps/test-non-windows.yml Outdated
Comment threadeng/pipelines/steps/test-non-windows.yml Outdated
Comment threadazure-pipelines.yml Outdated
Comment threadazure-pipelines.yml Outdated
Debug Test step routes through Microsoft.Testing.Platform.MSBuild's InvokeTestingPlatform
task, whose default TestingPlatformCaptureOutput=true buffers each test exe's stdout
into a per-module .log file and never logs it to the AzDO console (except on failure).
That hid the silence-driven heartbeat introduced in #9147: the renderer was firing but
the output was invisible to anyone watching the live pipeline.
Add -p:TestingPlatformCaptureOutput=false to the Debug 'dotnet test' invocations in
azure-pipelines.yml and eng/pipelines/steps/test-non-windows.yml so stdout is forwarded
to Log.LogMessage(MessageImportance.High, ...). The Release step uses --test-modules,
which bypasses MSBuild entirely, so it already streams per-module output and needs no
change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Publish _clock via Volatile.Write/Read to avoid reading a stale null across threads.
- Report a slow test's actual elapsed time instead of the scheduled threshold, so a delayed tick does not under-report the runtime.
- Let each IProgressRenderer choose its tick cadence; the silence-driven heartbeat now ticks once per second instead of every 500ms.
- Add regression test for the delayed-tick elapsed-time reporting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 15, 2026 15:21

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 26/26 changed files
  • Comments generated: 2

…eat-9139
# Conflicts:
#	azure-pipelines.yml
#	eng/pipelines/steps/test-non-windows.yml
@Evangelink
Amaury Levé (Evangelink) merged commit c738388 into mainJun 16, 2026
32 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/heartbeat-9139 branch June 16, 2026 09:48
Amaury Levé (Evangelink) added a commit that referenced this pull request Jun 17, 2026
…#9176`, `#9177` (#9207)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Silence-driven progress heartbeat renderer for SimpleAnsi/NoAnsi terminal modes

4 participants

@Evangelink@Youssef1313@JanKrivanek
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Add silence-driven progress heartbeat for SimpleAnsi/NoAnsi modes (#9139) - #9147

Merged
Amaury Levé (Evangelink) merged 7 commits into
mainfrom
dev/amauryleve/heartbeat-9139
Jun 16, 2026
Merged

Add silence-driven progress heartbeat for SimpleAnsi/NoAnsi modes (#9139)#9147
Amaury Levé (Evangelink) merged 7 commits into
mainfrom
dev/amauryleve/heartbeat-9139

Conversation

@Evangelink

@EvangelinkAmaury Levé (Evangelink) commented Jun 15, 2026

Copy link
Copy Markdown
Member

Implements the silence-driven progress heartbeat renderer for the SimpleAnsi and NoAnsi terminal modes. Fixes#9139.

Stacked on #9145 (the --progress flag rename, #9138). Base is dev/amauryleve/progress-option-9138; review the heartbeat diff here and merge after #9145.

Problem

Today TerminalOutputDevice force-disables progress for SimpleAnsi/NoAnsi, so CI / piped / file-redirected runs get zero progress signal between the banner and the summary. The old fix (a per-DLL summary every 3s) was rejected as spam (#6753). This design is silence-driven, not time-driven.

What changed

Renderer abstraction (OutputDevice/Terminal/)

  • IProgressRenderer — strategy for per-tick render, write-wrapping, and completion notification.
  • CursorProgressRenderer — the existing in-place redraw, extracted verbatim (no behavior change for ANSI cursor modes).
  • SilenceDrivenHeartbeatRenderernew, emits single durable lines only when needed:
    • E1 silence heartbeatrunning... N completed, M failed | active: X after N seconds of no completion; repeats once per interval during prolonged silence. A healthy fast suite emits nothing.
    • E2 slow-test[slow] still running after …: <test> (<asm>|<tfm>|<arch>) per running test over the threshold, with exponential backoff (60s → 2m → 4m …). Durable scrollback line.
    • E3 failures inline — verified: failures still print at the moment of failure.

Gating (TerminalOutputDevice.cs)

  • Collapse noProgress || ansiMode is NoAnsi or SimpleAnsi to just noProgress; renderer is chosen by resolved terminal capability. Test-host controller / --list-tests / server mode still suppress.

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)

Localization — 3 new strings in PlatformResources.resx with all 13 .xlf files regenerated via /t:UpdateXlf.

Before / After

The feature currently doesn't work with dotnet test and requires a change there but when running the exe itself (or via dotnet or dotnet run) we can see the change.

Before: no info during the 20s of the run.

image

After (the screenshot belows sets progress silence as 2s and slow tests as 2s too):

image

For a more real use case, on this pipeline with progress disabled and default output level, we have a gap of ~16.5min dead-air window. With this feature (assuming dotnet test handling is done), we would have about:

  • 40 lines of silence heartbeat - with default 30s threshold
  • 15 lines of slow tests

Tests

  • 8 new deterministic unit tests in SilenceDrivenHeartbeatRendererTests.cs (silence fire / no-fire, repeat-per-interval, completion reset, disabled, slow-test backoff, below-threshold, disabled, write-through).
  • Updated 2 existing tests for the new TestProgressStateAwareTerminal ctor signature.
  • Core platform builds clean (0 warnings); Microsoft.Testing.Platform.UnitTests green. No new CLI flag, so --help/--info acceptance expectations are unaffected.

Out of scope (deliberate)

…nal reporter
Fixes#9138
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements the silence-driven heartbeat renderer (#9139) so CI / piped /
file-redirected runs get a progress signal between banner and summary,
without the rejected fixed-cadence per-DLL summary (#6753).
- Extract IProgressRenderer strategy; keep existing in-place redraw as
CursorProgressRenderer (ANSI cursor modes, unchanged behavior).
- Add SilenceDrivenHeartbeatRenderer for SimpleAnsi/NoAnsi:
- E1 silence heartbeat (one line after N seconds of no completion),
- E2 slow-test surfacing with exponential backoff,
- E3 failures continue to print inline.
- Collapse the SimpleAnsi/NoAnsi progress gating in TerminalOutputDevice.
- Knobs (env vars only): MTP_PROGRESS_SILENCE_SECONDS (default 30),
MTP_PROGRESS_SLOW_TEST_SECONDS (default 60); 0 disables.
- Localized resources + regenerated xlf; unit tests for all rules.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Evangelink

This comment has been minimized.

@Youssef1313

Copy link
Copy Markdown
Member

Amaury Levé (@Evangelink) Can you please add some before/after screenshots showing the behavior difference?

…etailed
The CI test steps passed --no-progress (and --output detailed), which force-disabled progress so the new SilenceDrivenHeartbeatRenderer added in this PR was never exercised on CI (SimpleAnsi mode). Dropping these flags makes the testfx CI fall into the heartbeat path and dogfood the feature.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Base automatically changed from dev/amauryleve/progress-option-9138 to mainJune 15, 2026 13:43
@Evangelink

Copy link
Copy Markdown
MemberAuthor

🧪 Test quality grade — PR #9147

10 tests graded (8 new, 2 modified) across 2 files. All tests score A. The new SilenceDrivenHeartbeatRendererTests suite is a model of deterministic unit-test design: FakeClock/FakeStopwatch eliminate all wall-clock coupling, assertions cover both positive and negative outcomes, and method names precisely describe scenario and expected behavior. The two modified tests in TerminalTestReporterTests needed only a constructor-signature update and retain their original quality. No actionable issues found.

ΔTestGradeBandNotes
newSilenceDrivenHeartbeatRendererTests.
OnWrite_
DoesNotEraseOrRenderProgress_
JustWrites
A90–100Three assertions verify pass-through semantics: content correct, erase not called, render not called.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
DuringProlongedSilence_
RepeatsOncePerInterval
A90–100Verifies repeat-per-interval behavior with three equality assertions at precise clock positions.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenCompletionsKeepHappening_
DoesNotEmit
A90–100Clean negative test: verifies silence is maintained when completions reset the timer.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenNoCompletionForThreshold_
EmitsSingleSummaryLine
A90–100Comprehensive boundary test: verifies no output below threshold and correct multi-field content above it.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenThresholdIsZero_
NeverEmits
A90–100No issues found.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenBelowThreshold_
EmitsNothing
A90–100No issues found.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenExceedingThreshold_
EmitsWithExponentialBackoff
A90–100Multi-phase verification of exponential backoff with content, count, and duration assertions.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenThresholdIsZero_
NeverEmits
A90–100DoesNotContain("[slow]") precisely scopes the assertion to slow-test output, correctly allowing heartbeat lines.
modTerminalTestReporterTests.
TestProgressStateAwareTerminal_
CanStopProgressAcrossMultipleSessions
A90–100No issues found.
modTerminalTestReporterTests.
TestProgressStateAwareTerminal_
WriteToTerminal_
ShouldEraseProgressThenRenderProgress
A90–100Event-ordering assertions with descriptive messages confirm the correct progress lifecycle sequence.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Re-run with
/grade-tests.

🤖 Automated content by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account — the account owner did not write or approve this content personally. Generated by the Grade Tests on PR (on open / sync) workflow. · 612.3 AIC · ⌖ 25.4 AIC · [◷]( · )

…eat-9139
# Conflicts:
#	test/IntegrationTests/MSTest.Acceptance.IntegrationTests/ShowOutputOptionTests.cs
CopilotAI review requested due to automatic review settings June 15, 2026 14:22

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends Microsoft.Testing.Platform’s terminal progress reporting to work in SimpleAnsi/NoAnsi scenarios (CI, redirected output) by introducing a renderer strategy abstraction and adding a new silence-driven “heartbeat” renderer that emits durable progress lines only when needed. It also wires in environment-variable knobs for heartbeat thresholds, adds localized resource strings, and updates unit tests and CI scripts accordingly.

Changes:

  • Introduces IProgressRenderer with CursorProgressRenderer (existing in-place redraw) and SilenceDrivenHeartbeatRenderer (new durable-line heartbeat/slow-test output).
  • Enables progress in non-cursor terminal modes by selecting an appropriate renderer rather than force-disabling progress.
  • Adds new localized resource strings + unit tests, and updates CI pipeline invocations.
Show a summary per file
FileDescription
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.csUpdates tests to pass the new renderer dependency into TestProgressStateAwareTerminal.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/SilenceDrivenHeartbeatRendererTests.csAdds deterministic unit tests covering silence heartbeat, slow-test backoff, and write-through behavior.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hant.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hans.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.tr.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ru.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pt-BR.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pl.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ko.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ja.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.it.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.fr.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.es.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.de.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.cs.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/PlatformResources.resxAdds 3 new localized strings used by the heartbeat renderer.
src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.csRemoves forced progress-disable for SimpleAnsi/NoAnsi and adds env-var threshold parsing into options.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.csRoutes tick/write behavior through IProgressRenderer and adds NotifyTestCompleted() hook.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporterOptions.csAdds options for heartbeat silence threshold and slow-test threshold.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.TestCompletion.csNotifies the progress renderer on each test completion to reset silence timing.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.csSelects cursor vs heartbeat renderer based on terminal capability and passes it into the progress-aware terminal wrapper.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/SilenceDrivenHeartbeatRenderer.csImplements silence heartbeat + slow-test durable line emission logic.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/IProgressRenderer.csDefines the rendering strategy interface and thread-safety expectations.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/CursorProgressRenderer.csExtracts the existing in-place redraw behavior behind IProgressRenderer.
eng/pipelines/steps/test-non-windows.ymlRemoves --no-progress/--output detailed from CI dotnet test invocations.
azure-pipelines.ymlRemoves --no-progress/--output detailed from Windows CI dotnet test invocations.

Copilot's findings

  • Files reviewed: 26/26 changed files
  • Comments generated: 8

Comment threadeng/pipelines/steps/test-non-windows.yml Outdated
Comment threadeng/pipelines/steps/test-non-windows.yml Outdated
Comment threadazure-pipelines.yml Outdated
Comment threadazure-pipelines.yml Outdated
Debug Test step routes through Microsoft.Testing.Platform.MSBuild's InvokeTestingPlatform
task, whose default TestingPlatformCaptureOutput=true buffers each test exe's stdout
into a per-module .log file and never logs it to the AzDO console (except on failure).
That hid the silence-driven heartbeat introduced in #9147: the renderer was firing but
the output was invisible to anyone watching the live pipeline.
Add -p:TestingPlatformCaptureOutput=false to the Debug 'dotnet test' invocations in
azure-pipelines.yml and eng/pipelines/steps/test-non-windows.yml so stdout is forwarded
to Log.LogMessage(MessageImportance.High, ...). The Release step uses --test-modules,
which bypasses MSBuild entirely, so it already streams per-module output and needs no
change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Publish _clock via Volatile.Write/Read to avoid reading a stale null across threads.
- Report a slow test's actual elapsed time instead of the scheduled threshold, so a delayed tick does not under-report the runtime.
- Let each IProgressRenderer choose its tick cadence; the silence-driven heartbeat now ticks once per second instead of every 500ms.
- Add regression test for the delayed-tick elapsed-time reporting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 15, 2026 15:21

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 26/26 changed files
  • Comments generated: 2

…eat-9139
# Conflicts:
#	azure-pipelines.yml
#	eng/pipelines/steps/test-non-windows.yml
@Evangelink
Amaury Levé (Evangelink) merged commit c738388 into mainJun 16, 2026
32 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/heartbeat-9139 branch June 16, 2026 09:48
Amaury Levé (Evangelink) added a commit that referenced this pull request Jun 17, 2026
…#9176`, `#9177` (#9207)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Silence-driven progress heartbeat renderer for SimpleAnsi/NoAnsi terminal modes

4 participants

@Evangelink@Youssef1313@JanKrivanek
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add silence-driven progress heartbeat for SimpleAnsi/NoAnsi modes (#9139) - #9147

Merged
Amaury Levé (Evangelink) merged 7 commits into
mainfrom
dev/amauryleve/heartbeat-9139
Jun 16, 2026
Merged

Add silence-driven progress heartbeat for SimpleAnsi/NoAnsi modes (#9139)#9147
Amaury Levé (Evangelink) merged 7 commits into
mainfrom
dev/amauryleve/heartbeat-9139

Conversation

@Evangelink

@EvangelinkAmaury Levé (Evangelink) commented Jun 15, 2026

Copy link
Copy Markdown
Member

Implements the silence-driven progress heartbeat renderer for the SimpleAnsi and NoAnsi terminal modes. Fixes#9139.

Stacked on #9145 (the --progress flag rename, #9138). Base is dev/amauryleve/progress-option-9138; review the heartbeat diff here and merge after #9145.

Problem

Today TerminalOutputDevice force-disables progress for SimpleAnsi/NoAnsi, so CI / piped / file-redirected runs get zero progress signal between the banner and the summary. The old fix (a per-DLL summary every 3s) was rejected as spam (#6753). This design is silence-driven, not time-driven.

What changed

Renderer abstraction (OutputDevice/Terminal/)

  • IProgressRenderer — strategy for per-tick render, write-wrapping, and completion notification.
  • CursorProgressRenderer — the existing in-place redraw, extracted verbatim (no behavior change for ANSI cursor modes).
  • SilenceDrivenHeartbeatRenderernew, emits single durable lines only when needed:
    • E1 silence heartbeatrunning... N completed, M failed | active: X after N seconds of no completion; repeats once per interval during prolonged silence. A healthy fast suite emits nothing.
    • E2 slow-test[slow] still running after …: <test> (<asm>|<tfm>|<arch>) per running test over the threshold, with exponential backoff (60s → 2m → 4m …). Durable scrollback line.
    • E3 failures inline — verified: failures still print at the moment of failure.

Gating (TerminalOutputDevice.cs)

  • Collapse noProgress || ansiMode is NoAnsi or SimpleAnsi to just noProgress; renderer is chosen by resolved terminal capability. Test-host controller / --list-tests / server mode still suppress.

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)

Localization — 3 new strings in PlatformResources.resx with all 13 .xlf files regenerated via /t:UpdateXlf.

Before / After

The feature currently doesn't work with dotnet test and requires a change there but when running the exe itself (or via dotnet or dotnet run) we can see the change.

Before: no info during the 20s of the run.

image

After (the screenshot belows sets progress silence as 2s and slow tests as 2s too):

image

For a more real use case, on this pipeline with progress disabled and default output level, we have a gap of ~16.5min dead-air window. With this feature (assuming dotnet test handling is done), we would have about:

  • 40 lines of silence heartbeat - with default 30s threshold
  • 15 lines of slow tests

Tests

  • 8 new deterministic unit tests in SilenceDrivenHeartbeatRendererTests.cs (silence fire / no-fire, repeat-per-interval, completion reset, disabled, slow-test backoff, below-threshold, disabled, write-through).
  • Updated 2 existing tests for the new TestProgressStateAwareTerminal ctor signature.
  • Core platform builds clean (0 warnings); Microsoft.Testing.Platform.UnitTests green. No new CLI flag, so --help/--info acceptance expectations are unaffected.

Out of scope (deliberate)

…nal reporter
Fixes#9138
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements the silence-driven heartbeat renderer (#9139) so CI / piped /
file-redirected runs get a progress signal between banner and summary,
without the rejected fixed-cadence per-DLL summary (#6753).
- Extract IProgressRenderer strategy; keep existing in-place redraw as
CursorProgressRenderer (ANSI cursor modes, unchanged behavior).
- Add SilenceDrivenHeartbeatRenderer for SimpleAnsi/NoAnsi:
- E1 silence heartbeat (one line after N seconds of no completion),
- E2 slow-test surfacing with exponential backoff,
- E3 failures continue to print inline.
- Collapse the SimpleAnsi/NoAnsi progress gating in TerminalOutputDevice.
- Knobs (env vars only): MTP_PROGRESS_SILENCE_SECONDS (default 30),
MTP_PROGRESS_SLOW_TEST_SECONDS (default 60); 0 disables.
- Localized resources + regenerated xlf; unit tests for all rules.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Evangelink

This comment has been minimized.

@Youssef1313

Copy link
Copy Markdown
Member

Amaury Levé (@Evangelink) Can you please add some before/after screenshots showing the behavior difference?

…etailed
The CI test steps passed --no-progress (and --output detailed), which force-disabled progress so the new SilenceDrivenHeartbeatRenderer added in this PR was never exercised on CI (SimpleAnsi mode). Dropping these flags makes the testfx CI fall into the heartbeat path and dogfood the feature.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Base automatically changed from dev/amauryleve/progress-option-9138 to mainJune 15, 2026 13:43
@Evangelink

Copy link
Copy Markdown
MemberAuthor

🧪 Test quality grade — PR #9147

10 tests graded (8 new, 2 modified) across 2 files. All tests score A. The new SilenceDrivenHeartbeatRendererTests suite is a model of deterministic unit-test design: FakeClock/FakeStopwatch eliminate all wall-clock coupling, assertions cover both positive and negative outcomes, and method names precisely describe scenario and expected behavior. The two modified tests in TerminalTestReporterTests needed only a constructor-signature update and retain their original quality. No actionable issues found.

ΔTestGradeBandNotes
newSilenceDrivenHeartbeatRendererTests.
OnWrite_
DoesNotEraseOrRenderProgress_
JustWrites
A90–100Three assertions verify pass-through semantics: content correct, erase not called, render not called.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
DuringProlongedSilence_
RepeatsOncePerInterval
A90–100Verifies repeat-per-interval behavior with three equality assertions at precise clock positions.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenCompletionsKeepHappening_
DoesNotEmit
A90–100Clean negative test: verifies silence is maintained when completions reset the timer.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenNoCompletionForThreshold_
EmitsSingleSummaryLine
A90–100Comprehensive boundary test: verifies no output below threshold and correct multi-field content above it.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenThresholdIsZero_
NeverEmits
A90–100No issues found.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenBelowThreshold_
EmitsNothing
A90–100No issues found.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenExceedingThreshold_
EmitsWithExponentialBackoff
A90–100Multi-phase verification of exponential backoff with content, count, and duration assertions.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenThresholdIsZero_
NeverEmits
A90–100DoesNotContain("[slow]") precisely scopes the assertion to slow-test output, correctly allowing heartbeat lines.
modTerminalTestReporterTests.
TestProgressStateAwareTerminal_
CanStopProgressAcrossMultipleSessions
A90–100No issues found.
modTerminalTestReporterTests.
TestProgressStateAwareTerminal_
WriteToTerminal_
ShouldEraseProgressThenRenderProgress
A90–100Event-ordering assertions with descriptive messages confirm the correct progress lifecycle sequence.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Re-run with
/grade-tests.

🤖 Automated content by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account — the account owner did not write or approve this content personally. Generated by the Grade Tests on PR (on open / sync) workflow. · 612.3 AIC · ⌖ 25.4 AIC · [◷]( · )

…eat-9139
# Conflicts:
#	test/IntegrationTests/MSTest.Acceptance.IntegrationTests/ShowOutputOptionTests.cs
CopilotAI review requested due to automatic review settings June 15, 2026 14:22

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends Microsoft.Testing.Platform’s terminal progress reporting to work in SimpleAnsi/NoAnsi scenarios (CI, redirected output) by introducing a renderer strategy abstraction and adding a new silence-driven “heartbeat” renderer that emits durable progress lines only when needed. It also wires in environment-variable knobs for heartbeat thresholds, adds localized resource strings, and updates unit tests and CI scripts accordingly.

Changes:

  • Introduces IProgressRenderer with CursorProgressRenderer (existing in-place redraw) and SilenceDrivenHeartbeatRenderer (new durable-line heartbeat/slow-test output).
  • Enables progress in non-cursor terminal modes by selecting an appropriate renderer rather than force-disabling progress.
  • Adds new localized resource strings + unit tests, and updates CI pipeline invocations.
Show a summary per file
FileDescription
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.csUpdates tests to pass the new renderer dependency into TestProgressStateAwareTerminal.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/SilenceDrivenHeartbeatRendererTests.csAdds deterministic unit tests covering silence heartbeat, slow-test backoff, and write-through behavior.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hant.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hans.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.tr.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ru.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pt-BR.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pl.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ko.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ja.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.it.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.fr.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.es.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.de.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.cs.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/PlatformResources.resxAdds 3 new localized strings used by the heartbeat renderer.
src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.csRemoves forced progress-disable for SimpleAnsi/NoAnsi and adds env-var threshold parsing into options.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.csRoutes tick/write behavior through IProgressRenderer and adds NotifyTestCompleted() hook.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporterOptions.csAdds options for heartbeat silence threshold and slow-test threshold.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.TestCompletion.csNotifies the progress renderer on each test completion to reset silence timing.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.csSelects cursor vs heartbeat renderer based on terminal capability and passes it into the progress-aware terminal wrapper.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/SilenceDrivenHeartbeatRenderer.csImplements silence heartbeat + slow-test durable line emission logic.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/IProgressRenderer.csDefines the rendering strategy interface and thread-safety expectations.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/CursorProgressRenderer.csExtracts the existing in-place redraw behavior behind IProgressRenderer.
eng/pipelines/steps/test-non-windows.ymlRemoves --no-progress/--output detailed from CI dotnet test invocations.
azure-pipelines.ymlRemoves --no-progress/--output detailed from Windows CI dotnet test invocations.

Copilot's findings

  • Files reviewed: 26/26 changed files
  • Comments generated: 8

Comment threadeng/pipelines/steps/test-non-windows.yml Outdated
Comment threadeng/pipelines/steps/test-non-windows.yml Outdated
Comment threadazure-pipelines.yml Outdated
Comment threadazure-pipelines.yml Outdated
Debug Test step routes through Microsoft.Testing.Platform.MSBuild's InvokeTestingPlatform
task, whose default TestingPlatformCaptureOutput=true buffers each test exe's stdout
into a per-module .log file and never logs it to the AzDO console (except on failure).
That hid the silence-driven heartbeat introduced in #9147: the renderer was firing but
the output was invisible to anyone watching the live pipeline.
Add -p:TestingPlatformCaptureOutput=false to the Debug 'dotnet test' invocations in
azure-pipelines.yml and eng/pipelines/steps/test-non-windows.yml so stdout is forwarded
to Log.LogMessage(MessageImportance.High, ...). The Release step uses --test-modules,
which bypasses MSBuild entirely, so it already streams per-module output and needs no
change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Publish _clock via Volatile.Write/Read to avoid reading a stale null across threads.
- Report a slow test's actual elapsed time instead of the scheduled threshold, so a delayed tick does not under-report the runtime.
- Let each IProgressRenderer choose its tick cadence; the silence-driven heartbeat now ticks once per second instead of every 500ms.
- Add regression test for the delayed-tick elapsed-time reporting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 15, 2026 15:21

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 26/26 changed files
  • Comments generated: 2

…eat-9139
# Conflicts:
#	azure-pipelines.yml
#	eng/pipelines/steps/test-non-windows.yml
@Evangelink
Amaury Levé (Evangelink) merged commit c738388 into mainJun 16, 2026
32 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/heartbeat-9139 branch June 16, 2026 09:48
Amaury Levé (Evangelink) added a commit that referenced this pull request Jun 17, 2026
…#9176`, `#9177` (#9207)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Silence-driven progress heartbeat renderer for SimpleAnsi/NoAnsi terminal modes

4 participants

@Evangelink@Youssef1313@JanKrivanek
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add silence-driven progress heartbeat for SimpleAnsi/NoAnsi modes (#9139) - #9147

Merged
Amaury Levé (Evangelink) merged 7 commits into
mainfrom
dev/amauryleve/heartbeat-9139
Jun 16, 2026
Merged

Add silence-driven progress heartbeat for SimpleAnsi/NoAnsi modes (#9139)#9147
Amaury Levé (Evangelink) merged 7 commits into
mainfrom
dev/amauryleve/heartbeat-9139

Conversation

@Evangelink

@EvangelinkAmaury Levé (Evangelink) commented Jun 15, 2026

Copy link
Copy Markdown
Member

Implements the silence-driven progress heartbeat renderer for the SimpleAnsi and NoAnsi terminal modes. Fixes#9139.

Stacked on #9145 (the --progress flag rename, #9138). Base is dev/amauryleve/progress-option-9138; review the heartbeat diff here and merge after #9145.

Problem

Today TerminalOutputDevice force-disables progress for SimpleAnsi/NoAnsi, so CI / piped / file-redirected runs get zero progress signal between the banner and the summary. The old fix (a per-DLL summary every 3s) was rejected as spam (#6753). This design is silence-driven, not time-driven.

What changed

Renderer abstraction (OutputDevice/Terminal/)

  • IProgressRenderer — strategy for per-tick render, write-wrapping, and completion notification.
  • CursorProgressRenderer — the existing in-place redraw, extracted verbatim (no behavior change for ANSI cursor modes).
  • SilenceDrivenHeartbeatRenderernew, emits single durable lines only when needed:
    • E1 silence heartbeatrunning... N completed, M failed | active: X after N seconds of no completion; repeats once per interval during prolonged silence. A healthy fast suite emits nothing.
    • E2 slow-test[slow] still running after …: <test> (<asm>|<tfm>|<arch>) per running test over the threshold, with exponential backoff (60s → 2m → 4m …). Durable scrollback line.
    • E3 failures inline — verified: failures still print at the moment of failure.

Gating (TerminalOutputDevice.cs)

  • Collapse noProgress || ansiMode is NoAnsi or SimpleAnsi to just noProgress; renderer is chosen by resolved terminal capability. Test-host controller / --list-tests / server mode still suppress.

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)

Localization — 3 new strings in PlatformResources.resx with all 13 .xlf files regenerated via /t:UpdateXlf.

Before / After

The feature currently doesn't work with dotnet test and requires a change there but when running the exe itself (or via dotnet or dotnet run) we can see the change.

Before: no info during the 20s of the run.

image

After (the screenshot belows sets progress silence as 2s and slow tests as 2s too):

image

For a more real use case, on this pipeline with progress disabled and default output level, we have a gap of ~16.5min dead-air window. With this feature (assuming dotnet test handling is done), we would have about:

  • 40 lines of silence heartbeat - with default 30s threshold
  • 15 lines of slow tests

Tests

  • 8 new deterministic unit tests in SilenceDrivenHeartbeatRendererTests.cs (silence fire / no-fire, repeat-per-interval, completion reset, disabled, slow-test backoff, below-threshold, disabled, write-through).
  • Updated 2 existing tests for the new TestProgressStateAwareTerminal ctor signature.
  • Core platform builds clean (0 warnings); Microsoft.Testing.Platform.UnitTests green. No new CLI flag, so --help/--info acceptance expectations are unaffected.

Out of scope (deliberate)

…nal reporter
Fixes#9138
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements the silence-driven heartbeat renderer (#9139) so CI / piped /
file-redirected runs get a progress signal between banner and summary,
without the rejected fixed-cadence per-DLL summary (#6753).
- Extract IProgressRenderer strategy; keep existing in-place redraw as
CursorProgressRenderer (ANSI cursor modes, unchanged behavior).
- Add SilenceDrivenHeartbeatRenderer for SimpleAnsi/NoAnsi:
- E1 silence heartbeat (one line after N seconds of no completion),
- E2 slow-test surfacing with exponential backoff,
- E3 failures continue to print inline.
- Collapse the SimpleAnsi/NoAnsi progress gating in TerminalOutputDevice.
- Knobs (env vars only): MTP_PROGRESS_SILENCE_SECONDS (default 30),
MTP_PROGRESS_SLOW_TEST_SECONDS (default 60); 0 disables.
- Localized resources + regenerated xlf; unit tests for all rules.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Evangelink

This comment has been minimized.

@Youssef1313

Copy link
Copy Markdown
Member

Amaury Levé (@Evangelink) Can you please add some before/after screenshots showing the behavior difference?

…etailed
The CI test steps passed --no-progress (and --output detailed), which force-disabled progress so the new SilenceDrivenHeartbeatRenderer added in this PR was never exercised on CI (SimpleAnsi mode). Dropping these flags makes the testfx CI fall into the heartbeat path and dogfood the feature.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Base automatically changed from dev/amauryleve/progress-option-9138 to mainJune 15, 2026 13:43
@Evangelink

Copy link
Copy Markdown
MemberAuthor

🧪 Test quality grade — PR #9147

10 tests graded (8 new, 2 modified) across 2 files. All tests score A. The new SilenceDrivenHeartbeatRendererTests suite is a model of deterministic unit-test design: FakeClock/FakeStopwatch eliminate all wall-clock coupling, assertions cover both positive and negative outcomes, and method names precisely describe scenario and expected behavior. The two modified tests in TerminalTestReporterTests needed only a constructor-signature update and retain their original quality. No actionable issues found.

ΔTestGradeBandNotes
newSilenceDrivenHeartbeatRendererTests.
OnWrite_
DoesNotEraseOrRenderProgress_
JustWrites
A90–100Three assertions verify pass-through semantics: content correct, erase not called, render not called.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
DuringProlongedSilence_
RepeatsOncePerInterval
A90–100Verifies repeat-per-interval behavior with three equality assertions at precise clock positions.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenCompletionsKeepHappening_
DoesNotEmit
A90–100Clean negative test: verifies silence is maintained when completions reset the timer.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenNoCompletionForThreshold_
EmitsSingleSummaryLine
A90–100Comprehensive boundary test: verifies no output below threshold and correct multi-field content above it.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenThresholdIsZero_
NeverEmits
A90–100No issues found.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenBelowThreshold_
EmitsNothing
A90–100No issues found.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenExceedingThreshold_
EmitsWithExponentialBackoff
A90–100Multi-phase verification of exponential backoff with content, count, and duration assertions.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenThresholdIsZero_
NeverEmits
A90–100DoesNotContain("[slow]") precisely scopes the assertion to slow-test output, correctly allowing heartbeat lines.
modTerminalTestReporterTests.
TestProgressStateAwareTerminal_
CanStopProgressAcrossMultipleSessions
A90–100No issues found.
modTerminalTestReporterTests.
TestProgressStateAwareTerminal_
WriteToTerminal_
ShouldEraseProgressThenRenderProgress
A90–100Event-ordering assertions with descriptive messages confirm the correct progress lifecycle sequence.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Re-run with
/grade-tests.

🤖 Automated content by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account — the account owner did not write or approve this content personally. Generated by the Grade Tests on PR (on open / sync) workflow. · 612.3 AIC · ⌖ 25.4 AIC · [◷]( · )

…eat-9139
# Conflicts:
#	test/IntegrationTests/MSTest.Acceptance.IntegrationTests/ShowOutputOptionTests.cs
CopilotAI review requested due to automatic review settings June 15, 2026 14:22

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends Microsoft.Testing.Platform’s terminal progress reporting to work in SimpleAnsi/NoAnsi scenarios (CI, redirected output) by introducing a renderer strategy abstraction and adding a new silence-driven “heartbeat” renderer that emits durable progress lines only when needed. It also wires in environment-variable knobs for heartbeat thresholds, adds localized resource strings, and updates unit tests and CI scripts accordingly.

Changes:

  • Introduces IProgressRenderer with CursorProgressRenderer (existing in-place redraw) and SilenceDrivenHeartbeatRenderer (new durable-line heartbeat/slow-test output).
  • Enables progress in non-cursor terminal modes by selecting an appropriate renderer rather than force-disabling progress.
  • Adds new localized resource strings + unit tests, and updates CI pipeline invocations.
Show a summary per file
FileDescription
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.csUpdates tests to pass the new renderer dependency into TestProgressStateAwareTerminal.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/SilenceDrivenHeartbeatRendererTests.csAdds deterministic unit tests covering silence heartbeat, slow-test backoff, and write-through behavior.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hant.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hans.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.tr.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ru.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pt-BR.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pl.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ko.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ja.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.it.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.fr.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.es.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.de.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.cs.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/PlatformResources.resxAdds 3 new localized strings used by the heartbeat renderer.
src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.csRemoves forced progress-disable for SimpleAnsi/NoAnsi and adds env-var threshold parsing into options.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.csRoutes tick/write behavior through IProgressRenderer and adds NotifyTestCompleted() hook.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporterOptions.csAdds options for heartbeat silence threshold and slow-test threshold.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.TestCompletion.csNotifies the progress renderer on each test completion to reset silence timing.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.csSelects cursor vs heartbeat renderer based on terminal capability and passes it into the progress-aware terminal wrapper.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/SilenceDrivenHeartbeatRenderer.csImplements silence heartbeat + slow-test durable line emission logic.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/IProgressRenderer.csDefines the rendering strategy interface and thread-safety expectations.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/CursorProgressRenderer.csExtracts the existing in-place redraw behavior behind IProgressRenderer.
eng/pipelines/steps/test-non-windows.ymlRemoves --no-progress/--output detailed from CI dotnet test invocations.
azure-pipelines.ymlRemoves --no-progress/--output detailed from Windows CI dotnet test invocations.

Copilot's findings

  • Files reviewed: 26/26 changed files
  • Comments generated: 8

Comment threadeng/pipelines/steps/test-non-windows.yml Outdated
Comment threadeng/pipelines/steps/test-non-windows.yml Outdated
Comment threadazure-pipelines.yml Outdated
Comment threadazure-pipelines.yml Outdated
Debug Test step routes through Microsoft.Testing.Platform.MSBuild's InvokeTestingPlatform
task, whose default TestingPlatformCaptureOutput=true buffers each test exe's stdout
into a per-module .log file and never logs it to the AzDO console (except on failure).
That hid the silence-driven heartbeat introduced in #9147: the renderer was firing but
the output was invisible to anyone watching the live pipeline.
Add -p:TestingPlatformCaptureOutput=false to the Debug 'dotnet test' invocations in
azure-pipelines.yml and eng/pipelines/steps/test-non-windows.yml so stdout is forwarded
to Log.LogMessage(MessageImportance.High, ...). The Release step uses --test-modules,
which bypasses MSBuild entirely, so it already streams per-module output and needs no
change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Publish _clock via Volatile.Write/Read to avoid reading a stale null across threads.
- Report a slow test's actual elapsed time instead of the scheduled threshold, so a delayed tick does not under-report the runtime.
- Let each IProgressRenderer choose its tick cadence; the silence-driven heartbeat now ticks once per second instead of every 500ms.
- Add regression test for the delayed-tick elapsed-time reporting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 15, 2026 15:21

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 26/26 changed files
  • Comments generated: 2

…eat-9139
# Conflicts:
#	azure-pipelines.yml
#	eng/pipelines/steps/test-non-windows.yml
@Evangelink
Amaury Levé (Evangelink) merged commit c738388 into mainJun 16, 2026
32 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/heartbeat-9139 branch June 16, 2026 09:48
Amaury Levé (Evangelink) added a commit that referenced this pull request Jun 17, 2026
…#9176`, `#9177` (#9207)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Silence-driven progress heartbeat renderer for SimpleAnsi/NoAnsi terminal modes

4 participants

@Evangelink@Youssef1313@JanKrivanek
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Add silence-driven progress heartbeat for SimpleAnsi/NoAnsi modes (#9139) - #9147

Merged
Amaury Levé (Evangelink) merged 7 commits into
mainfrom
dev/amauryleve/heartbeat-9139
Jun 16, 2026
Merged

Add silence-driven progress heartbeat for SimpleAnsi/NoAnsi modes (#9139)#9147
Amaury Levé (Evangelink) merged 7 commits into
mainfrom
dev/amauryleve/heartbeat-9139

Conversation

@Evangelink

@EvangelinkAmaury Levé (Evangelink) commented Jun 15, 2026

Copy link
Copy Markdown
Member

Implements the silence-driven progress heartbeat renderer for the SimpleAnsi and NoAnsi terminal modes. Fixes#9139.

Stacked on #9145 (the --progress flag rename, #9138). Base is dev/amauryleve/progress-option-9138; review the heartbeat diff here and merge after #9145.

Problem

Today TerminalOutputDevice force-disables progress for SimpleAnsi/NoAnsi, so CI / piped / file-redirected runs get zero progress signal between the banner and the summary. The old fix (a per-DLL summary every 3s) was rejected as spam (#6753). This design is silence-driven, not time-driven.

What changed

Renderer abstraction (OutputDevice/Terminal/)

  • IProgressRenderer — strategy for per-tick render, write-wrapping, and completion notification.
  • CursorProgressRenderer — the existing in-place redraw, extracted verbatim (no behavior change for ANSI cursor modes).
  • SilenceDrivenHeartbeatRenderernew, emits single durable lines only when needed:
    • E1 silence heartbeatrunning... N completed, M failed | active: X after N seconds of no completion; repeats once per interval during prolonged silence. A healthy fast suite emits nothing.
    • E2 slow-test[slow] still running after …: <test> (<asm>|<tfm>|<arch>) per running test over the threshold, with exponential backoff (60s → 2m → 4m …). Durable scrollback line.
    • E3 failures inline — verified: failures still print at the moment of failure.

Gating (TerminalOutputDevice.cs)

  • Collapse noProgress || ansiMode is NoAnsi or SimpleAnsi to just noProgress; renderer is chosen by resolved terminal capability. Test-host controller / --list-tests / server mode still suppress.

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)

Localization — 3 new strings in PlatformResources.resx with all 13 .xlf files regenerated via /t:UpdateXlf.

Before / After

The feature currently doesn't work with dotnet test and requires a change there but when running the exe itself (or via dotnet or dotnet run) we can see the change.

Before: no info during the 20s of the run.

image

After (the screenshot belows sets progress silence as 2s and slow tests as 2s too):

image

For a more real use case, on this pipeline with progress disabled and default output level, we have a gap of ~16.5min dead-air window. With this feature (assuming dotnet test handling is done), we would have about:

  • 40 lines of silence heartbeat - with default 30s threshold
  • 15 lines of slow tests

Tests

  • 8 new deterministic unit tests in SilenceDrivenHeartbeatRendererTests.cs (silence fire / no-fire, repeat-per-interval, completion reset, disabled, slow-test backoff, below-threshold, disabled, write-through).
  • Updated 2 existing tests for the new TestProgressStateAwareTerminal ctor signature.
  • Core platform builds clean (0 warnings); Microsoft.Testing.Platform.UnitTests green. No new CLI flag, so --help/--info acceptance expectations are unaffected.

Out of scope (deliberate)

…nal reporter
Fixes#9138
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements the silence-driven heartbeat renderer (#9139) so CI / piped /
file-redirected runs get a progress signal between banner and summary,
without the rejected fixed-cadence per-DLL summary (#6753).
- Extract IProgressRenderer strategy; keep existing in-place redraw as
CursorProgressRenderer (ANSI cursor modes, unchanged behavior).
- Add SilenceDrivenHeartbeatRenderer for SimpleAnsi/NoAnsi:
- E1 silence heartbeat (one line after N seconds of no completion),
- E2 slow-test surfacing with exponential backoff,
- E3 failures continue to print inline.
- Collapse the SimpleAnsi/NoAnsi progress gating in TerminalOutputDevice.
- Knobs (env vars only): MTP_PROGRESS_SILENCE_SECONDS (default 30),
MTP_PROGRESS_SLOW_TEST_SECONDS (default 60); 0 disables.
- Localized resources + regenerated xlf; unit tests for all rules.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Evangelink

This comment has been minimized.

@Youssef1313

Copy link
Copy Markdown
Member

Amaury Levé (@Evangelink) Can you please add some before/after screenshots showing the behavior difference?

…etailed
The CI test steps passed --no-progress (and --output detailed), which force-disabled progress so the new SilenceDrivenHeartbeatRenderer added in this PR was never exercised on CI (SimpleAnsi mode). Dropping these flags makes the testfx CI fall into the heartbeat path and dogfood the feature.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Base automatically changed from dev/amauryleve/progress-option-9138 to mainJune 15, 2026 13:43
@Evangelink

Copy link
Copy Markdown
MemberAuthor

🧪 Test quality grade — PR #9147

10 tests graded (8 new, 2 modified) across 2 files. All tests score A. The new SilenceDrivenHeartbeatRendererTests suite is a model of deterministic unit-test design: FakeClock/FakeStopwatch eliminate all wall-clock coupling, assertions cover both positive and negative outcomes, and method names precisely describe scenario and expected behavior. The two modified tests in TerminalTestReporterTests needed only a constructor-signature update and retain their original quality. No actionable issues found.

ΔTestGradeBandNotes
newSilenceDrivenHeartbeatRendererTests.
OnWrite_
DoesNotEraseOrRenderProgress_
JustWrites
A90–100Three assertions verify pass-through semantics: content correct, erase not called, render not called.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
DuringProlongedSilence_
RepeatsOncePerInterval
A90–100Verifies repeat-per-interval behavior with three equality assertions at precise clock positions.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenCompletionsKeepHappening_
DoesNotEmit
A90–100Clean negative test: verifies silence is maintained when completions reset the timer.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenNoCompletionForThreshold_
EmitsSingleSummaryLine
A90–100Comprehensive boundary test: verifies no output below threshold and correct multi-field content above it.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenThresholdIsZero_
NeverEmits
A90–100No issues found.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenBelowThreshold_
EmitsNothing
A90–100No issues found.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenExceedingThreshold_
EmitsWithExponentialBackoff
A90–100Multi-phase verification of exponential backoff with content, count, and duration assertions.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenThresholdIsZero_
NeverEmits
A90–100DoesNotContain("[slow]") precisely scopes the assertion to slow-test output, correctly allowing heartbeat lines.
modTerminalTestReporterTests.
TestProgressStateAwareTerminal_
CanStopProgressAcrossMultipleSessions
A90–100No issues found.
modTerminalTestReporterTests.
TestProgressStateAwareTerminal_
WriteToTerminal_
ShouldEraseProgressThenRenderProgress
A90–100Event-ordering assertions with descriptive messages confirm the correct progress lifecycle sequence.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Re-run with
/grade-tests.

🤖 Automated content by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account — the account owner did not write or approve this content personally. Generated by the Grade Tests on PR (on open / sync) workflow. · 612.3 AIC · ⌖ 25.4 AIC · [◷]( · )

…eat-9139
# Conflicts:
#	test/IntegrationTests/MSTest.Acceptance.IntegrationTests/ShowOutputOptionTests.cs
CopilotAI review requested due to automatic review settings June 15, 2026 14:22

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends Microsoft.Testing.Platform’s terminal progress reporting to work in SimpleAnsi/NoAnsi scenarios (CI, redirected output) by introducing a renderer strategy abstraction and adding a new silence-driven “heartbeat” renderer that emits durable progress lines only when needed. It also wires in environment-variable knobs for heartbeat thresholds, adds localized resource strings, and updates unit tests and CI scripts accordingly.

Changes:

  • Introduces IProgressRenderer with CursorProgressRenderer (existing in-place redraw) and SilenceDrivenHeartbeatRenderer (new durable-line heartbeat/slow-test output).
  • Enables progress in non-cursor terminal modes by selecting an appropriate renderer rather than force-disabling progress.
  • Adds new localized resource strings + unit tests, and updates CI pipeline invocations.
Show a summary per file
FileDescription
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.csUpdates tests to pass the new renderer dependency into TestProgressStateAwareTerminal.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/SilenceDrivenHeartbeatRendererTests.csAdds deterministic unit tests covering silence heartbeat, slow-test backoff, and write-through behavior.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hant.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hans.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.tr.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ru.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pt-BR.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pl.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ko.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ja.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.it.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.fr.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.es.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.de.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.cs.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/PlatformResources.resxAdds 3 new localized strings used by the heartbeat renderer.
src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.csRemoves forced progress-disable for SimpleAnsi/NoAnsi and adds env-var threshold parsing into options.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.csRoutes tick/write behavior through IProgressRenderer and adds NotifyTestCompleted() hook.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporterOptions.csAdds options for heartbeat silence threshold and slow-test threshold.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.TestCompletion.csNotifies the progress renderer on each test completion to reset silence timing.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.csSelects cursor vs heartbeat renderer based on terminal capability and passes it into the progress-aware terminal wrapper.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/SilenceDrivenHeartbeatRenderer.csImplements silence heartbeat + slow-test durable line emission logic.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/IProgressRenderer.csDefines the rendering strategy interface and thread-safety expectations.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/CursorProgressRenderer.csExtracts the existing in-place redraw behavior behind IProgressRenderer.
eng/pipelines/steps/test-non-windows.ymlRemoves --no-progress/--output detailed from CI dotnet test invocations.
azure-pipelines.ymlRemoves --no-progress/--output detailed from Windows CI dotnet test invocations.

Copilot's findings

  • Files reviewed: 26/26 changed files
  • Comments generated: 8

Comment threadeng/pipelines/steps/test-non-windows.yml Outdated
Comment threadeng/pipelines/steps/test-non-windows.yml Outdated
Comment threadazure-pipelines.yml Outdated
Comment threadazure-pipelines.yml Outdated
Debug Test step routes through Microsoft.Testing.Platform.MSBuild's InvokeTestingPlatform
task, whose default TestingPlatformCaptureOutput=true buffers each test exe's stdout
into a per-module .log file and never logs it to the AzDO console (except on failure).
That hid the silence-driven heartbeat introduced in #9147: the renderer was firing but
the output was invisible to anyone watching the live pipeline.
Add -p:TestingPlatformCaptureOutput=false to the Debug 'dotnet test' invocations in
azure-pipelines.yml and eng/pipelines/steps/test-non-windows.yml so stdout is forwarded
to Log.LogMessage(MessageImportance.High, ...). The Release step uses --test-modules,
which bypasses MSBuild entirely, so it already streams per-module output and needs no
change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Publish _clock via Volatile.Write/Read to avoid reading a stale null across threads.
- Report a slow test's actual elapsed time instead of the scheduled threshold, so a delayed tick does not under-report the runtime.
- Let each IProgressRenderer choose its tick cadence; the silence-driven heartbeat now ticks once per second instead of every 500ms.
- Add regression test for the delayed-tick elapsed-time reporting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 15, 2026 15:21

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 26/26 changed files
  • Comments generated: 2

…eat-9139
# Conflicts:
#	azure-pipelines.yml
#	eng/pipelines/steps/test-non-windows.yml
@Evangelink
Amaury Levé (Evangelink) merged commit c738388 into mainJun 16, 2026
32 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/heartbeat-9139 branch June 16, 2026 09:48
Amaury Levé (Evangelink) added a commit that referenced this pull request Jun 17, 2026
…#9176`, `#9177` (#9207)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Silence-driven progress heartbeat renderer for SimpleAnsi/NoAnsi terminal modes

4 participants

@Evangelink@Youssef1313@JanKrivanek
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add silence-driven progress heartbeat for SimpleAnsi/NoAnsi modes (#9139) - #9147

Merged
Amaury Levé (Evangelink) merged 7 commits into
mainfrom
dev/amauryleve/heartbeat-9139
Jun 16, 2026
Merged

Add silence-driven progress heartbeat for SimpleAnsi/NoAnsi modes (#9139)#9147
Amaury Levé (Evangelink) merged 7 commits into
mainfrom
dev/amauryleve/heartbeat-9139

Conversation

@Evangelink

@EvangelinkAmaury Levé (Evangelink) commented Jun 15, 2026

Copy link
Copy Markdown
Member

Implements the silence-driven progress heartbeat renderer for the SimpleAnsi and NoAnsi terminal modes. Fixes#9139.

Stacked on #9145 (the --progress flag rename, #9138). Base is dev/amauryleve/progress-option-9138; review the heartbeat diff here and merge after #9145.

Problem

Today TerminalOutputDevice force-disables progress for SimpleAnsi/NoAnsi, so CI / piped / file-redirected runs get zero progress signal between the banner and the summary. The old fix (a per-DLL summary every 3s) was rejected as spam (#6753). This design is silence-driven, not time-driven.

What changed

Renderer abstraction (OutputDevice/Terminal/)

  • IProgressRenderer — strategy for per-tick render, write-wrapping, and completion notification.
  • CursorProgressRenderer — the existing in-place redraw, extracted verbatim (no behavior change for ANSI cursor modes).
  • SilenceDrivenHeartbeatRenderernew, emits single durable lines only when needed:
    • E1 silence heartbeatrunning... N completed, M failed | active: X after N seconds of no completion; repeats once per interval during prolonged silence. A healthy fast suite emits nothing.
    • E2 slow-test[slow] still running after …: <test> (<asm>|<tfm>|<arch>) per running test over the threshold, with exponential backoff (60s → 2m → 4m …). Durable scrollback line.
    • E3 failures inline — verified: failures still print at the moment of failure.

Gating (TerminalOutputDevice.cs)

  • Collapse noProgress || ansiMode is NoAnsi or SimpleAnsi to just noProgress; renderer is chosen by resolved terminal capability. Test-host controller / --list-tests / server mode still suppress.

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)

Localization — 3 new strings in PlatformResources.resx with all 13 .xlf files regenerated via /t:UpdateXlf.

Before / After

The feature currently doesn't work with dotnet test and requires a change there but when running the exe itself (or via dotnet or dotnet run) we can see the change.

Before: no info during the 20s of the run.

image

After (the screenshot belows sets progress silence as 2s and slow tests as 2s too):

image

For a more real use case, on this pipeline with progress disabled and default output level, we have a gap of ~16.5min dead-air window. With this feature (assuming dotnet test handling is done), we would have about:

  • 40 lines of silence heartbeat - with default 30s threshold
  • 15 lines of slow tests

Tests

  • 8 new deterministic unit tests in SilenceDrivenHeartbeatRendererTests.cs (silence fire / no-fire, repeat-per-interval, completion reset, disabled, slow-test backoff, below-threshold, disabled, write-through).
  • Updated 2 existing tests for the new TestProgressStateAwareTerminal ctor signature.
  • Core platform builds clean (0 warnings); Microsoft.Testing.Platform.UnitTests green. No new CLI flag, so --help/--info acceptance expectations are unaffected.

Out of scope (deliberate)

…nal reporter
Fixes#9138
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements the silence-driven heartbeat renderer (#9139) so CI / piped /
file-redirected runs get a progress signal between banner and summary,
without the rejected fixed-cadence per-DLL summary (#6753).
- Extract IProgressRenderer strategy; keep existing in-place redraw as
CursorProgressRenderer (ANSI cursor modes, unchanged behavior).
- Add SilenceDrivenHeartbeatRenderer for SimpleAnsi/NoAnsi:
- E1 silence heartbeat (one line after N seconds of no completion),
- E2 slow-test surfacing with exponential backoff,
- E3 failures continue to print inline.
- Collapse the SimpleAnsi/NoAnsi progress gating in TerminalOutputDevice.
- Knobs (env vars only): MTP_PROGRESS_SILENCE_SECONDS (default 30),
MTP_PROGRESS_SLOW_TEST_SECONDS (default 60); 0 disables.
- Localized resources + regenerated xlf; unit tests for all rules.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Evangelink

This comment has been minimized.

@Youssef1313

Copy link
Copy Markdown
Member

Amaury Levé (@Evangelink) Can you please add some before/after screenshots showing the behavior difference?

…etailed
The CI test steps passed --no-progress (and --output detailed), which force-disabled progress so the new SilenceDrivenHeartbeatRenderer added in this PR was never exercised on CI (SimpleAnsi mode). Dropping these flags makes the testfx CI fall into the heartbeat path and dogfood the feature.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Base automatically changed from dev/amauryleve/progress-option-9138 to mainJune 15, 2026 13:43
@Evangelink

Copy link
Copy Markdown
MemberAuthor

🧪 Test quality grade — PR #9147

10 tests graded (8 new, 2 modified) across 2 files. All tests score A. The new SilenceDrivenHeartbeatRendererTests suite is a model of deterministic unit-test design: FakeClock/FakeStopwatch eliminate all wall-clock coupling, assertions cover both positive and negative outcomes, and method names precisely describe scenario and expected behavior. The two modified tests in TerminalTestReporterTests needed only a constructor-signature update and retain their original quality. No actionable issues found.

ΔTestGradeBandNotes
newSilenceDrivenHeartbeatRendererTests.
OnWrite_
DoesNotEraseOrRenderProgress_
JustWrites
A90–100Three assertions verify pass-through semantics: content correct, erase not called, render not called.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
DuringProlongedSilence_
RepeatsOncePerInterval
A90–100Verifies repeat-per-interval behavior with three equality assertions at precise clock positions.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenCompletionsKeepHappening_
DoesNotEmit
A90–100Clean negative test: verifies silence is maintained when completions reset the timer.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenNoCompletionForThreshold_
EmitsSingleSummaryLine
A90–100Comprehensive boundary test: verifies no output below threshold and correct multi-field content above it.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenThresholdIsZero_
NeverEmits
A90–100No issues found.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenBelowThreshold_
EmitsNothing
A90–100No issues found.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenExceedingThreshold_
EmitsWithExponentialBackoff
A90–100Multi-phase verification of exponential backoff with content, count, and duration assertions.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenThresholdIsZero_
NeverEmits
A90–100DoesNotContain("[slow]") precisely scopes the assertion to slow-test output, correctly allowing heartbeat lines.
modTerminalTestReporterTests.
TestProgressStateAwareTerminal_
CanStopProgressAcrossMultipleSessions
A90–100No issues found.
modTerminalTestReporterTests.
TestProgressStateAwareTerminal_
WriteToTerminal_
ShouldEraseProgressThenRenderProgress
A90–100Event-ordering assertions with descriptive messages confirm the correct progress lifecycle sequence.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Re-run with
/grade-tests.

🤖 Automated content by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account — the account owner did not write or approve this content personally. Generated by the Grade Tests on PR (on open / sync) workflow. · 612.3 AIC · ⌖ 25.4 AIC · [◷]( · )

…eat-9139
# Conflicts:
#	test/IntegrationTests/MSTest.Acceptance.IntegrationTests/ShowOutputOptionTests.cs
CopilotAI review requested due to automatic review settings June 15, 2026 14:22

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends Microsoft.Testing.Platform’s terminal progress reporting to work in SimpleAnsi/NoAnsi scenarios (CI, redirected output) by introducing a renderer strategy abstraction and adding a new silence-driven “heartbeat” renderer that emits durable progress lines only when needed. It also wires in environment-variable knobs for heartbeat thresholds, adds localized resource strings, and updates unit tests and CI scripts accordingly.

Changes:

  • Introduces IProgressRenderer with CursorProgressRenderer (existing in-place redraw) and SilenceDrivenHeartbeatRenderer (new durable-line heartbeat/slow-test output).
  • Enables progress in non-cursor terminal modes by selecting an appropriate renderer rather than force-disabling progress.
  • Adds new localized resource strings + unit tests, and updates CI pipeline invocations.
Show a summary per file
FileDescription
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.csUpdates tests to pass the new renderer dependency into TestProgressStateAwareTerminal.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/SilenceDrivenHeartbeatRendererTests.csAdds deterministic unit tests covering silence heartbeat, slow-test backoff, and write-through behavior.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hant.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hans.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.tr.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ru.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pt-BR.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pl.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ko.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ja.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.it.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.fr.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.es.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.de.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.cs.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/PlatformResources.resxAdds 3 new localized strings used by the heartbeat renderer.
src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.csRemoves forced progress-disable for SimpleAnsi/NoAnsi and adds env-var threshold parsing into options.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.csRoutes tick/write behavior through IProgressRenderer and adds NotifyTestCompleted() hook.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporterOptions.csAdds options for heartbeat silence threshold and slow-test threshold.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.TestCompletion.csNotifies the progress renderer on each test completion to reset silence timing.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.csSelects cursor vs heartbeat renderer based on terminal capability and passes it into the progress-aware terminal wrapper.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/SilenceDrivenHeartbeatRenderer.csImplements silence heartbeat + slow-test durable line emission logic.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/IProgressRenderer.csDefines the rendering strategy interface and thread-safety expectations.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/CursorProgressRenderer.csExtracts the existing in-place redraw behavior behind IProgressRenderer.
eng/pipelines/steps/test-non-windows.ymlRemoves --no-progress/--output detailed from CI dotnet test invocations.
azure-pipelines.ymlRemoves --no-progress/--output detailed from Windows CI dotnet test invocations.

Copilot's findings

  • Files reviewed: 26/26 changed files
  • Comments generated: 8

Comment threadeng/pipelines/steps/test-non-windows.yml Outdated
Comment threadeng/pipelines/steps/test-non-windows.yml Outdated
Comment threadazure-pipelines.yml Outdated
Comment threadazure-pipelines.yml Outdated
Debug Test step routes through Microsoft.Testing.Platform.MSBuild's InvokeTestingPlatform
task, whose default TestingPlatformCaptureOutput=true buffers each test exe's stdout
into a per-module .log file and never logs it to the AzDO console (except on failure).
That hid the silence-driven heartbeat introduced in #9147: the renderer was firing but
the output was invisible to anyone watching the live pipeline.
Add -p:TestingPlatformCaptureOutput=false to the Debug 'dotnet test' invocations in
azure-pipelines.yml and eng/pipelines/steps/test-non-windows.yml so stdout is forwarded
to Log.LogMessage(MessageImportance.High, ...). The Release step uses --test-modules,
which bypasses MSBuild entirely, so it already streams per-module output and needs no
change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Publish _clock via Volatile.Write/Read to avoid reading a stale null across threads.
- Report a slow test's actual elapsed time instead of the scheduled threshold, so a delayed tick does not under-report the runtime.
- Let each IProgressRenderer choose its tick cadence; the silence-driven heartbeat now ticks once per second instead of every 500ms.
- Add regression test for the delayed-tick elapsed-time reporting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 15, 2026 15:21

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 26/26 changed files
  • Comments generated: 2

…eat-9139
# Conflicts:
#	azure-pipelines.yml
#	eng/pipelines/steps/test-non-windows.yml
@Evangelink
Amaury Levé (Evangelink) merged commit c738388 into mainJun 16, 2026
32 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/heartbeat-9139 branch June 16, 2026 09:48
Amaury Levé (Evangelink) added a commit that referenced this pull request Jun 17, 2026
…#9176`, `#9177` (#9207)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Silence-driven progress heartbeat renderer for SimpleAnsi/NoAnsi terminal modes

4 participants

@Evangelink@Youssef1313@JanKrivanek
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add silence-driven progress heartbeat for SimpleAnsi/NoAnsi modes (#9139) - #9147

Merged
Amaury Levé (Evangelink) merged 7 commits into
mainfrom
dev/amauryleve/heartbeat-9139
Jun 16, 2026
Merged

Add silence-driven progress heartbeat for SimpleAnsi/NoAnsi modes (#9139)#9147
Amaury Levé (Evangelink) merged 7 commits into
mainfrom
dev/amauryleve/heartbeat-9139

Conversation

@Evangelink

@EvangelinkAmaury Levé (Evangelink) commented Jun 15, 2026

Copy link
Copy Markdown
Member

Implements the silence-driven progress heartbeat renderer for the SimpleAnsi and NoAnsi terminal modes. Fixes#9139.

Stacked on #9145 (the --progress flag rename, #9138). Base is dev/amauryleve/progress-option-9138; review the heartbeat diff here and merge after #9145.

Problem

Today TerminalOutputDevice force-disables progress for SimpleAnsi/NoAnsi, so CI / piped / file-redirected runs get zero progress signal between the banner and the summary. The old fix (a per-DLL summary every 3s) was rejected as spam (#6753). This design is silence-driven, not time-driven.

What changed

Renderer abstraction (OutputDevice/Terminal/)

  • IProgressRenderer — strategy for per-tick render, write-wrapping, and completion notification.
  • CursorProgressRenderer — the existing in-place redraw, extracted verbatim (no behavior change for ANSI cursor modes).
  • SilenceDrivenHeartbeatRenderernew, emits single durable lines only when needed:
    • E1 silence heartbeatrunning... N completed, M failed | active: X after N seconds of no completion; repeats once per interval during prolonged silence. A healthy fast suite emits nothing.
    • E2 slow-test[slow] still running after …: <test> (<asm>|<tfm>|<arch>) per running test over the threshold, with exponential backoff (60s → 2m → 4m …). Durable scrollback line.
    • E3 failures inline — verified: failures still print at the moment of failure.

Gating (TerminalOutputDevice.cs)

  • Collapse noProgress || ansiMode is NoAnsi or SimpleAnsi to just noProgress; renderer is chosen by resolved terminal capability. Test-host controller / --list-tests / server mode still suppress.

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)

Localization — 3 new strings in PlatformResources.resx with all 13 .xlf files regenerated via /t:UpdateXlf.

Before / After

The feature currently doesn't work with dotnet test and requires a change there but when running the exe itself (or via dotnet or dotnet run) we can see the change.

Before: no info during the 20s of the run.

image

After (the screenshot belows sets progress silence as 2s and slow tests as 2s too):

image

For a more real use case, on this pipeline with progress disabled and default output level, we have a gap of ~16.5min dead-air window. With this feature (assuming dotnet test handling is done), we would have about:

  • 40 lines of silence heartbeat - with default 30s threshold
  • 15 lines of slow tests

Tests

  • 8 new deterministic unit tests in SilenceDrivenHeartbeatRendererTests.cs (silence fire / no-fire, repeat-per-interval, completion reset, disabled, slow-test backoff, below-threshold, disabled, write-through).
  • Updated 2 existing tests for the new TestProgressStateAwareTerminal ctor signature.
  • Core platform builds clean (0 warnings); Microsoft.Testing.Platform.UnitTests green. No new CLI flag, so --help/--info acceptance expectations are unaffected.

Out of scope (deliberate)

…nal reporter
Fixes#9138
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements the silence-driven heartbeat renderer (#9139) so CI / piped /
file-redirected runs get a progress signal between banner and summary,
without the rejected fixed-cadence per-DLL summary (#6753).
- Extract IProgressRenderer strategy; keep existing in-place redraw as
CursorProgressRenderer (ANSI cursor modes, unchanged behavior).
- Add SilenceDrivenHeartbeatRenderer for SimpleAnsi/NoAnsi:
- E1 silence heartbeat (one line after N seconds of no completion),
- E2 slow-test surfacing with exponential backoff,
- E3 failures continue to print inline.
- Collapse the SimpleAnsi/NoAnsi progress gating in TerminalOutputDevice.
- Knobs (env vars only): MTP_PROGRESS_SILENCE_SECONDS (default 30),
MTP_PROGRESS_SLOW_TEST_SECONDS (default 60); 0 disables.
- Localized resources + regenerated xlf; unit tests for all rules.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Evangelink

This comment has been minimized.

@Youssef1313

Copy link
Copy Markdown
Member

Amaury Levé (@Evangelink) Can you please add some before/after screenshots showing the behavior difference?

…etailed
The CI test steps passed --no-progress (and --output detailed), which force-disabled progress so the new SilenceDrivenHeartbeatRenderer added in this PR was never exercised on CI (SimpleAnsi mode). Dropping these flags makes the testfx CI fall into the heartbeat path and dogfood the feature.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Base automatically changed from dev/amauryleve/progress-option-9138 to mainJune 15, 2026 13:43
@Evangelink

Copy link
Copy Markdown
MemberAuthor

🧪 Test quality grade — PR #9147

10 tests graded (8 new, 2 modified) across 2 files. All tests score A. The new SilenceDrivenHeartbeatRendererTests suite is a model of deterministic unit-test design: FakeClock/FakeStopwatch eliminate all wall-clock coupling, assertions cover both positive and negative outcomes, and method names precisely describe scenario and expected behavior. The two modified tests in TerminalTestReporterTests needed only a constructor-signature update and retain their original quality. No actionable issues found.

ΔTestGradeBandNotes
newSilenceDrivenHeartbeatRendererTests.
OnWrite_
DoesNotEraseOrRenderProgress_
JustWrites
A90–100Three assertions verify pass-through semantics: content correct, erase not called, render not called.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
DuringProlongedSilence_
RepeatsOncePerInterval
A90–100Verifies repeat-per-interval behavior with three equality assertions at precise clock positions.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenCompletionsKeepHappening_
DoesNotEmit
A90–100Clean negative test: verifies silence is maintained when completions reset the timer.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenNoCompletionForThreshold_
EmitsSingleSummaryLine
A90–100Comprehensive boundary test: verifies no output below threshold and correct multi-field content above it.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenThresholdIsZero_
NeverEmits
A90–100No issues found.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenBelowThreshold_
EmitsNothing
A90–100No issues found.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenExceedingThreshold_
EmitsWithExponentialBackoff
A90–100Multi-phase verification of exponential backoff with content, count, and duration assertions.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenThresholdIsZero_
NeverEmits
A90–100DoesNotContain("[slow]") precisely scopes the assertion to slow-test output, correctly allowing heartbeat lines.
modTerminalTestReporterTests.
TestProgressStateAwareTerminal_
CanStopProgressAcrossMultipleSessions
A90–100No issues found.
modTerminalTestReporterTests.
TestProgressStateAwareTerminal_
WriteToTerminal_
ShouldEraseProgressThenRenderProgress
A90–100Event-ordering assertions with descriptive messages confirm the correct progress lifecycle sequence.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Re-run with
/grade-tests.

🤖 Automated content by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account — the account owner did not write or approve this content personally. Generated by the Grade Tests on PR (on open / sync) workflow. · 612.3 AIC · ⌖ 25.4 AIC · [◷]( · )

…eat-9139
# Conflicts:
#	test/IntegrationTests/MSTest.Acceptance.IntegrationTests/ShowOutputOptionTests.cs
CopilotAI review requested due to automatic review settings June 15, 2026 14:22

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends Microsoft.Testing.Platform’s terminal progress reporting to work in SimpleAnsi/NoAnsi scenarios (CI, redirected output) by introducing a renderer strategy abstraction and adding a new silence-driven “heartbeat” renderer that emits durable progress lines only when needed. It also wires in environment-variable knobs for heartbeat thresholds, adds localized resource strings, and updates unit tests and CI scripts accordingly.

Changes:

  • Introduces IProgressRenderer with CursorProgressRenderer (existing in-place redraw) and SilenceDrivenHeartbeatRenderer (new durable-line heartbeat/slow-test output).
  • Enables progress in non-cursor terminal modes by selecting an appropriate renderer rather than force-disabling progress.
  • Adds new localized resource strings + unit tests, and updates CI pipeline invocations.
Show a summary per file
FileDescription
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.csUpdates tests to pass the new renderer dependency into TestProgressStateAwareTerminal.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/SilenceDrivenHeartbeatRendererTests.csAdds deterministic unit tests covering silence heartbeat, slow-test backoff, and write-through behavior.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hant.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hans.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.tr.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ru.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pt-BR.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pl.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ko.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ja.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.it.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.fr.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.es.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.de.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.cs.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/PlatformResources.resxAdds 3 new localized strings used by the heartbeat renderer.
src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.csRemoves forced progress-disable for SimpleAnsi/NoAnsi and adds env-var threshold parsing into options.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.csRoutes tick/write behavior through IProgressRenderer and adds NotifyTestCompleted() hook.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporterOptions.csAdds options for heartbeat silence threshold and slow-test threshold.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.TestCompletion.csNotifies the progress renderer on each test completion to reset silence timing.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.csSelects cursor vs heartbeat renderer based on terminal capability and passes it into the progress-aware terminal wrapper.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/SilenceDrivenHeartbeatRenderer.csImplements silence heartbeat + slow-test durable line emission logic.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/IProgressRenderer.csDefines the rendering strategy interface and thread-safety expectations.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/CursorProgressRenderer.csExtracts the existing in-place redraw behavior behind IProgressRenderer.
eng/pipelines/steps/test-non-windows.ymlRemoves --no-progress/--output detailed from CI dotnet test invocations.
azure-pipelines.ymlRemoves --no-progress/--output detailed from Windows CI dotnet test invocations.

Copilot's findings

  • Files reviewed: 26/26 changed files
  • Comments generated: 8

Comment threadeng/pipelines/steps/test-non-windows.yml Outdated
Comment threadeng/pipelines/steps/test-non-windows.yml Outdated
Comment threadazure-pipelines.yml Outdated
Comment threadazure-pipelines.yml Outdated
Debug Test step routes through Microsoft.Testing.Platform.MSBuild's InvokeTestingPlatform
task, whose default TestingPlatformCaptureOutput=true buffers each test exe's stdout
into a per-module .log file and never logs it to the AzDO console (except on failure).
That hid the silence-driven heartbeat introduced in #9147: the renderer was firing but
the output was invisible to anyone watching the live pipeline.
Add -p:TestingPlatformCaptureOutput=false to the Debug 'dotnet test' invocations in
azure-pipelines.yml and eng/pipelines/steps/test-non-windows.yml so stdout is forwarded
to Log.LogMessage(MessageImportance.High, ...). The Release step uses --test-modules,
which bypasses MSBuild entirely, so it already streams per-module output and needs no
change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Publish _clock via Volatile.Write/Read to avoid reading a stale null across threads.
- Report a slow test's actual elapsed time instead of the scheduled threshold, so a delayed tick does not under-report the runtime.
- Let each IProgressRenderer choose its tick cadence; the silence-driven heartbeat now ticks once per second instead of every 500ms.
- Add regression test for the delayed-tick elapsed-time reporting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 15, 2026 15:21

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 26/26 changed files
  • Comments generated: 2

…eat-9139
# Conflicts:
#	azure-pipelines.yml
#	eng/pipelines/steps/test-non-windows.yml
@Evangelink
Amaury Levé (Evangelink) merged commit c738388 into mainJun 16, 2026
32 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/heartbeat-9139 branch June 16, 2026 09:48
Amaury Levé (Evangelink) added a commit that referenced this pull request Jun 17, 2026
…#9176`, `#9177` (#9207)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Silence-driven progress heartbeat renderer for SimpleAnsi/NoAnsi terminal modes

4 participants

@Evangelink@Youssef1313@JanKrivanek
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Add silence-driven progress heartbeat for SimpleAnsi/NoAnsi modes (#9139) - #9147

Merged
Amaury Levé (Evangelink) merged 7 commits into
mainfrom
dev/amauryleve/heartbeat-9139
Jun 16, 2026
Merged

Add silence-driven progress heartbeat for SimpleAnsi/NoAnsi modes (#9139)#9147
Amaury Levé (Evangelink) merged 7 commits into
mainfrom
dev/amauryleve/heartbeat-9139

Conversation

@Evangelink

@EvangelinkAmaury Levé (Evangelink) commented Jun 15, 2026

Copy link
Copy Markdown
Member

Implements the silence-driven progress heartbeat renderer for the SimpleAnsi and NoAnsi terminal modes. Fixes#9139.

Stacked on #9145 (the --progress flag rename, #9138). Base is dev/amauryleve/progress-option-9138; review the heartbeat diff here and merge after #9145.

Problem

Today TerminalOutputDevice force-disables progress for SimpleAnsi/NoAnsi, so CI / piped / file-redirected runs get zero progress signal between the banner and the summary. The old fix (a per-DLL summary every 3s) was rejected as spam (#6753). This design is silence-driven, not time-driven.

What changed

Renderer abstraction (OutputDevice/Terminal/)

  • IProgressRenderer — strategy for per-tick render, write-wrapping, and completion notification.
  • CursorProgressRenderer — the existing in-place redraw, extracted verbatim (no behavior change for ANSI cursor modes).
  • SilenceDrivenHeartbeatRenderernew, emits single durable lines only when needed:
    • E1 silence heartbeatrunning... N completed, M failed | active: X after N seconds of no completion; repeats once per interval during prolonged silence. A healthy fast suite emits nothing.
    • E2 slow-test[slow] still running after …: <test> (<asm>|<tfm>|<arch>) per running test over the threshold, with exponential backoff (60s → 2m → 4m …). Durable scrollback line.
    • E3 failures inline — verified: failures still print at the moment of failure.

Gating (TerminalOutputDevice.cs)

  • Collapse noProgress || ansiMode is NoAnsi or SimpleAnsi to just noProgress; renderer is chosen by resolved terminal capability. Test-host controller / --list-tests / server mode still suppress.

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)

Localization — 3 new strings in PlatformResources.resx with all 13 .xlf files regenerated via /t:UpdateXlf.

Before / After

The feature currently doesn't work with dotnet test and requires a change there but when running the exe itself (or via dotnet or dotnet run) we can see the change.

Before: no info during the 20s of the run.

image

After (the screenshot belows sets progress silence as 2s and slow tests as 2s too):

image

For a more real use case, on this pipeline with progress disabled and default output level, we have a gap of ~16.5min dead-air window. With this feature (assuming dotnet test handling is done), we would have about:

  • 40 lines of silence heartbeat - with default 30s threshold
  • 15 lines of slow tests

Tests

  • 8 new deterministic unit tests in SilenceDrivenHeartbeatRendererTests.cs (silence fire / no-fire, repeat-per-interval, completion reset, disabled, slow-test backoff, below-threshold, disabled, write-through).
  • Updated 2 existing tests for the new TestProgressStateAwareTerminal ctor signature.
  • Core platform builds clean (0 warnings); Microsoft.Testing.Platform.UnitTests green. No new CLI flag, so --help/--info acceptance expectations are unaffected.

Out of scope (deliberate)

…nal reporter
Fixes#9138
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements the silence-driven heartbeat renderer (#9139) so CI / piped /
file-redirected runs get a progress signal between banner and summary,
without the rejected fixed-cadence per-DLL summary (#6753).
- Extract IProgressRenderer strategy; keep existing in-place redraw as
CursorProgressRenderer (ANSI cursor modes, unchanged behavior).
- Add SilenceDrivenHeartbeatRenderer for SimpleAnsi/NoAnsi:
- E1 silence heartbeat (one line after N seconds of no completion),
- E2 slow-test surfacing with exponential backoff,
- E3 failures continue to print inline.
- Collapse the SimpleAnsi/NoAnsi progress gating in TerminalOutputDevice.
- Knobs (env vars only): MTP_PROGRESS_SILENCE_SECONDS (default 30),
MTP_PROGRESS_SLOW_TEST_SECONDS (default 60); 0 disables.
- Localized resources + regenerated xlf; unit tests for all rules.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Evangelink

This comment has been minimized.

@Youssef1313

Copy link
Copy Markdown
Member

Amaury Levé (@Evangelink) Can you please add some before/after screenshots showing the behavior difference?

…etailed
The CI test steps passed --no-progress (and --output detailed), which force-disabled progress so the new SilenceDrivenHeartbeatRenderer added in this PR was never exercised on CI (SimpleAnsi mode). Dropping these flags makes the testfx CI fall into the heartbeat path and dogfood the feature.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Base automatically changed from dev/amauryleve/progress-option-9138 to mainJune 15, 2026 13:43
@Evangelink

Copy link
Copy Markdown
MemberAuthor

🧪 Test quality grade — PR #9147

10 tests graded (8 new, 2 modified) across 2 files. All tests score A. The new SilenceDrivenHeartbeatRendererTests suite is a model of deterministic unit-test design: FakeClock/FakeStopwatch eliminate all wall-clock coupling, assertions cover both positive and negative outcomes, and method names precisely describe scenario and expected behavior. The two modified tests in TerminalTestReporterTests needed only a constructor-signature update and retain their original quality. No actionable issues found.

ΔTestGradeBandNotes
newSilenceDrivenHeartbeatRendererTests.
OnWrite_
DoesNotEraseOrRenderProgress_
JustWrites
A90–100Three assertions verify pass-through semantics: content correct, erase not called, render not called.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
DuringProlongedSilence_
RepeatsOncePerInterval
A90–100Verifies repeat-per-interval behavior with three equality assertions at precise clock positions.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenCompletionsKeepHappening_
DoesNotEmit
A90–100Clean negative test: verifies silence is maintained when completions reset the timer.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenNoCompletionForThreshold_
EmitsSingleSummaryLine
A90–100Comprehensive boundary test: verifies no output below threshold and correct multi-field content above it.
newSilenceDrivenHeartbeatRendererTests.
SilenceHeartbeat_
WhenThresholdIsZero_
NeverEmits
A90–100No issues found.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenBelowThreshold_
EmitsNothing
A90–100No issues found.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenExceedingThreshold_
EmitsWithExponentialBackoff
A90–100Multi-phase verification of exponential backoff with content, count, and duration assertions.
newSilenceDrivenHeartbeatRendererTests.
SlowTest_
WhenThresholdIsZero_
NeverEmits
A90–100DoesNotContain("[slow]") precisely scopes the assertion to slow-test output, correctly allowing heartbeat lines.
modTerminalTestReporterTests.
TestProgressStateAwareTerminal_
CanStopProgressAcrossMultipleSessions
A90–100No issues found.
modTerminalTestReporterTests.
TestProgressStateAwareTerminal_
WriteToTerminal_
ShouldEraseProgressThenRenderProgress
A90–100Event-ordering assertions with descriptive messages confirm the correct progress lifecycle sequence.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Re-run with
/grade-tests.

🤖 Automated content by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account — the account owner did not write or approve this content personally. Generated by the Grade Tests on PR (on open / sync) workflow. · 612.3 AIC · ⌖ 25.4 AIC · [◷]( · )

…eat-9139
# Conflicts:
#	test/IntegrationTests/MSTest.Acceptance.IntegrationTests/ShowOutputOptionTests.cs
CopilotAI review requested due to automatic review settings June 15, 2026 14:22

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends Microsoft.Testing.Platform’s terminal progress reporting to work in SimpleAnsi/NoAnsi scenarios (CI, redirected output) by introducing a renderer strategy abstraction and adding a new silence-driven “heartbeat” renderer that emits durable progress lines only when needed. It also wires in environment-variable knobs for heartbeat thresholds, adds localized resource strings, and updates unit tests and CI scripts accordingly.

Changes:

  • Introduces IProgressRenderer with CursorProgressRenderer (existing in-place redraw) and SilenceDrivenHeartbeatRenderer (new durable-line heartbeat/slow-test output).
  • Enables progress in non-cursor terminal modes by selecting an appropriate renderer rather than force-disabling progress.
  • Adds new localized resource strings + unit tests, and updates CI pipeline invocations.
Show a summary per file
FileDescription
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.csUpdates tests to pass the new renderer dependency into TestProgressStateAwareTerminal.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/SilenceDrivenHeartbeatRendererTests.csAdds deterministic unit tests covering silence heartbeat, slow-test backoff, and write-through behavior.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hant.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hans.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.tr.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ru.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pt-BR.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pl.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ko.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ja.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.it.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.fr.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.es.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.de.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.cs.xlfAdds new trans-units for heartbeat/slow-test strings (state=new).
src/Platform/Microsoft.Testing.Platform/Resources/PlatformResources.resxAdds 3 new localized strings used by the heartbeat renderer.
src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.csRemoves forced progress-disable for SimpleAnsi/NoAnsi and adds env-var threshold parsing into options.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.csRoutes tick/write behavior through IProgressRenderer and adds NotifyTestCompleted() hook.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporterOptions.csAdds options for heartbeat silence threshold and slow-test threshold.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.TestCompletion.csNotifies the progress renderer on each test completion to reset silence timing.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.csSelects cursor vs heartbeat renderer based on terminal capability and passes it into the progress-aware terminal wrapper.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/SilenceDrivenHeartbeatRenderer.csImplements silence heartbeat + slow-test durable line emission logic.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/IProgressRenderer.csDefines the rendering strategy interface and thread-safety expectations.
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/CursorProgressRenderer.csExtracts the existing in-place redraw behavior behind IProgressRenderer.
eng/pipelines/steps/test-non-windows.ymlRemoves --no-progress/--output detailed from CI dotnet test invocations.
azure-pipelines.ymlRemoves --no-progress/--output detailed from Windows CI dotnet test invocations.

Copilot's findings

  • Files reviewed: 26/26 changed files
  • Comments generated: 8

Comment threadeng/pipelines/steps/test-non-windows.yml Outdated
Comment threadeng/pipelines/steps/test-non-windows.yml Outdated
Comment threadazure-pipelines.yml Outdated
Comment threadazure-pipelines.yml Outdated
Debug Test step routes through Microsoft.Testing.Platform.MSBuild's InvokeTestingPlatform
task, whose default TestingPlatformCaptureOutput=true buffers each test exe's stdout
into a per-module .log file and never logs it to the AzDO console (except on failure).
That hid the silence-driven heartbeat introduced in #9147: the renderer was firing but
the output was invisible to anyone watching the live pipeline.
Add -p:TestingPlatformCaptureOutput=false to the Debug 'dotnet test' invocations in
azure-pipelines.yml and eng/pipelines/steps/test-non-windows.yml so stdout is forwarded
to Log.LogMessage(MessageImportance.High, ...). The Release step uses --test-modules,
which bypasses MSBuild entirely, so it already streams per-module output and needs no
change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Publish _clock via Volatile.Write/Read to avoid reading a stale null across threads.
- Report a slow test's actual elapsed time instead of the scheduled threshold, so a delayed tick does not under-report the runtime.
- Let each IProgressRenderer choose its tick cadence; the silence-driven heartbeat now ticks once per second instead of every 500ms.
- Add regression test for the delayed-tick elapsed-time reporting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 15, 2026 15:21

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 26/26 changed files
  • Comments generated: 2

…eat-9139
# Conflicts:
#	azure-pipelines.yml
#	eng/pipelines/steps/test-non-windows.yml
@Evangelink
Amaury Levé (Evangelink) merged commit c738388 into mainJun 16, 2026
32 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/heartbeat-9139 branch June 16, 2026 09:48
Amaury Levé (Evangelink) added a commit that referenced this pull request Jun 17, 2026
…#9176`, `#9177` (#9207)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Silence-driven progress heartbeat renderer for SimpleAnsi/NoAnsi terminal modes

4 participants

@Evangelink@Youssef1313@JanKrivanek