Skip to content

Surface and re-print handshake failures in dotnet test#54501

Open
Evangelink wants to merge 2 commits into
mainfrom
dev/amauryleve/handshake-failure-summary
Open

Surface and re-print handshake failures in dotnet test#54501
Evangelink wants to merge 2 commits into
mainfrom
dev/amauryleve/handshake-failure-summary

Conversation

@Evangelink
Copy link
Copy Markdown
Member

Follow-up to #54437 addressing the review feedback from @Youssef1313 and the remaining concerns from #51608:

  • Are we burying the reason why test host controller failed?
  • The error should probably surface in a better way.
  • The error should be re-printed towards the end so the user doesn''t have to scroll up to find it.

Changes

  • Reword the misleading "race" comment in AssemblyRunCompleted. Controller-only handshakes are already routed to HandshakeFailure by TestApplicationHandler.OnTestProcessExited, so the defensive branch is unreachable in normal operation. The new comment reflects that — it stays as belt-and-suspenders for stray completions after _assemblies.Clear() or future regressions.
  • Track each handshake failure (assembly path, target framework, exit code, stdout, stderr) in a List<T> guarded by a lock so we can re-print details at end of run.
  • Fix summary-line precedence: anyAssemblyFailed now takes precedence over allTestsWereSkipped. Previously a pure handshake-failure run (zero tests registered) printed "Zero tests ran" as the headline, masking the failure; it now correctly prints "Failed!".
  • Append a "Handshake failures:" recap after the summary listing each failing assembly with its exit code and captured stdout/stderr, so users don''t have to scroll to find the actionable cause.
  • Tests: extracted CapturingConsole into a shared helper and added coverage for the recap, multiple-failure ordering, the no-failure path, and the existing defensive AssemblyRunCompleted branch.

Localization

The new HandshakeFailuresHeader resource string was added to CliCommandStrings.resx with a descriptive <comment>. The 13 xlf files were regenerated via /t:UpdateXlf /p:UpdateXlfOnBuild=true (do not hand-edit — entries are in state="new" for translators per the repo workflow).

Verification

.\.dotnet\dotnet exec artifacts\bin\redist\Debug\dotnet.Tests.dll -method "*HandshakeFailure*" -method "*AssemblyRunCompleted_WhenExecutionIdUnknown*"
=== TEST EXECUTION SUMMARY ===
   dotnet.Tests  Total: 5, Errors: 0, Failed: 0, Skipped: 0, Not Run: 0, Time: 0.323s

cc @Youssef1313

Follow-up to PR #54437 addressing review feedback (#54437 (review)) and
remaining concerns from #51608:

- Reword the misleading 'race' comment in AssemblyRunCompleted. Controller-
  only handshake is already routed to HandshakeFailure by
  TestApplicationHandler.OnTestProcessExited, so the defensive branch is
  unreachable in normal operation; the new comment reflects that.
- Track each HandshakeFailure (assembly, tfm, exit code, stdout, stderr) in
  a List<T> guarded by a lock so we can re-print the details at end of run.
- Fix summary-line precedence so 'Failed!' wins over 'Zero tests ran' when
  every assembly failed its handshake (previously the headline implied a
  benign empty run).
- Append a 'Handshake failures:' recap section after the summary listing
  each failing assembly with its exit code and captured output so users do
  not have to scroll to find the cause.
- Extract CapturingConsole into a shared test helper and add tests covering
  the recap, multiple-failure ordering, no-failure path, and the existing
  defensive AssemblyRunCompleted branch.
- Add HandshakeFailuresHeader resource string (xlf files auto-generated via
  /t:UpdateXlf, left in state='new' for translators).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 29, 2026 09:53
@Evangelink Evangelink requested a review from a team as a code owner May 29, 2026 09:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Follow-up to #54437 that makes test-host handshake failures more discoverable. The reporter now records every handshake failure, re-prints a "Handshake failures:" recap at the end of the run, and fixes the summary precedence so a handshake-only failure no longer prints "Zero tests ran" as the headline.

Changes:

  • Track handshake failures in a lock-guarded list and append a recap section after the test run summary.
  • Reorder summary branches so anyTestFailed || anyAssemblyFailed takes precedence over allTestsWereSkipped.
  • Add a new localized HandshakeFailuresHeader resource (resx + 13 xlf) and extract CapturingConsole into a shared test helper with new coverage.
Show a summary per file
File Description
src/Cli/dotnet/Commands/Test/MTP/Terminal/TerminalTestReporter.cs Records handshake failures, emits end-of-run recap, fixes summary precedence, reworded defensive comment.
src/Cli/dotnet/Commands/CliCommandStrings.resx Adds HandshakeFailuresHeader string with translator comment.
src/Cli/dotnet/Commands/xlf/CliCommandStrings.*.xlf (13 files) Regenerated xlf entries with state="new".
test/dotnet.Tests/CommandTests/Test/CapturingConsole.cs Extracted shared IConsole test double.
test/dotnet.Tests/CommandTests/Test/TerminalTestReporterTests.cs New tests covering recap, multiple failures, no-failure path.
test/dotnet.Tests/CommandTests/Test/TestApplicationHandlerTests.cs Removes the now-shared inner CapturingConsole.

