Skip to content

tests: stop three fixtures from killing the test host on window close - #8701

Merged
austinywang merged 1 commit into
manaflow-ai:mainfrom
ejc3:fix-three-fixtures-window-release
Aug 4, 2026
Merged

austinywang merged 1 commit into
manaflow-ai:mainfrom
ejc3:fix-three-fixtures-window-release

Conversation

@ejc3

@ejc3 ejc3 commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Three suites built an NSWindow and later closed it without opting out of AppKit's close-time release. ARC still holds a strong reference, so each close over-releases and takes the whole test host down.

A dead host is worse than a failing test. The suite reports no verdict at all, and every suite sharing that host loses its verdict too, which is how a single unguarded window turns into a spray of unexplained reds elsewhere.

Measured, one suite per app host

Both arms ran on the same worktree with the same warm derived-data path, with only this change between them. #8651 is applied in both, since the cmuxTests target does not compile without it.

suite before after
TerminalNotificationSocketActionTests 2 restarts, 0 of 7 tests ran, ** TEST FAILED ** 0 restarts, 7 of 7, ** TEST SUCCEEDED **
FilePreviewPanelTextSavingTests 2 restarts, 2 of 27 ran, ** TEST FAILED ** 0 restarts, 27 run, 24 pass
FilePreviewReviewFeedbackTests 1 restart, 11 of 17 ran, ** TEST FAILED ** 0 restarts, 17 run, 16 pass

Where the guard goes, and why not everywhere

FilePreviewPanelTextSavingTests creates three windows across two tests, and all three go through one private windowHosting helper, so the guard belongs there rather than at three call sites. The other window fixtures declared in WindowAndDragTests.swift build windows and only ever orderOut them, which does not release, so they need nothing. I mapped every NSWindow site in that file to its enclosing suite before touching it rather than applying the flag broadly.

That mapping is the same rule that predicted these three suites in the first place: counting the tests that close a self-created unguarded window matches the measured host-restart count. Across the browser suites it lands at 16, 3, 1 and 1, and correctly predicts 0 for the one suite that fails assertions without ever restarting.

The product already sets isReleasedWhenClosed = false everywhere it owns a window (BrowserPanel.swift, BrowserPrewarmedWebViewPool.swift, BrowserPopupWindowController.swift, ReleasingWindowController.swift). Only these fixtures were missing it, which is why nothing in Sources changes here.

What is still red behind the crash

Fixing the crash exposed assertions nobody could see while the host was dying. These are separate bugs and get their own change:

FilePreviewPanelTextSavingTests  (24 of 27 pass)
  testSavingTextViewUsesConfiguredSaveShortcut      ("") != ("saved by configured shortcut")
  testTextEditorInsetsReapplyWhenMovedBetweenWindows  ("nil") != ("Optional(0.0)")   x2

FilePreviewReviewFeedbackTests   (16 of 17 pass)
  testSavingTextViewUsesChordedSaveShortcut         ("") != ("saved by chord")

The two SavingTextView…SaveShortcut failures look like one mechanism: a convenience initializer that builds the view without a text container, so there is no text to save.

Read the restart count and the test count when checking this, not the verdict line. A host that dies mid-run still prints a summary, and these logs also carry earlier Executed 0 tests, with 0 failures lines that a tail-only grep would read as fine.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Prevents test host crashes when three test fixtures close their NSWindow. Sets isReleasedWhenClosed = false so closing doesn’t over-release under ARC, removing host restarts and letting all tests run.

  • Bug Fixes
    • TerminalNotificationSocketActionTests: set testWindow.isReleasedWhenClosed = false.
    • FilePreviewPanelTextSavingTests: set the flag in the private windowHosting helper used by two tests that create three windows.
    • FilePreviewReviewFeedbackTests: set window.isReleasedWhenClosed = false.
    • Result: no host restarts; suites run to completion. Any newly visible assertion failures are separate. Tests-only change; no product code touched.

Written for commit 4b04891. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Tests
    • Improved test window lifecycle handling to prevent over-release errors.
    • Increased stability and reliability of window, file preview, terminal notification, and drag-related test suites.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Three test window creation paths now disable AppKit’s automatic release-on-close behavior, preventing test-host over-release during cleanup.

Changes

Test Window Lifecycle

Layer / File(s) Summary
Disable automatic window release
cmuxTests/FilePreviewReviewFeedbackTests.swift, cmuxTests/TerminalNotificationSocketActionTests.swift, cmuxTests/WindowAndDragTests.swift
Test-created NSWindow instances set isReleasedWhenClosed to false, with comments documenting the test-host lifecycle issue.

