Skip to content

fix(test): bound process-output drains in test helpers - #117

Merged
ThatRendle merged 2 commits into
mainfrom
fix/terminal-tests-hang
Sep 11, 2026
Merged

ThatRendle merged 2 commits into
mainfrom
fix/terminal-tests-hang

Conversation

@ThatRendle

Copy link
Copy Markdown
Contributor

Resolves the tech-debt/terminal-tests-hang.md item.

Problem

Six test helpers (Terminal's InitFeedFixture and InitCommandTests; Core's ComposedCoreFeedFixture, CompositionRootTests, ToolPackTests and VersionRangeRestoreTests) ran pack-core.sh or dotnet build/pack/restore with redirected output. They waited for the process to exit, then awaited ReadToEndAsync with no timeout. A reusable MSBuild worker node inherits the pipe and outlives the script, so EOF never arrived until the node idled out (~15 min).

Fix

One shared helper, test/Shared/ProcessRunner.cs, compiled into both test projects as a linked file:

  1. It sets MSBUILDDISABLENODEREUSE=1 on every child process.
  2. It pumps output into a snapshot and bounds the post-exit drain with a 10 s grace. If a descendant still holds the pipe, it returns truncated output instead of blocking.

It deliberately does not use BeginOutputReadLine: WaitForExitAsync waits for EOF on streams read that way, which would reproduce the hang. ProcessRunnerTests proves measure 2 in isolation.

Verification

All runs started from zero MSBuild nodes, with node reuse on, using --filter InitCommandTests:

  • main (before): hung, aborted at the 2 min hang timeout
  • this branch: 3/3 in 7 s, no leftover node
  • this branch with measure 1 disabled: 3/3, with a leftover nodeReuse:true node, so measure 2 alone survived the hang condition

Two full dotnet test Everything.slnx runs without the workaround variable: every assembly passed, about 70 s each.

A production site, CoreProcessManager.BuildFileBasedProgramAsync, has the same shape. It did not reproduce (a single-project build appears not to start a worker node) and is left alone, as recorded in the note.

🤖 Generated with Claude Code

https://claude.ai/code/session_015CJu5CxqY5B6o3GfMVAfsu

ThatRendle and others added 2 commits September 11, 2026 15:57
Six test helpers ran pack-core.sh or dotnet build/pack/restore with
redirected output and then awaited ReadToEndAsync with no timeout. A
reusable MSBuild worker node inherits the pipe and outlives the script,
so EOF never arrived until the node idled out (~15 min). This hung the
three InitCommandTests, and Core's ComposedCoreFeedFixture has the
same shape.

All six now call one shared test/Shared/ProcessRunner.cs, linked into
Dmon.Core.Tests and Dmon.Terminal.Tests. It sets
MSBUILDDISABLENODEREUSE=1 on every child and bounds the post-exit drain
with a grace, returning truncated output rather than blocking when a
descendant still holds the pipe. Each measure is sufficient alone; both
were forced from zero build nodes with node reuse on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CJu5CxqY5B6o3GfMVAfsu
Record the fix, the forcing runs in both directions, and a correction:
OutputDataReceived-mode reads do not escape the hang, because
WaitForExitAsync waits for EOF on them. Note that production's
BuildFileBasedProgramAsync has the same shape but did not reproduce,
and that the suspected Core-hang mechanism is now closed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CJu5CxqY5B6o3GfMVAfsu
@ThatRendle
ThatRendle merged commit 9edd380 into main Sep 11, 2026
5 checks passed
@ThatRendle
ThatRendle deleted the fix/terminal-tests-hang branch September 11, 2026 15:30
Sign up for free to 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.

1 participant