Copilot's findings

  • Files reviewed: 18/18 changed files
  • Comments generated: 0

@Youssef1313 Youssef1313 requested a review from nohwnd May 29, 2026 09:58
@Youssef1313
Copy link
Copy Markdown
Member

I won't be able to review all the changes here, but worth getting some review from @nohwnd.

/// the actionable cause.
/// </summary>
[Fact]
public void TestExecutionCompleted_AfterHandshakeFailure_ReprintsFailureDetailsAtEndOfRun()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I prefer to see integration tests instead of unit tests.

The integration test could try to simulate the scenario by having a custom test host controller extension that crashes or whatever it was that gets us in the bad state. I would like to see the E2E behavior, preferably have a "before" and "after" views.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Seconding this.

Per @Youssef1313 and @nohwnd review feedback on PR #54501, replace the
recap-related unit tests in TerminalTestReporterTests with end-to-end
coverage in GivenDotnetTestRunsConsoleAppWithoutHandshake that exercises
the user-visible behavior through 'dotnet test'.

- Expand GivenDotnetTestRunsConsoleAppWithoutHandshake with a new test
  asserting stdout contains the 'Handshake failures:' recap header,
  the recap section identifies the failing assembly, and the summary
  headline reads 'Failed!' rather than 'Zero tests ran' (scoped to the
  summary line only, since the per-assembly progress can legitimately
  contain 'Zero tests ran').
- Remove the three recap-related unit tests
  (TestExecutionCompleted_AfterHandshakeFailure_ReprintsFailureDetailsAtEndOfRun,
  TestExecutionCompleted_WithMultipleHandshakeFailures_ListsAllOfThemAtEndOfRun,
  TestExecutionCompleted_WithNoHandshakeFailures_DoesNotEmitRecapHeader)
  now covered E2E.
- Keep AssemblyRunCompleted_WhenExecutionIdUnknown_DoesNotThrowAndReportsHandshakeFailure
  as a defensive unit test — it guards an unreachable back-stop branch
  that cannot be exercised end-to-end.
- Keep OnTestProcessExited_WhenOnlyControllerHandshakeReceived_RoutesToHandshakeFailureWithModuleContext
  in TestApplicationHandlerTests as a defensive routing test.

Guards the new text assertions with SdkTestContext.IsLocalized() per repo
convention.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Evangelink
Copy link
Copy Markdown
Member Author

Thanks @Youssef1313 and @nohwnd for the feedback! Addressed in 31f0c99.

Changes:

  • Removed the three recap-related unit tests in TerminalTestReporterTests (TestExecutionCompleted_AfterHandshakeFailure_ReprintsFailureDetailsAtEndOfRun, …_WithMultipleHandshakeFailures…, …_WithNoHandshakeFailures…).
  • Extended the existing integration test GivenDotnetTestRunsConsoleAppWithoutHandshake (Debug+Release) to assert on the new user-visible behavior end-to-end via dotnet test against ConsoleAppDoesNothing:
    • stdout contains the Handshake failures: recap header,
    • the recap section identifies the failing assembly (ConsoleAppDoesNothing),
    • the summary headline reads Failed! and not Zero tests ran (scoped to the summary line only, since the inline per-assembly progress can legitimately still show Zero tests ran because zero tests really did register).

Captured stdout from a green run on my box:

》Q:\…\ConsoleAppDoesNothing.dll (net11.0) Zero tests ran
》Exit code: 0
》
》Test run summary: Failed!
》  error: 1
》
》  total: 0
》  failed: 0
》  succeeded: 0
》  skipped: 0
》  duration: 163ms
》Test run completed with non-success exit code: 1 (see: https://aka.ms/testingplatform/exitcodes)
》
》Handshake failures:
》  Q:\…\ConsoleAppDoesNothing.dll (net11.0)
》Exit code: 0

Kept as defensive unit tests (cannot be exercised E2E):

  • TerminalTestReporterTests.AssemblyRunCompleted_WhenExecutionIdUnknown_DoesNotThrowAndReportsHandshakeFailure — guards an unreachable back-stop branch inside TerminalTestReporter.AssemblyRunCompleted that the normal handler routing never hits.
  • TestApplicationHandlerTests.OnTestProcessExited_WhenOnlyControllerHandshakeReceived_RoutesToHandshakeFailureWithModuleContext — pins the controller-only handshake routing decision from Handle assemblies gracefully #52308 (the exact scenario you called out). Avoids needing a bespoke "controller crashes mid-handshake" test asset, which would depend on MTP internals and be brittle to crash at exactly the right moment.

Let me know if you'd still like a dedicated controller-crash test asset; happy to add one if you think the routing-level unit test isn't enough.

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.

4 participants