Estimated code review effort: 1 (Trivial) | ~3 minutes

Possibly related PRs

Suggested reviewers: austinywang

🚥 Pre-merge checks | ✅ 25
✅ Passed checks (25 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Cmux Swift Actor Isolation ✅ Passed Test-only NSWindow lifecycle fixes; no production code or actor-isolation-sensitive declarations were introduced or worsened.
Cmux Swift Blocking Runtime ✅ Passed PR only adds test-only NSWindow lifecycle flags/comments; no semaphores, waits, sleeps, syncs, or locks were introduced or expanded.
Cmux Browser Automation Off-Main ✅ Passed PR only sets NSWindow.isReleasedWhenClosed=false in test fixtures; no browser.* routing, worker-lane, or policy-test changes were introduced.
Cmux Expensive Synchronous Load ✅ Passed Diff only touches three test files and adds NSWindow lifecycle guards; no production Swift or agent-load paths are introduced.
Cmux Cache Substitution Correctness ✅ Passed Only test files changed; no production Swift/TS/JS cache or snapshot substitution appears in the diff, so the cache-substitution rule is not implicated.
Cmux No Hacky Sleeps ✅ Passed Only test-only NSWindow lifecycle flags changed; no sleeps, timers, polling, or delayed dispatch were added or worsened, and the rule excludes Swift tests.
Cmux Algorithmic Complexity ✅ Passed Only test fixtures changed; no production collection-processing paths or scalable scans were added or worsened, so the complexity rule is not implicated.
Cmux Swift Concurrency ✅ Passed Diff only adds isReleasedWhenClosed = false to test-created AppKit windows; no new Dispatch/Combine/completion-handler/Task lifecycle patterns.
Cmux Swift @Concurrent ✅ Passed Diff only sets NSWindow.isReleasedWhenClosed in test fixtures; no @concurrent, nonisolated async, or UI-hop changes are introduced.
Cmux Swift Package Boundaries ✅ Passed PASS: Diff only touches cmuxTests fixtures, and the boundary rule explicitly allows test fixtures; no production Swift files changed.
Cmux Swiftpm Lockfiles ✅ Passed Only test files changed; no .gitignore, Package.swift, Xcode project, workflow, or Package.resolved diffs are present, so the SwiftPM lockfile rule isn't implicated.
Cmux Swift Logging ✅ Passed Diff only touches test files and adds no print/debugPrint/dump/NSLog/Logger usage; the swift-logging rule exempts tests/fixtures.
Cmux User-Facing Error Privacy ✅ Passed Commit only touches test files and adds private comments plus isReleasedWhenClosed guards; no production user-facing error text changed.
Cmux Full Internationalization ✅ Passed Only test fixtures/comments changed in cmuxTests; the i18n rule exempts tests and no production locale assets or user-facing strings were touched.
Cmux Swiftui State Layout ✅ Passed Changes are AppKit test-fixture window-release guards only; no new SwiftUI state/layout patterns appear.
Cmux Architecture Rethink ✅ Passed Test-only NSWindow close guards with clear fixture ownership; no timing hacks, extra owners, or split lifecycle wiring were introduced.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: the three NSWindow changes are test-only fixtures in cmuxTests, which the rule explicitly allows; the lint script scans Sources only.
Cmux Source Artifacts ✅ Passed All changed paths are Swift test sources, and the diff only adds isReleasedWhenClosed guards/comments—no logs, build outputs, caches, screenshots, or temp artifacts.
Cmux No Test Or Debug Seam In Production Source ✅ Passed HEAD changes only three cmuxTests files; no production Sources files were modified or widened for test/debug access.
Cmux No Ambient Global State ✅ Passed PR only adds test-local window flags inside methods; no new file-scope funcs, globals, namespaces, or singletons were introduced.
Title check ✅ Passed The title clearly and concisely describes the main change: preventing three test fixtures from crashing the test host when windows close.
Description check ✅ Passed The description explains the cause, scope, testing results, and remaining failures in clear detail, despite omitting some template boilerplate sections.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 25, 2026
Seven test call sites constructed a bare SavingTextView(). The product never
does: makeFilePreviewTextView() builds an explicit TextKit 1 stack, because a
default NSTextView is TextKit 2 and its selection path pegged the main thread on
large documents (manaflow-ai#4576, manaflow-ai#5255). A bare init therefore has no configured text
container, and it also skips applyFilePreviewTextEditorInsets(), which the
factory applies.

So these tests exercised a view the app refuses to ship, and failed on it: the
save-shortcut tests read back an empty string instead of the saved text, and the
inset test read nil where it expected a value.

The files already disagreed with themselves. FilePreviewReviewFeedbackTests used
the bare init at line 44 and the factory at line 408, CanvasShortcutContextTests
uses the factory at three sites, and FilePreviewTextEditorTextKitTests exists to
assert the factory yields a pure TextKit 1 view.

  FilePreviewPanelTextSavingTests   27 tests, 3 failures -> 0 failures
  FilePreviewReviewFeedbackTests    17 tests, 1 failure  -> 0 failures

Both suites need the window-release guard in manaflow-ai#8701 to reach these assertions at
all; without it they crash the test host first. Both arms above were measured
with that guard applied.
@ejc3
ejc3 force-pushed the fix-three-fixtures-window-release branch from 36e5918 to 7edaab5 Compare July 25, 2026 04:56
@ejc3
ejc3 marked this pull request as ready for review July 25, 2026 04:58
@greptile-apps

greptile-apps Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes three test fixtures that were crashing the XCTest host process by closing an NSWindow without opting out of AppKit's close-time release. With isReleasedWhenClosed at its default (true), AppKit sends an extra release on close(), which over-releases under ARC and terminates the host — silently dropping every suite sharing that process.

  • Sets isReleasedWhenClosed = false in TerminalNotificationSocketActionTests and FilePreviewReviewFeedbackTests immediately after window construction, and in the shared windowHosting(_:) helper in FilePreviewPanelTextSavingTests (covering all 14 affected tests in one place).
  • No production source is changed; the fix is test-only and matches the pattern already used throughout Sources/ (BrowserPanel.swift, ReleasingWindowController.swift, etc.).

Confidence Score: 5/5

Safe to merge — all three changes are confined to the test target, apply the standard AppKit isReleasedWhenClosed = false guard that the rest of the codebase already uses, and carry no risk of regression in production code.

The changes are minimal, test-only, and target an objectively wrong default (isReleasedWhenClosed = true under ARC) that was causing deterministic host crashes. The fix is consistent with how every production window in Sources/ handles the same situation. No new logic, no new state, no risk of behavioral change outside the three affected test suites.

Files Needing Attention: No files require special attention.

Important Files Changed

Filename Overview
cmuxTests/FilePreviewReviewFeedbackTests.swift Adds window.isReleasedWhenClosed = false immediately after NSWindow creation in testFocusCoordinatorKeepsPendingFocusUntilEndpointHasWindow, before the defer block that calls close(). Prevents ARC double-release that was crashing the test host.
cmuxTests/TerminalNotificationSocketActionTests.swift Adds testWindow.isReleasedWhenClosed = false immediately after NSWindow construction inside the includeWindow branch of the test setup helper. Correct placement and idiomatic fix.
cmuxTests/WindowAndDragTests.swift Adds window.isReleasedWhenClosed = false in the private windowHosting(_:) helper used by 14 tests in FilePreviewPanelTextSavingTests. Centralising the guard in the helper rather than at 14 call sites is the right approach.

Sequence Diagram

sequenceDiagram
    participant Test as XCTest Function
    participant AppKit as AppKit (NSWindow)
    participant ARC as Swift ARC

    Note over Test,ARC: Before fix (isReleasedWhenClosed = true, default)
    Test->>AppKit: NSWindow(...)
    AppKit-->>ARC: "ARC retains (refcount = 1)"
    Test->>AppKit: window.close()
    AppKit->>ARC: "AppKit releases (refcount = 0 → dealloc)"
    Test-->>ARC: "local var goes out of scope → release (refcount = -1 💥)"
    Note over Test,ARC: Over-release → host process crash

    Note over Test,ARC: After fix (isReleasedWhenClosed = false)
    Test->>AppKit: NSWindow(...)
    AppKit-->>ARC: "ARC retains (refcount = 1)"
    Test->>AppKit: "window.isReleasedWhenClosed = false"
    Test->>AppKit: window.close()
    Note over AppKit: AppKit skips extra release
    Test-->>ARC: "local var goes out of scope → release (refcount = 0 → dealloc ✅)"
Loading

Reviews (1): Last reviewed commit: "tests: stop three fixtures from killing ..." | Re-trigger Greptile

Three suites created an NSWindow and later closed it without opting out of
AppKit's close-time release, so each close over-released a window ARC still
held and took the whole test host down with it.

A dead host is worse than a failing test: the suite reports no verdict, and
every suite sharing that host loses its verdict too.

  TerminalNotificationSocketActionTests  2 restarts, 0 of 7 tests ran   -> 0 restarts, 7 pass
  FilePreviewPanelTextSavingTests        2 restarts, 2 of 27 tests ran  -> 0 restarts, 27 run
  FilePreviewReviewFeedbackTests         1 restart, 11 of 17 tests ran  -> 0 restarts, 17 run

FilePreviewPanelTextSavingTests closes a window in fourteen tests, all through
one private windowHosting helper, so the guard goes there rather than at each
call site. The other twenty-two suites in that file build windows and only ever
orderOut them, which does not release, so they need nothing.

Two of these suites still have assertion failures behind the crash that nobody
could see while the host was dying: three in FilePreviewPanelTextSavingTests
(24 of 27 pass) and one in FilePreviewReviewFeedbackTests (16 of 17 pass).
Those are separate bugs and get their own change.

The product already sets isReleasedWhenClosed = false everywhere it owns a
window; only these fixtures were missing it.
@ejc3
ejc3 force-pushed the fix-three-fixtures-window-release branch from 7edaab5 to 4b04891 Compare July 31, 2026 06:55
@cursor

cursor Bot commented Jul 31, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 31, 2026
Seven test call sites constructed a bare SavingTextView(). The product never
does: makeFilePreviewTextView() builds an explicit TextKit 1 stack, because a
default NSTextView is TextKit 2 and its selection path pegged the main thread on
large documents (manaflow-ai#4576, manaflow-ai#5255). A bare init therefore has no configured text
container, and it also skips applyFilePreviewTextEditorInsets(), which the
factory applies.

So these tests exercised a view the app refuses to ship, and failed on it: the
save-shortcut tests read back an empty string instead of the saved text, and the
inset test read nil where it expected a value.

The files already disagreed with themselves. FilePreviewReviewFeedbackTests used
the bare init at line 44 and the factory at line 408, CanvasShortcutContextTests
uses the factory at three sites, and FilePreviewTextEditorTextKitTests exists to
assert the factory yields a pure TextKit 1 view.

  FilePreviewPanelTextSavingTests   27 tests, 3 failures -> 0 failures
  FilePreviewReviewFeedbackTests    17 tests, 1 failure  -> 0 failures

Both suites need the window-release guard in manaflow-ai#8701 to reach these assertions at
all; without it they crash the test host first. Both arms above were measured
with that guard applied.
@austinywang

Copy link
Copy Markdown
Contributor

Closeout review audit:

  • CodeRabbit and Greptile reported no actionable findings; both validated the three test-fixture lifetime guards.
  • The Cursor spend-limit notice is infrastructure-only and requires no code change.
  • GitHub has no inline review comments or submitted reviews on this PR.
  • The deterministic cmux policy checker only flags the two pre-existing XCTest files touched by this fixture-only patch. I am consciously rejecting an in-PR framework migration: the patch adds no test or assertion, WindowAndDragTests.swift contains many unrelated legacy suites, and converting them would broaden this lifetime fix into a risky behavioral migration. All other policy and guard checks passed.

@austinywang

Copy link
Copy Markdown
Contributor

Factual correction to the bot summaries: FilePreviewPanelTextSavingTests uses the shared windowHosting helper in two tests that create three windows total, not fourteen tests. The implementation boundary remains correct because every one of those three windows is closed through the paired helper. I corrected both occurrences in the PR body.

austinywang pushed a commit that referenced this pull request Aug 4, 2026
Seven test call sites constructed a bare SavingTextView(). The product never
does: makeFilePreviewTextView() builds an explicit TextKit 1 stack, because a
default NSTextView is TextKit 2 and its selection path pegged the main thread on
large documents (#4576, #5255). A bare init therefore has no configured text
container, and it also skips applyFilePreviewTextEditorInsets(), which the
factory applies.

So these tests exercised a view the app refuses to ship, and failed on it: the
save-shortcut tests read back an empty string instead of the saved text, and the
inset test read nil where it expected a value.

The files already disagreed with themselves. FilePreviewReviewFeedbackTests used
the bare init at line 44 and the factory at line 408, CanvasShortcutContextTests
uses the factory at three sites, and FilePreviewTextEditorTextKitTests exists to
assert the factory yields a pure TextKit 1 view.

  FilePreviewPanelTextSavingTests   27 tests, 3 failures -> 0 failures
  FilePreviewReviewFeedbackTests    17 tests, 1 failure  -> 0 failures

Both suites need the window-release guard in #8701 to reach these assertions at
all; without it they crash the test host first. Both arms above were measured
with that guard applied.

Co-authored-by: ejc3 <ejc3@users.noreply.github.com>
@austinywang
austinywang merged commit 91df18d into manaflow-ai:main Aug 4, 2026
6 checks passed
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