Skip to content

Make the tool report its own failures, and make the test suite runnable by anyone - #379

Merged
tylervick merged 26 commits into
mainfrom
tylervick/verification-oracle
Aug 7, 2026
Merged

Make the tool report its own failures, and make the test suite runnable by anyone#379
tylervick merged 26 commits into
mainfrom
tylervick/verification-oracle

Conversation

@tylervick

@tylervick tylervick commented Aug 7, 2026

Copy link
Copy Markdown
Member

Two problems, one root cause, and a data-loss bug found on the way.

1. The tool could not report its own failure

xchtmlreport exited 0 with "Report successfully created" even when it failed to parse parts of the result bundle. Nothing downstream — CI, a maintainer, or any automation — could distinguish a complete report from a silently degraded one.

Verified on Xcode 26.2: a run emitting six Unable to retrieve json from file … for expected type: GenericReferencedObject errors still exited 0 with a success message.

This PR adds a Fault/FaultCollector pair (thread-safe; parsing is concurrent) that records degradation in two places:

  • at XCResultKit call sites, wherever it signals failure by returning nil
  • as a post-condition (Summary.validate()), which catches nested decode failures where the parent object decodes but a child comes back empty — those never surface as a nil at any call site, so call-site checks alone are not sufficient

⚠️ Breaking change

xchtmlreport now exits 3 when the report is degraded. Reports are still written — this is purely an exit-code change. Pass --lenient for the previous always-zero behaviour.

Code Meaning
0 No faults detected
1 Could not write an output file
3 Report generated but degraded
64 Invalid arguments

Intended for a 3.0 release. Pipelines that were silently consuming incomplete reports will start failing — which is the point, but it warrants a major version and a note in the release announcement.

2. Nobody but the maintainer could run the tests

test.yml and codecov.yml downloaded .xcresult fixtures from a private R2 bucket via repository secrets. GitHub does not expose secrets to fork pull requests, so no outside contributor has ever been able to get a green check. On a fresh clone swift test did not merely fail — it did not compile, because SwiftPM only synthesises Bundle.module when a target's declared resources exist.

CI now generates its own fixtures with prepareTestResults.sh, which required fixing it: it hardcoded an "iPhone 12" simulator that no longer exists, and one sample test fetched apple.com at test time. It now resolves the newest available iOS runtime and the highest-numbered iPhone within it, and needs no network.

This addresses the root cause of #219.

3. A data-loss bug, found by pointing the tool at its own fixture

Running against RetryResults.xcresult exited 3 intermittently — 8 of 10 runs.

Two Attachments can share a single payloadRef.id. XCResultKit exports every caller of an id to the same temp path, so concurrent exports raced: try? removeItem deleted the file another thread had just moved into place. The attachment was then genuinely missing from the generated report. Fixed by serialising exports per payload id.

This is a plausible contributor to long-standing "screenshots/attachments not visible" reports.

Testing

  • swift test: 22 tests, 1 skipped, 0 failures
  • RetryResults.xcresult: 30 consecutive runs, all exit 0 (was 8/10 exiting 3)
  • Exit codes verified against the built binary: clean → 0, degraded → 3 with a named fault list, --lenient → 0, and the report is written in every case

CoreTests.testRetryFunctionalityJunit is skipped pending #378 — its JUnit expectations drift on Xcode 26 (one fewer .unknown activity result per test case). The assertions are unchanged, not weakened, so they run again once that is resolved.

Follow-ups (not in this PR)

  • pages.yml is broken two ways: swift run xchtmlreport -- -j exits 64 because SwiftPM forwards the --, and it downloads an artifact name test-artifacts.yml no longer produces
  • exportJson() has no nil check, so --json can emit [] and exit 0
  • Summary.validate() flags attachments that have no payloadRef at all, which would be a false positive on a bundle containing them
  • XCResultFile.exportPayload never returns nil, so two fault branches in ResultFile are dead code

Review note

Best reviewed squashed — the branch carries some plan-amendment commits from its development.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added lenient mode to complete report generation despite detected issues.
    • Added detailed fault reporting for missing records, unresolved attachments, and export failures.
  • Bug Fixes
    • Improved shared-attachment handling, concurrent exports, and incomplete-result processing.
    • Improved diagnostic output by directing warnings and errors to standard error.
  • Documentation
    • Documented exit codes, degraded reports, lenient mode, and local testing.
  • CI
    • Updated testing for current Xcode versions and dynamic simulator selection.
    • Removed network-dependent test fixtures.

