Skip to content

Stop asserting simulator reliability in testResultStatusCount - #397

Merged
tylervick merged 1 commit into
mainfrom
fix-flaky-status-count
Aug 7, 2026
Merged

Stop asserting simulator reliability in testResultStatusCount#397
tylervick merged 1 commit into
mainfrom
fix-flaky-status-count

Conversation

@tylervick

Copy link
Copy Markdown
Member

The Codecov run on main is failing. Test on the same commit passes.

53adfaf  Test    -> success
53adfaf  Codecov -> failure

Both run ./prepareTestResults.sh && swift test. Same code, same commit, different outcome — so this is nondeterminism, not a code defect.

What happens

CoreTests.swift:65: XCTAssertEqual failed: ("Optional(6)") is not equal to ("Optional(7)")
CoreTests.swift:67: XCTAssertEqual failed: ("Optional(6)") is not equal to ("Optional(5)")

Passed went 7→6 and Failed went 5→6; the total stayed 13. One test moved buckets. The fixture-generation log shows FirstSuite.testOne as both passed and failed:

1 Test Case '-[SampleAppUITests.FirstSuite testOne]' passed
1 Test Case '-[SampleAppUITests.FirstSuite testOne]' failed

Its body is XCTAssert(true) plus an attachment — it cannot fail on its own assertion. The failure comes from setUp, which calls XCUIApplication().launch() with continueAfterFailure = false. Under CI load the simulator is sometimes slow to launch the app, and the test fails before its body runs.

Why it started now

This is a consequence of #379. Fixtures used to be fixed artifacts downloaded from a private bucket — byte-identical every run, so exact per-bucket assertions were safe. They are now regenerated on every run, which is a better trade overall (they can't go stale, and fork PRs can run them), but it means UI-test flakiness feeds straight into assertions that were written for stable input.

The fix

Assert only what the sample sources actually determine:

Assertion Why it's stable
all == 13 one row per test method, fixed by source
skipped == 1 SampleAppUnitTests.testSkipped is an unconditional XCTSkipIf
mixed == 0 TestResults excludes RetryTests
passed + failed == all - skipped every test lands in exactly one bucket
failed >= 5 five sample tests fail deliberately

The exact pass/fail split is dropped, because it measures whether the simulator launched an app in time — not anything this project does.

This still catches real regressions. Losing a test breaks all == 13. Silently dropping failures breaks failed >= 5. Miscounting breaks the sum invariant. Under the flaked run (passed=6, failed=6) the new assertions correctly pass.

testMixedStatus is left alone: it reads RetryResults, and RetryTests has no setUp and never launches the app, so it isn't exposed to this.

Follow-up

The underlying flakiness is worth its own issue — the sample UI tests launch the app purely so the run produces screen recordings (the only .mp4 fixture coverage), and none of them actually interact with the UI. There may be a way to keep the recordings without the launch being a failure point.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@tylervick, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 16 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1483b6ae-de3b-4103-bb76-2f30b5663250

📥 Commits

Reviewing files that changed from the base of the PR and between 53adfaf and 895611c.

📒 Files selected for processing (1)
  • Tests/XCTestHTMLReportTests/CoreTests.swift
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-flaky-status-count

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

The Codecov run on main failed while the Test run on the same commit
passed: 53adfaf produced Passed(6)/Failed(6) instead of Passed(7)/Failed(5).
FirstSuite.testOne appears as both passed and failed across the fixture
generation logs. Its body is XCTAssert(true) plus an attachment, so it
cannot fail on its own assertion — the failure comes from setUp, which
calls XCUIApplication().launch() with continueAfterFailure = false. A slow
simulator turns a would-be pass into a failure.

This became reachable when fixtures started being regenerated on every
run instead of downloaded as fixed artifacts. Exact per-bucket counts
were safe against byte-identical fixtures; they are not against freshly
generated ones.

Assert what the sample sources actually determine — total, skipped, mixed,
the bucket-sum invariant, and a floor on deliberate failures — and stop
asserting the pass/fail split, which measures the simulator rather than
this project. A lost test still breaks the "all == 13" assertion, dropped
failures still break "failed >= 5", and miscounting still breaks the sum.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tylervick
tylervick force-pushed the fix-flaky-status-count branch from 9d9f916 to 895611c Compare August 7, 2026 20:24
@tylervick
tylervick merged commit 69b32fe into main Aug 7, 2026
6 checks passed
@tylervick
tylervick deleted the fix-flaky-status-count branch August 7, 2026 20:38
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