Skip to content

Fix flaky FileExplorerStoreTests (macOS Compatibility) - #2880

Merged
lawrencecchen merged 2 commits into
mainfrom
fix-fileexplorer-flaky-test
Apr 14, 2026
Merged

lawrencecchen merged 2 commits into
mainfrom
fix-fileexplorer-flaky-test

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Apr 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The macOS Compatibility workflow has been flaky on warp-macos-15-arm64-6x since PR #1963 was merged (commit 6d7bbd2, author: me). The failure surfaced again on main after PR #2875 and the user flagged it started failing around commit ab46c55 too.

Root cause: cmuxTests/FileExplorerStoreTests.swift used fixed Task.sleep(nanoseconds: 100_000_000) waits to let the unstructured Task { ... } started by setRootPath/expand/setProvider hop to @MainActor and mutate @Published state. On the slower macOS 15 warp runner, 100ms isn't enough — rootNodes is still empty, and testExpandedNodesSurviveStoreRecreation line 180 force-unwraps store.rootNodes.first { \$0.name == \"lib\" }! and crashes the test runner. macOS 26 always wins the race, so the older runner alone hangs until the 30m job timeout.

Fix: replace every fixed sleep with a polling waitFor(...) helper that returns as soon as the observable state (rootNodes, children, error, isRootLoading) matches the expected condition, with a 5s safety timeout. Also pin the test class to @MainActor so reads and writes of @Published state share an actor instead of racing across threads.

Test plan

  • cmux-unit scheme builds locally
  • All 8 FileExplorerStoreTests pass locally in 0.34s total (vs. ~1.2s of sleeps before)
  • macOS Compatibility passes on both warp-macos-26-arm64-6x and warp-macos-15-arm64-6x in CI

Summary by cubic

Stabilizes FileExplorerStoreTests by replacing fixed sleeps with a polling waitFor(...) that throws on timeout and marking the test class @MainActor. This removes race conditions on slower macOS runners and prevents force-unwrap crashes.

  • Bug Fixes
    • waitFor(...) now throws on timeout to stop tests before unsafe fallthroughs.
    • Remote hydration test waits for the provider to be consulted, not just isRootLoading == false.
    • Removed redundant srcNode.children = nil in the stale error retry path.
    • Tests run reliably on CI and finish faster locally.

Written for commit 03dc505. Summary will update on new commits.

Summary by CodeRabbit

  • Tests
    • Improved async test reliability by adding main-thread isolation for test execution.
    • Introduced a polling-based wait helper to replace fixed delays, gating assertions on observable state transitions.
    • Strengthened assertions to check state via polling (loading, expansion, remote hydration, retries) instead of timing-based sleeps.

Tests used fixed Task.sleep(100ms) waits to let unstructured Tasks
started by setRootPath/expand/setProvider hop to @mainactor and
mutate @published state. On warp-macos-15 the 100ms budget wasn't
enough, so testExpandedNodesSurviveStoreRecreation hit a force-unwrap
nil crash. Replace the sleeps with polling waits on observable state
(rootNodes, children, error, isRootLoading) and pin the test class to
@mainactor so reads and writes share an actor.
@vercel

vercel Bot commented Apr 14, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Apr 14, 2026 3:42am

@coderabbitai

coderabbitai Bot commented Apr 14, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ebc5884c-6173-4f71-877f-7db6219aefb8

📥 Commits

Reviewing files that changed from the base of the PR and between 5e8d7e6 and 03dc505.

📒 Files selected for processing (1)
  • cmuxTests/FileExplorerStoreTests.swift

📝 Walkthrough

Walkthrough

Introduces an @MainActor async polling helper waitFor(_:timeout:file:line:_:) and refactors cmuxTests/FileExplorerStoreTests.swift to replace fixed Task.sleep(...) delays with polling-based waits, updating assertions to rely on observed state transitions (loading, expansion, hydration, retry).

Changes

Cohort / File(s) Summary
Test Polling & Refactors
cmuxTests/FileExplorerStoreTests.swift
Added an @MainActor async waitFor helper to poll boolean conditions with timeout; replaced multiple Task.sleep(...) usages with await waitFor(...); moved assertions into polling conditions and adjusted tests for provider hydration and stale-error retry flows.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • PR #2308: Makes similar test-reliability changes by introducing a polling helper (waitUntil) and replacing fixed sleeps with condition-based waits in cmuxTests.

Poem

🐰 I hopped through tests with patient cheer,
No more naps—just checks that appear.
I poll the state, I wait, then sing,
Flaky sleeps out, stability in. 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix flaky FileExplorerStoreTests (macOS Compatibility)' directly and clearly describes the main change: fixing flakiness in test suite on macOS.
Description check ✅ Passed The description comprehensively covers the problem (fixed sleeps insufficient on slower runners), root cause, solution (polling waitFor helper), and test verification with checklist items marked.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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-fileexplorer-flaky-test

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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 the current code and only fix it if needed.

Inline comments:
In `@cmuxTests/FileExplorerStoreTests.swift`:
- Around line 153-154: The test can pass before the initial load runs because it
only waits for store.isRootLoading == false; change the test to first wait for
the load to start and then for it to finish (e.g. await waitFor("{start}") {
store.isRootLoading == true } followed by await waitFor("{finish}") {
store.isRootLoading == false }) so the unstructured root-load task has actually
been executed; locate usages of waitFor and the store.isRootLoading check in
FileExplorerStoreTests.swift and update the assertion sequence around the
initial root load to assert start->finish rather than only finish.
🪄 Autofix (Beta)

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

Run ID: b8462ee1-6ecc-44b7-a866-2f4b51240b8b

📥 Commits

