Migrate off xcresulttool --legacy before Apple removes it · Issue #391 · XCTestHTMLReport/XCTestHTMLReport · GitHub
Skip to content

Migrate off xcresulttool --legacy before Apple removes it #391

Closed

Description

@tylervick

This is the existential risk for this project.

XCResultKit drives xcrun xcresulttool get --format json --legacy. Apple has deprecated the legacy commands and will eventually remove them; when that happens, xchtmlreport stops working entirely.

We are two hops from a change we do not control: upstream davidahouse/XCResultKit last shipped v1.2.2 in Feb 2025, and its own tracking issue (davidahouse/XCResultKit#58, "Stop using --legacy") is open and unaddressed.

Task: decide the strategy and then execute it:

  • contribute the migration upstream to XCResultKit (best for the ecosystem, slowest, depends on upstream responsiveness), or
  • vendor/fork XCResultKit into this repo (full control, ongoing maintenance cost), or
  • read the new xcresulttool JSON format directly and drop the dependency

Whichever path, the fixture suite added in 3.0 is what makes the migration verifiable — the new format can be diffed against the old one on the same bundles.

Activity

  1. added this to the 3.0 milestone on Aug 7, 2026
  2. tylervick commented on Aug 11, 2026

    @tylervick
    MemberAuthor

    Research findings, recorded here so they outlive the branch

    A design spec and implementation plan were produced on tylervick/legacy-migration-391 (PR #426). The plan is not being merged — it is execution scaffolding that would go stale. The findings below are not, because they were measured rather than reasoned, against Xcode 26.2 (17C52), xcresulttool 24514, schema 0.1.0, legacy format 3.56.

    Recommended strategy: read the new format directly, drop XCResultKit

    Behind a reader abstraction that keeps the legacy path alive until Apple removes it. Not yet ratified — recorded as the researched recommendation.

    Both alternatives were rejected for concrete reasons:

    • Contribute upstream — XCResultKit's public API is the legacy object graph (ActionsInvocationRecord, ActionTestSummaryGroup are one-to-one with legacy JSON types). Migrating it means proposing a breaking API redesign to a maintainer who has not responded to the tracking issue in over a year, and blocking an existential risk on a third party.
    • Vendor/fork — solves "upstream is unresponsive" but not "the format is being removed"; the vendored code still shells out to --legacy. Inherits 3,549 lines across 78 files to use ~15 types, and the new-format reader still has to be written.

    Since all three options require writing a new-format reader, the direct read is strictly less work for the same outcome.

    The central finding: the new format is not a superset

    Legacy New format Verdict
    activity activityType (5 constants driving CSS classes) absent; only isAssociatedWithFailure lost
    activity start and finish startTime only lost — per-activity duration not derivable
    attachment name (user-supplied, e.g. "HTML") name holds the legacy filename lost — survives only in a sibling child-activity title
    attachment uniformTypeIdentifier absent derivable from exportedFileName extension
    ActionTestFailureSummary.fileName/.lineNumber/.issueType one string: "RetryTests.swift:31: XCTAssertTrue failed" lost as structure, text preserved
    log section emittedOutput structured messages lost
    exportRecursiveJson() (drives --json) none no replacement
    repetitions first-class nodeType: "Repetition" improved

    Byte-identical output across backends is therefore not achievable. The bar has to be a declared, reviewed diff asserted in CI — not an empty one. Anyone who plans this work expecting parity will be wrong.

    Tree shape changes

    LEGACY  Target → "All tests" → "Target.xctest" → Target → tests
                    → "SwiftTestingSuite" → tests        (sibling of "All tests")
    MODERN  Target → Target → tests
                    → SwiftTestingSuite → tests          (consistent siblings)
    

    Synthesizing the legacy wrappers was rejected: "Selected tests" vs "All tests" depends on run filtering, which the new format does not expose, so one label would always be fabricated. Swift Testing display names also differ — legacy taggedMultiplication() vs modern Tagged multiplication check.

    Attachments get materially better

    xcresulttool export attachments --path B --output-path D exports every attachment in one call (0.05s on TestResults), writing <uuid>.<ext> plus a manifest.json. The join is activities[].attachments[].uuid ↔ basename of manifest[].attachments[].exportedFileName, verified concretely (4DB9AD3F-… ↔ 4DB9AD3F-….mp4).

    This replaces the legacy one-subprocess-per-payload export — and with it the per-payload lock table in ResultFile that exists only because XCResultKit exports every caller of an id to one shared temp path.

    Performance: no claim in either direction

    Same-runner interleaved A/B, 5 reps each: legacy 27 subprocess spawns / 0.74s median; modern 20 spawns / 0.76s median. Equal within noise. Measured only to rule out the modern path being dramatically worse because activities is per-test-id. Do not cite as a speedup, and do not compare against numbers from another runner.


    Full spec and task-by-task plan remain on tylervick/legacy-migration-391 / PR #426 if the work is picked up.

  3. modified the milestones: 3.0, 4.0 on Aug 11, 2026
  4. tylervick commented on Aug 14, 2026

    @tylervick
    MemberAuthor

    Closing as complete. Everything in this issue's scope is merged and proven on main:

    Fixed along the way: #449 (screen-recording export race, root-caused to Xcode's shared display names; exports are now content-addressed <payloadId>.<ext>). Breaking changes are documented in docs/release-notes/4.0.0.md.

    Deliberately not part of this issue (per the design spec's phase 6): removing XCResultKit and LegacyResultReader waits until Apple actually removes --legacy — the differential keeps running for as long as both backends exist. Tracked in the follow-up issue filed alongside this close.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions