Backend selection and the --result-reader flag (#391, Task 11) by tylervick · Pull Request #448 · XCTestHTMLReport/XCTestHTMLReport · GitHub
Skip to content

Backend selection and the --result-reader flag (#391, Task 11) - #448

Merged
tylervick merged 2 commits into
mainfrom
tylervick/result-backend-391
Aug 13, 2026
Merged

tylervick merged 2 commits into
mainfrom
tylervick/result-backend-391

Conversation

@tylervick

@tylervick tylervick commented Aug 13, 2026 •

Copy link
Copy Markdown
Member

Part of #391 (milestone 4.0) — plan Task 11.

What

  • ResultBackend (auto|legacy|modern) with resolve(), which checks the requested backend against XCResultToolClient.legacyCapability (XCResultToolClient and new-format schema structs (#391) #441's probe):
    • auto prefers legacy while the toolchain advertises it, falls back to modern otherwise (including an unparseable version string). Never resolves to auto.
    • Explicit legacy is never silently substituted. On a toolchain that definitively lacks the legacy commands it resolves to .legacyUnavailable: the CLI rejects it in validate() with a clear error, and Summary.init (which cannot throw) records a new legacyReaderUnavailable fault that reaches exit 3 — otherwise the Task 12 differential could run modern-vs-modern and report parity against itself.
    • unknown capability treats legacy as available for explicit legacy (not proof of absence); any resulting command failure surfaces as a fault, not a capability signal.
  • Demotion is scoped to capability detection only. Corrupt bundles, permission errors, and dead subprocesses still propagate through FaultCollector to exit 3 exactly as before; there is no retry-on-other-backend anywhere.
  • Summary.init(..., backend: ResultBackend = .auto) appended after faultCollector, wiring per-bundle reader/payload-provider construction through the resolution (LegacyResultReader+ResultFile vs ModernResultReader+ModernPayloadStore).
  • --result-reader legacy|modern|auto (default auto) on the CLI, drivable out of process through the existing xchtmlreportCmd helper — Task 12's differential forces each reader through the CLI.

Tests

ResultBackendTests per the plan (resolution table, both-backends-render-with-zero-faults through the real Summary path), plus testResultReaderFlagWorksOutOfProcess: runs the built binary with --result-reader modern (and legacy where available) and asserts the rendered group counts differ, proving the flag reaches Summary from a separate process.

  • swift test: 71 tests, 0 failures (2 pre-existing skips)
  • Plan step-5 end-to-end check: legacy exit=0 groups=14, modern exit=0 groups=10 — counts differ as required

Plan/spec amendments (in this PR)

  • Plan Task 11 step 5: added mkdir -p — xchtmlreport does not create the output directory, so the snippet as written exited 1 for both readers.
  • Plan Task 11 step 4: expected test count corrected (said 4; snippet defines 5, now 6 with the CLI test).
  • Plan Task 11 step 1: added the out-of-process CLI test to the canonical test file.
  • Spec "Backend selection": the paragraph still said an explicit legacy falls back to modern; rewritten to the never-silently-substituted semantics the plan's tests encode.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added options to choose automatic, legacy, or modern result reading.
    • Automatic selection uses the legacy reader when available and otherwise uses the modern reader.
    • Reports can now be generated using either supported reading mode.
  • Bug Fixes

    • Explicit legacy selection now clearly handles unavailable readers with an appropriate error or fallback.
  • Tests

    • Added coverage for reader selection, fallback behavior, faults, and report output consistency.

ResultBackend resolves a requested backend against the toolchain's
legacy capability: auto prefers legacy while the toolchain advertises
it, and an explicit legacy request is never silently substituted — the
CLI rejects it up front and Summary records a legacyReaderUnavailable
fault for library consumers. Demotion stays scoped to capability
detection; per-bundle failures propagate through FaultCollector exactly
as before.

Amends plan Task 11 (mkdir -p in the step-5 check, test count, added
out-of-process CLI test for Task 12's differential) and the spec's
Backend selection section, which still said explicit legacy falls back.

Refs #391

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tylervick tylervick added this to the 4.0 milestone Aug 13, 2026
@coderabbitai

coderabbitai Bot commented Aug 13, 2026 •

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Tests/XCTestHTMLReportTests/ResultBackendTests.swift`:
- Around line 42-61: Update testBothBackendsProduceAReportForTheSameBundle so
the modern backend is always tested, while ResultBackend.legacy is included only
when XCResultToolClient.legacyCapability is .available; do not substitute modern
for an unavailable legacy backend. Rename the test to reflect that both readers
are not guaranteed to run on every host.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 67db38f3-0dd2-4d9e-ba72-a634c6a55f26

📥 Commits

Reviewing files that changed from the base of the PR and between 3441ff3 and 3c48261.

📒 Files selected for processing (7)
  • Sources/XCTestHTMLReport/XCTestHtmlReport.swift
  • Sources/XCTestHTMLReportCore/Classes/Helpers/FaultCollector.swift
  • Sources/XCTestHTMLReportCore/Classes/Models/Summary.swift
  • Sources/XCTestHTMLReportCore/Classes/ResultReading/ResultBackend.swift
  • Tests/XCTestHTMLReportTests/ResultBackendTests.swift
  • docs/superpowers/plans/2026-08-10-xcresulttool-legacy-migration.md
  • docs/superpowers/specs/2026-08-10-xcresulttool-legacy-migration-design.md

Comment thread Tests/XCTestHTMLReportTests/ResultBackendTests.swift
The both-backends parity test looped over legacy unconditionally, which
fails by design on a modern-only host: the explicit legacy arm records
legacyReaderUnavailable. Same anti-time-bomb reasoning the resolution
tests already use; parity stays fully asserted on every host that has
both backends. Plan snippet amended to match.

Refs #391

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Tests/XCTestHTMLReportTests/ResultBackendTests.swift (1)

112-119: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that the legacy report contains groups.

groupCount(reader: "legacy") returns 0 when the HTML has no test-summary-group. Since modernGroups is only required to be greater than zero, XCTAssertNotEqual can pass for a missing legacy report.

Store the legacy count and assert that it is greater than zero before comparing it with modernGroups.

Proposed fix
         if XCResultToolClient.legacyCapability == .available {
             // Legacy interposes the wrapper levels the modern tree omits, so
             // equal counts mean the flag never reached `Summary` and both
             // runs used one backend.
+            let legacyGroups = try groupCount(reader: "legacy")
+            XCTAssertGreaterThan(legacyGroups, 0, "legacy rendered no test groups")
             XCTAssertNotEqual(
-                try groupCount(reader: "legacy"), modernGroups,
+                legacyGroups, modernGroups,
                 "legacy and modern rendered identical group counts — the flag is not reaching Summary"
             )
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/XCTestHTMLReportTests/ResultBackendTests.swift` around lines 112 - 119,
Update the legacy-versus-modern assertion in the
XCResultToolClient.legacyCapability block to store groupCount(reader: "legacy")
in a named value, assert that this legacy count is greater than zero, then
compare it against modernGroups with the existing inequality assertion.
🧹 Nitpick comments (1)
Tests/XCTestHTMLReportTests/ResultBackendTests.swift (1)

13-24: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Make capability-state tests deterministic.

XCResultToolClient.legacyCapability is a process-wide static value. Each test run executes only one switch branch. A host with .available does not test .unavailable, and a host with .unavailable does not test .available or .unknown.

Add an injectable capability parameter or a pure resolver helper. Table-test all three states for .legacy and .auto.

Based on learnings: capability detection is tri-state; only auto may fall back to modern, while explicit legacy uses legacy for .available/.unknown and returns .legacyUnavailable for .unavailable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/XCTestHTMLReportTests/ResultBackendTests.swift` around lines 13 - 24,
Make backend resolution independently testable by adding an injectable
capability parameter or pure resolver helper instead of relying on
XCResultToolClient.legacyCapability. Table-test .legacy and .auto against
.available, .unknown, and .unavailable, preserving that explicit legacy never
resolves to modern, while auto falls back to modern only when legacy is
unavailable.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@Tests/XCTestHTMLReportTests/ResultBackendTests.swift`:
- Around line 112-119: Update the legacy-versus-modern assertion in the
XCResultToolClient.legacyCapability block to store groupCount(reader: "legacy")
in a named value, assert that this legacy count is greater than zero, then
compare it against modernGroups with the existing inequality assertion.

---

Nitpick comments:
In `@Tests/XCTestHTMLReportTests/ResultBackendTests.swift`:
- Around line 13-24: Make backend resolution independently testable by adding an
injectable capability parameter or pure resolver helper instead of relying on
XCResultToolClient.legacyCapability. Table-test .legacy and .auto against
.available, .unknown, and .unavailable, preserving that explicit legacy never
resolves to modern, while auto falls back to modern only when legacy is
unavailable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 10e8bb32-3d6f-423a-9696-d5a9e39d408d

📥 Commits

Reviewing files that changed from the base of the PR and between 3c48261 and 836111f.

📒 Files selected for processing (2)
  • Tests/XCTestHTMLReportTests/ResultBackendTests.swift
  • docs/superpowers/plans/2026-08-10-xcresulttool-legacy-migration.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/superpowers/plans/2026-08-10-xcresulttool-legacy-migration.md

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