Reviewing files that changed from the base of the PR and between a7f67e3 and 5e8d7e6.

📒 Files selected for processing (1)
  • cmuxTests/FileExplorerStoreTests.swift

Comment thread cmuxTests/FileExplorerStoreTests.swift Outdated
@greptile-apps

greptile-apps Bot commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes flaky FileExplorerStoreTests on the slower warp-macos-15 CI runner by replacing hardcoded Task.sleep delays with a polling waitFor helper that returns as soon as the observed @Published state matches the expected condition (5 s safety timeout). The test class is also pinned to @MainActor, which keeps condition-closure reads on the same actor as the store's loadChildren mutations, eliminating the data-race that made 100 ms sleeps insufficient.

Confidence Score: 5/5

  • Safe to merge — approach is correct and all remaining findings are P2.
  • The @MainActor pinning and polling waitFor pattern correctly eliminate the timing race without introducing new concurrency hazards. The only finding is a no-op srcNode.children = nil that is cosmetically misleading but has zero runtime impact.
  • No files require special attention.

Important Files Changed

Filename Overview
cmuxTests/FileExplorerStoreTests.swift Fixed flaky tests by replacing fixed Task.sleep waits with a polling waitFor helper and pinning the class to @MainActor; one no-op srcNode.children = nil is cosmetically misleading but harmless.

Sequence Diagram

sequenceDiagram
    participant Test as @MainActor Test
    participant Store as FileExplorerStore
    participant Task as Unstructured Task
    participant Provider as MockFileExplorerProvider

    Test->>Store: setRootPath("/home/user/project")
    Store->>Store: "reload() → isRootLoading = true"
    Store->>Task: "launch Task { loadChildren }"
    Test->>Test: "waitFor("root loaded") { condition }"

    loop Poll every 10ms (main actor yields)
        Test->>Test: condition() → false
        Test->>Test: await Task.sleep(10ms) [yields main actor]
        Task-->>Store: "@MainActor loadChildren runs"
        Store->>Provider: listDirectory(path:)
        Provider-->>Store: [FileExplorerEntry]
        Store->>Store: "rootNodes = children"
        Store->>Store: "isRootLoading = false"
        Test->>Test: condition() → true ✓
    end

    Test->>Test: assertions on rootNodes
Loading

Reviews (1): Last reviewed commit: "Fix flaky FileExplorerStoreTests" | Re-trigger Greptile

Comment thread cmuxTests/FileExplorerStoreTests.swift Outdated
Comment on lines +243 to +244
store.collapse(node: srcNode)
srcNode.children = nil

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Redundant children = nil assignment

After a failed expand, loadChildren's catch block only sets parentNode.isLoading and parentNode.error — it never assigns parentNode.children — so srcNode.children is already nil when this line runs. The explicit nil-out is a no-op and slightly misleads the reader into thinking the error path might leave children non-nil. Either remove it, or add a comment explaining it's a defensive guard against a potential future refactor.

Suggested change
store.collapse(node: srcNode)
srcNode.children = nil
store.collapse(node: srcNode)
store.expand(node: srcNode)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 03dc505 — dropped the redundant srcNode.children = nil. The error path in loadChildren never assigns children, so it was a no-op.

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="cmuxTests/FileExplorerStoreTests.swift">

<violation number="1" location="cmuxTests/FileExplorerStoreTests.swift:66">
P2: `waitFor` times out with `XCTFail` and `return`, but does not abort the test, so callers can continue into force-unwrap/index access and crash after a failed wait.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread cmuxTests/FileExplorerStoreTests.swift Outdated
- waitFor now throws on timeout instead of XCTFail + return, so callers
  abort via `try await` and don't fall through to force-unwraps
  (cubic, cmuxTests/FileExplorerStoreTests.swift:66)
- In testExpandedRemoteNodesHydrateWhenProviderBecomesAvailable, wait
  for the provider to actually be consulted, not just for
  isRootLoading == false which can be true before the unstructured
  load Task has started (CodeRabbit, line 154)
- Drop redundant srcNode.children = nil in testStaleErrorClearsOnRetry;
  the error path in loadChildren never assigns children (Greptile,
  line 244)
@lawrencecchen
lawrencecchen merged commit fffc8ef into main Apr 14, 2026
26 checks passed
@lawrencecchen
lawrencecchen deleted the fix-fileexplorer-flaky-test branch April 14, 2026 03:51
rodchristiansen pushed a commit to rodchristiansen/cmux that referenced this pull request Sep 2, 2026
)

* Fix flaky FileExplorerStoreTests

Tests used fixed Task.sleep(100ms) waits to let unstructured Tasks
started by setRootPath/expand/setProvider hop to @mainactor and
mutate @published state. On warp-macos-15 the 100ms budget wasn't
enough, so testExpandedNodesSurviveStoreRecreation hit a force-unwrap
nil crash. Replace the sleeps with polling waits on observable state
(rootNodes, children, error, isRootLoading) and pin the test class to
@mainactor so reads and writes share an actor.

* Address review feedback

- waitFor now throws on timeout instead of XCTFail + return, so callers
  abort via `try await` and don't fall through to force-unwraps
  (cubic, cmuxTests/FileExplorerStoreTests.swift:66)
- In testExpandedRemoteNodesHydrateWhenProviderBecomesAvailable, wait
  for the provider to actually be consulted, not just for
  isRootLoading == false which can be true before the unstructured
  load Task has started (CodeRabbit, line 154)
- Drop redundant srcNode.children = nil in testStaleErrorClearsOnRetry;
  the error path in loadChildren never assigns children (Greptile,
  line 244)

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview — 03dc5052 Deployed Apr 14, 2026 by vercel[bot]
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