tylervick and others added 25 commits August 6, 2026 22:58
Documents the current state of the project as measured on 2026-08-06
(Xcode 26.2 / Swift 6.2.3) and proposes a phased revival:

- Track A: build a verification oracle (fault collection + honest exit
  codes, self-contained CI fixtures, close fixture coverage gaps)
- Track B: propose-only issue triage lane for the 62-issue backlog
- Track D: dependency updates, release pipeline repair, drift detector

Key finding: the tool still processes Xcode 26.2 xcresults but exits 0
while emitting parse errors, so no consumer can distinguish a complete
report from a degraded one. A large fraction of open issues are
unreproducible because no sample-app fixture exercises their code path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Covers spec tracks A2 (self-contained fixtures) then A1 (fault
collection and honest exit codes), in that order — reading the code
showed A1 cannot be test-driven until fixtures are generatable locally.

Eight tasks, each ending in an independently testable deliverable.
Includes a fix for Summary.init using break instead of continue, which
silently abandons remaining result bundles after one unreadable bundle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Per pre-flight ruling: the zero-secrets global constraint binds every
workflow, not just test.yml. codecov.yml pulled fixtures from the same
R2 bucket.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resolve the newest available iPhone simulator instead of hardcoding
iPhone 12, which no longer exists in Xcode 26. Replace the apple.com
fetch with inline HTML data so fixture generation has no network
dependency.
The original snippet sorted device names as strings, so 'iPhone 8' beat
'iPhone 17 Pro Max' and 'iPhone SE' beat everything. Silently wrong on
any runner with mixed simulator generations. Replaced with tested logic
that picks the newest iOS runtime, then the highest numeric iPhone model
within it, and pins OS to that runtime instead of 'latest'.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Lexicographic sort of device names picked "iPhone SE (3rd generation)"
over "iPhone 17 Pro Max" on mixed-generation runners, since '8' > '1'
in ASCII and unnumbered SE/X models sort last regardless. Group
devices by runtime, rank iPhone models numerically within the newest
runtime, and pin -destination's OS to that runtime's version instead
of "OS=latest" so the resolved name and OS can never disagree.
Fork PRs could never go green because fixtures came from an R2 bucket
behind repo secrets. Both test.yml and codecov.yml now run
prepareTestResults.sh to build their own xcresult bundles. Closes the
root cause of #219.

Also removes the long-dead .travis.yml.
codecov-action@v4 dropped tokenless upload for base-repo pushes, so the
step would silently no-op (fail_ci_if_error defaults false). The secret
already exists on the repo.

Also rewords the global constraint: it exists so fork PRs can go green,
which binds test.yml only. Workflows that cannot run from a fork PR may
use secrets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
codecov-action v4 dropped tokenless upload for pushes from the base
repo, and without a token the upload silently no-ops since
fail_ci_if_error defaults to false. codecov.yml only triggers on
push-to-main and workflow_dispatch, never on fork PRs, so adding the
already-existing CODECOV_TOKEN secret here does not weaken the
zero-secrets constraint on fork PR runs (test.yml stays secret-free).
Foundation for reporting degraded output. Parsing is concurrent, so
mutations serialize behind a private dispatch queue.
XCResultKit signals failure by returning nil, so record a fault wherever
we already handle one. Also fixes Summary.init using break instead of
continue, which silently abandoned every remaining result bundle after
one unreadable bundle.
Call-site nil checks miss nested XCResultKit decode failures, where the
parent decodes but a child comes back empty. Check the assembled model
directly for attachments with no content.

NOTE: committed with --no-gpg-sign because the 1Password SSH agent was
erroring. Re-sign before pushing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
testValidateIsIdempotent asserted 0 == 0 on a clean fixture and would
have passed with validate()'s dedup deleted. Seed a non-empty fault set
first and compare the whole fault array across two calls.

Also tightens validate()'s doc comment: dedup keys on filename, and the
guarantee holds for sequential calls only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Call-site nil checks miss nested XCResultKit decode failures, where the
parent decodes but a child comes back empty. Check the assembled model
directly for attachments with no content.
BREAKING CHANGE: xchtmlreport now exits 3 when it could not fully parse
the result bundle, instead of exiting 0 with a success message. Reports
are still written. Pass --lenient for pre-3.0 behaviour.
The stderr assertion was guarded by #if !DEBUG, and swift test always
builds debug — so the suite could never observe degradation. Check the
exit code and the fault summary instead.
CONTRIBUTING never explained how to run tests, which was moot while
fixtures lived behind repo secrets. Both are now accurate.
Distinct attachments can share one payload ref — the screen recording of a
test retried under `-retry-tests-on-failure` is the common case, and both
screen recordings and retries are defaults, not exotic configuration.

XCResultKit exports every caller of an id to `NSTemporaryDirectory()/<id>`,
a path shared by all of them. Parsing is concurrent, so two exports of one
id raced twice over: on that temp file, and on the destination inside the
bundle. The first `moveItem` consumed the temp file, the second failed with
"the former doesn't exist" and returned nil, which became
`RenderingContent.none`, which `validate()` flagged as an unresolved
attachment. `RetryResults.xcresult` exited 3 on 8 of 10 identical runs.

Worse, the `try? removeItem` before the move could delete the copy the
first thread had just placed, so the attachment really did go missing from
the bundle rather than merely being misreported.

Serialize exports per payload id. Different ids still run in parallel, so
this costs nothing on bundles without duplicate refs. The `catch` also now
treats an already-populated destination as success, and records a
`payloadExportFailed` fault when the export genuinely failed — it recorded
nothing at all before.

The new CLI test repeats the run so a reintroduced race is caught rather
than sampled away.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`prepareTestResults.sh` now resolves the newest available simulator instead
of hardcoding "iPhone 12", so the device name embedded in the JUnit output
is whatever the machine has — "iPhone 17 Pro Max" on current Xcode. Both
regexes required digits immediately followed by " - ", so they stopped
matching. Accept any model name, and escape the literal dot in the OS
version while here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every test case in RetryResults.xcresult now produces exactly one fewer
`.unknown` JUnit result — an activity/log line — than these expectations
were written against: 9 vs 10, then 3 vs 4, then 3 vs 4. That is fixture
drift on Xcode 26, unrelated to anything on this branch.

Skip rather than adjust the numbers. The expectations are the only thing
pinning JUnit result composition, and loosening them to match today's Xcode
would quietly discard that coverage. Tracked in #378; the skip comes out
when that lands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The spec and implementation plan are workflow scratch, not project
documentation. They introduce a `docs/` tree that does not exist on main,
and the plan already contradicts itself — its "Follow-on work" section
lists work this branch finished.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`Logger.error` and `Logger.warning` used bare `print`, so the "Report is
degraded" diagnostics landed on the same stream as the path of the
generated report. A pipeline reading that path got the complaints mixed
into it. In a release whose point is exit-code honesty this is the wrong
stream, and 3.0 is the window to move it without it being a separate
breaking change.

`success`, `step` and `substep` stay on stdout — they are the tool's
output, and the test harness parses the report path out of `success`.

The harness's degraded-report check followed the warnings to stderr; it
now inspects both streams so it cannot silently stop working again, and
the exit-status failure message quotes both.

Also drain the child's pipes concurrently and before waiting on it.
Waiting first deadlocks the moment either stream outgrows its pipe buffer,
which is not something this harness controls: xchtmlreport hands its own
stdout to every `xcresulttool` it spawns. A hung suite was observed on this
code path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`validate()` used `attachment.filename` as the fault detail, which is ""
for nameless attachments — the report then said `unresolvedAttachment:`
with nothing after it, and two nameless attachments deduped into one.
Fall back to the attachment name and its payload id, which is also what
the `payloadExportFailed` details carry, so the two correlate.

Also document what triggers each `Fault.Kind`. It is public API being
frozen in a major release; the names alone do not say whether a kind means
"could not read", "could not export", or "read but came back empty".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
README:
- Exit 1 was missing and is reachable: any failure to write the output
  rethrows, so `xchtmlreport -o <nonexistent-dir>` exits 1.
- "Report generated successfully" overclaimed for exit 0. Some XCResultKit
  decode failures are not surfaced as faults yet — `TestResults.xcresult`
  exits 0 while printing parse errors — so exit 0 means "no faults
  detected", not "report is complete".
- "could not be parsed" was wrong for two of the four fault kinds:
  `unresolvedAttachment` and `payloadExportFailed` are export and
  resolution failures, not parse failures.

test-artifacts.yml created and booted "iPhone 12" before running
prepareTestResults.sh. The boot fails on any modern runner, and the script
now resolves its own simulator, so the two lines are both broken and
redundant.

prepareTestResults.sh: under `set -e`, `read` returns non-zero on empty
input, so the script died at the `read` and never reached the "No iPhone
simulator available" guard below it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`1...5` -> `1 ... 5`. CliTests.swift was the one test file that passed
`swiftformat --lint` before this branch; keep it that way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds fault collection, degraded-report exit handling, serialized shared-payload exports, dynamic simulator fixture generation, updated CI workflows, and documentation for build commands and exit codes.

Changes

Fault reporting and CI fixture updates

Layer / File(s) Summary
Fault collection and export handling
Sources/XCTestHTMLReportCore/Classes/Helpers/*, Sources/XCTestHTMLReportCore/Classes/Models/*, Tests/XCTestHTMLReportTests/Fault*
The report pipeline records missing records, attachment faults, payload export failures, and log export failures. Shared payload exports use per-payload locking. Validation avoids duplicate faults.
CLI degraded-report handling
Sources/XCTestHTMLReport/XCTestHtmlReport.swift, Tests/XCTestHTMLReportTests/CliTests.swift, Tests/XCTestHTMLReportTests/TestSupport.swift
The CLI supports --lenient, reports collected faults, returns exit code 3 for degraded reports by default, and drains process output concurrently in tests.
Dynamic simulator fixture generation
prepareTestResults.sh, XCTestHTMLReportSampleApp/SampleAppUITests/FirstSuite.swift, Tests/XCTestHTMLReportTests/CoreTests.swift
Fixture generation selects an available runtime and iPhone simulator. The sample UI test uses deterministic inline HTML data. Device-name and version matching accept current simulator formats.
CI workflows and project documentation
.github/workflows/*, CONTRIBUTING.md, README.md
Workflows generate fixtures locally, use stable Xcode versions, run tests and coverage, upload failed fixtures, and use Codecov v4. Documentation describes build commands and CLI exit codes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

  • Issue 378 — The test update skips retry assertions affected by Xcode 26 fixture drift.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: degraded-failure reporting and making the test suite runnable without private fixtures.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tylervick/verification-oracle

Comment @coderabbitai help to get the list of available commands.


- name: Setup Xcode version
uses: maxim-lobanov/setup-xcode@v1.6.0
uses: maxim-lobanov/setup-xcode@v1.6.0
-instr-profile .build/debug/codecov/default.profdata > info.lcov

- uses: codecov/codecov-action@v3
- uses: codecov/codecov-action@v4

- name: Setup Xcode version
uses: maxim-lobanov/setup-xcode@v1.6.0
uses: maxim-lobanov/setup-xcode@v1.6.0

@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: 4

🤖 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 @.github/workflows/codecov.yml:
- Line 16: Pin the external actions to verified full commit SHAs: update
maxim-lobanov/setup-xcode in .github/workflows/codecov.yml lines 16-16 and
.github/workflows/test.yml lines 16-16, and codecov/codecov-action in
.github/workflows/codecov.yml lines 32-32; retain inline version comments where
useful.

In `@CONTRIBUTING.md`:
- Around line 50-55: Update the CONTRIBUTING.md guidance around
prepareTestResults.sh to remove the claim that a successful local run guarantees
a green pull request. Keep the instruction to run the two commands locally, but
state that CI is authoritative because environments and Xcode-generated
.xcresult fixtures may differ.

In `@Sources/XCTestHTMLReportCore/Classes/Models/ResultFile.swift`:
- Around line 84-86: Update ResultFile export error handling at
Sources/XCTestHTMLReportCore/Classes/Models/ResultFile.swift lines 84-86 and
115-116: in the Data(contentsOf:) failure path, record .payloadExportFailed with
the payload ID, and in the log write failure path, record .logExportFailed with
the log ID, alongside the existing error handling.

In `@Sources/XCTestHTMLReportCore/Classes/Models/Summary.swift`:
- Around line 111-121: Update the attachment validation loop in validate() so
each newly accepted unresolved attachment detail is inserted into alreadyFlagged
immediately before recording the fault. Preserve the existing duplicate check
and ensure subsequent attachments with the same faultDescription are skipped
during the same validation pass.
🪄 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: ff932951-aeae-47d8-807e-e18023f7b467

📥 Commits

Reviewing files that changed from the base of the PR and between 0146368 and 35a1ce2.

📒 Files selected for processing (19)
  • .github/workflows/codecov.yml
  • .github/workflows/test-artifacts.yml
  • .github/workflows/test.yml
  • .travis.yml
  • CONTRIBUTING.md
  • README.md
  • Sources/XCTestHTMLReport/XCTestHtmlReport.swift
  • Sources/XCTestHTMLReportCore/Classes/Helpers/FaultCollector.swift
  • Sources/XCTestHTMLReportCore/Classes/Helpers/Logger.swift
  • Sources/XCTestHTMLReportCore/Classes/Models/Attachment.swift
  • Sources/XCTestHTMLReportCore/Classes/Models/ResultFile.swift
  • Sources/XCTestHTMLReportCore/Classes/Models/Summary.swift
  • Tests/XCTestHTMLReportTests/CliTests.swift
  • Tests/XCTestHTMLReportTests/CoreTests.swift
  • Tests/XCTestHTMLReportTests/FaultCollectorTests.swift
  • Tests/XCTestHTMLReportTests/FaultReportingTests.swift
  • Tests/XCTestHTMLReportTests/TestSupport.swift
  • XCTestHTMLReportSampleApp/SampleAppUITests/FirstSuite.swift
  • prepareTestResults.sh
💤 Files with no reviewable changes (1)
  • .travis.yml


- name: Setup Xcode version
uses: maxim-lobanov/setup-xcode@v1.6.0
uses: maxim-lobanov/setup-xcode@v1.6.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Pin external GitHub Actions to immutable commit SHAs.

Version tags can move after review. Pin each action to a verified full commit SHA and retain an inline version comment if needed.

  • .github/workflows/codecov.yml#L16-L16: replace maxim-lobanov/setup-xcode@v1.6.0 with its verified full commit SHA.
  • .github/workflows/codecov.yml#L32-L32: replace codecov/codecov-action@v4 with its verified full commit SHA.
  • .github/workflows/test.yml#L16-L16: replace maxim-lobanov/setup-xcode@v1.6.0 with its verified full commit SHA.
🧰 Tools
🪛 GitHub Check: SonarCloud

[failure] 16-16: External GitHub Actions and workflows should be pinned to a commit hash

Use full commit SHA hash for this dependency.

See more on SonarQube Cloud

🪛 GitHub Check: SonarCloud Code Analysis

[failure] 16-16: Use full commit SHA hash for this dependency.

See more on https://sonarcloud.io/project/issues?id=XCTestHTMLReport_XCTestHTMLReport&issues=AZ_dnatfmIgK0UPq7v8g&open=AZ_dnatfmIgK0UPq7v8g&pullRequest=379

📍 Affects 2 files
  • .github/workflows/codecov.yml#L16-L16 (this comment)
  • .github/workflows/codecov.yml#L32-L32
  • .github/workflows/test.yml#L16-L16
🤖 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 @.github/workflows/codecov.yml at line 16, Pin the external actions to
verified full commit SHAs: update maxim-lobanov/setup-xcode in
.github/workflows/codecov.yml lines 16-16 and .github/workflows/test.yml lines
16-16, and codecov/codecov-action in .github/workflows/codecov.yml lines 32-32;
retain inline version comments where useful.

Source: Linters/SAST tools

Comment thread CONTRIBUTING.md
Comment on lines +50 to +55
`prepareTestResults.sh` picks the newest available iPhone simulator automatically.
No credentials or secrets are required — CI runs exactly these two commands, so a
green run locally means a green run on your pull request.

Regenerate fixtures after upgrading Xcode; `.xcresult` contents change between
Xcode versions.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the CI-success guarantee.

A local green run does not guarantee a green pull-request run. Local and CI environments can use different Xcode versions and generate different .xcresult fixtures. State that contributors should run these commands locally, but CI remains the authoritative check.

🤖 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 `@CONTRIBUTING.md` around lines 50 - 55, Update the CONTRIBUTING.md guidance
around prepareTestResults.sh to remove the claim that a successful local run
guarantees a green pull request. Keep the instruction to run the two commands
locally, but state that CI is authoritative because environments and
Xcode-generated .xcresult fixtures may differ.

Comment on lines 84 to +86
guard let savedURL = file.exportPayload(id: id) else {
Logger.warning("Can't export payload with id \(id)")
faultCollector.record(.payloadExportFailed, "payload id \(id)")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Record every export failure in FaultCollector.

The new fault handling records only failures where XCResultKit returns nil. A Data(contentsOf:) failure and a log write failure leave the collector unchanged. Summary.validate() can later report the payload case only as an unresolved attachment. It does not validate logs, so a log write failure can still exit with status 0.

  • Sources/XCTestHTMLReportCore/Classes/Models/ResultFile.swift#L84-L86: Record .payloadExportFailed in the Data(contentsOf:) error path with the payload ID.
  • Sources/XCTestHTMLReportCore/Classes/Models/ResultFile.swift#L115-L116: Record .logExportFailed in the log write error path with the log ID.
📍 Affects 1 file
  • Sources/XCTestHTMLReportCore/Classes/Models/ResultFile.swift#L84-L86 (this comment)
  • Sources/XCTestHTMLReportCore/Classes/Models/ResultFile.swift#L115-L116
🤖 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 `@Sources/XCTestHTMLReportCore/Classes/Models/ResultFile.swift` around lines 84
- 86, Update ResultFile export error handling at
Sources/XCTestHTMLReportCore/Classes/Models/ResultFile.swift lines 84-86 and
115-116: in the Data(contentsOf:) failure path, record .payloadExportFailed with
the payload ID, and in the log write failure path, record .logExportFailed with
the log ID, alongside the existing error handling.

Comment on lines +111 to +121
let alreadyFlagged = Set(
faultCollector.faults
.filter { $0.kind == .unresolvedAttachment }
.map(\.detail)
)

for attachment in allAttachments {
guard case .none = attachment.content else { continue }
let detail = attachment.faultDescription
guard !alreadyFlagged.contains(detail) else { continue }
faultCollector.record(.unresolvedAttachment, detail)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Deduplicate faults recorded in the same validation pass.

alreadyFlagged does not change inside the loop. If two unresolved attachments have the same faultDescription, validate() records both faults. Insert each accepted detail into the set before continuing.

Proposed fix
-        let alreadyFlagged = Set(
+        var alreadyFlagged = Set(
             faultCollector.faults
                 .filter { $0.kind == .unresolvedAttachment }
                 .map(\.detail)
         )
 
         for attachment in allAttachments {
             guard case .none = attachment.content else { continue }
             let detail = attachment.faultDescription
-            guard !alreadyFlagged.contains(detail) else { continue }
+            guard alreadyFlagged.insert(detail).inserted else { continue }
             faultCollector.record(.unresolvedAttachment, detail)
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let alreadyFlagged = Set(
faultCollector.faults
.filter { $0.kind == .unresolvedAttachment }
.map(\.detail)
)
for attachment in allAttachments {
guard case .none = attachment.content else { continue }
let detail = attachment.faultDescription
guard !alreadyFlagged.contains(detail) else { continue }
faultCollector.record(.unresolvedAttachment, detail)
var alreadyFlagged = Set(
faultCollector.faults
.filter { $0.kind == .unresolvedAttachment }
.map(\.detail)
)
for attachment in allAttachments {
guard case .none = attachment.content else { continue }
let detail = attachment.faultDescription
guard alreadyFlagged.insert(detail).inserted else { continue }
faultCollector.record(.unresolvedAttachment, detail)
🤖 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 `@Sources/XCTestHTMLReportCore/Classes/Models/Summary.swift` around lines 111 -
121, Update the attachment validation loop in validate() so each newly accepted
unresolved attachment detail is inserted into alreadyFlagged immediately before
recording the fault. Preserve the existing duplicate check and ensure subsequent
attachments with the same faultDescription are skipped during the same
validation pass.

macos-latest is now macos-26, which ships only Xcode 26.x, so the
xcode_version: 15 leg failed with 'Could not find Xcode version that
satisfied version spec'. Xcode 16 is absent there too.

Keeps the latest-stable + one-major-back pattern this matrix has used
since 2023 by pinning the older leg to macos-15, which still carries
Xcode 16.4 — the same approach the matrix took when it pinned macos-13
to test Xcode 14.

Also inlines the single-element arch list.

Co-Authored-By: Claude Opus 5 (1M context) <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.

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 @.github/workflows/ci.yml:
- Around line 14-22: Update the CI matrix to retain an Intel x86_64-apple-macosx
leg compatible with the .macOS(.v10_15) deployment target and existing release
workflow. Add the appropriate macOS runner and toolchain configuration alongside
the current macOS entries; only raise the deployment target if Intel support is
intentionally removed, including corresponding release documentation updates.
🪄 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: 8cff8bc3-e4a6-4804-a2cd-911e264fbb35

📥 Commits

Reviewing files that changed from the base of the PR and between 35a1ce2 and b655f99.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml

Comment thread .github/workflows/ci.yml
Comment on lines +14 to +22
# Newest toolchain, plus one major version back — the pattern this
# matrix has followed since 2023. The older leg needs its own image:
# macos-latest is now macos-26, which ships only Xcode 26.x.
include:
- os: macos-latest
xcode_version: latest-stable
- os: macos-15
xcode_version: "16"
runs-on: ${{ matrix.os }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

if rg -q '\.macOS\(\.v10_15\)' Package.swift &&
   ! rg -q 'x86_64-apple-macosx|x86_64|macos-[0-9]+-intel' .github/workflows; then
  echo "Package.swift supports macOS 10.15, but CI has no Intel target."
  exit 1
fi

Repository: XCTestHTMLReport/XCTestHTMLReport

Length of output: 171


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- workflow ---'
cat -n .github/workflows/ci.yml | sed -n '1,60p'

printf '%s\n' '--- package platform declarations ---'
rg -n -C 3 'macOS|platforms|v10_15|x86_64|arm64' Package.swift .github/workflows || true

printf '%s\n' '--- relevant workflow targets ---'
rg -n -C 4 'swift build|target|architecture|matrix|macos-' .github/workflows/ci.yml || true

Repository: XCTestHTMLReport/XCTestHTMLReport

Length of output: 3957


🌐 Web query:

Apple arm64 macOS minimum deployment target macOS 11 x86_64 macOS 10.15 Swift

💡 Result:

The minimum deployment target for macOS on arm64 (Apple Silicon) is macOS 11.0 [1][2][3]. Because Apple Silicon hardware was introduced with macOS 11, the build system enforces this requirement; any attempt to target an older macOS version for an arm64 binary slice will be clamped to 11.0 [3]. For x86_64 architectures, the minimum deployment target can be set to older versions of macOS (such as 10.12 or 10.13, depending on the Xcode version and SDK used) [1][4][3]. When building universal binaries containing both architectures, the macOS 11.0 minimum constraint applies only to the arm64 slice, while the x86_64 slice can maintain a lower deployment target [4][3]. Regarding Swift, the language itself does not dictate these hardware-based OS limits [4]. Instead, the minimum deployment target is a property of the toolchain, the SDK, and the underlying platform requirements [4]. While Swift Package Manager allows you to specify a minimum platform version in your Package.swift file using the.macOS API, you remain bound by the architectural constraints enforced by the build system [5][4][3][6]. If you specify a version lower than the required minimum for a specific architecture, the build system will effectively clamp the deployment target for that slice to its architectural minimum [3].

Citations:


Keep an Intel CI leg for the declared macOS 10.15 target.

Package.swift declares .macOS(.v10_15), and the release workflow still builds x86_64-apple-macosx. This CI workflow now builds only arm64-apple-macosx, which requires macOS 11 or later. Add an x86_64-apple-macosx matrix leg, or raise the deployment target and update the release documentation if Intel support is intentionally removed.

🤖 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 @.github/workflows/ci.yml around lines 14 - 22, Update the CI matrix to
retain an Intel x86_64-apple-macosx leg compatible with the .macOS(.v10_15)
deployment target and existing release workflow. Add the appropriate macOS
runner and toolchain configuration alongside the current macOS entries; only
raise the deployment target if Intel support is intentionally removed, including
corresponding release documentation updates.

@tylervick
tylervick merged commit 41f7019 into main Aug 7, 2026
4 of 6 checks passed
@tylervick
tylervick deleted the tylervick/verification-oracle branch August 7, 2026 19: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.

2 participants