Skip to content

test(datagrid): hold the window across the appearance flip in the overlay colour test - #2464

Merged
datlechin merged 2 commits into
mainfrom
test/overlay-appearance-flake
Aug 26, 2026
Merged

test(datagrid): hold the window across the appearance flip in the overlay colour test#2464
datlechin merged 2 commits into
mainfrom
test/overlay-appearance-flake

Conversation

@datlechin

Copy link
Copy Markdown
Member

Found while investigating #2449, when CellOverlayAppearanceTests/appAppearanceChangeRepaintsAnOpenOverlay failed once in an isolated run and then passed 3/3 on re-run with identical code. It will redden CI at random.

Root cause

The test builds an NSWindow, adds the overlay container to it, flips NSApp.appearance, and reads the container's layer colours. The window is never ordered front, and its last use is the contentView read that happens before the flip.

AppKit does not retain a window that was never ordered front. Measured with a probe that drains an autorelease pool between building the window and flipping the appearance:

after pool drain: window alive=false   probe.window != nil=false
callbacks after flip: 0                effectiveAppearance=NSAppearanceNameAqua
NSApp.windows count=0

The window is deallocated, the container is left in no window, viewDidChangeEffectiveAppearance never runs, and effectiveAppearance stays at Aqua. Both reads then return the light colour and the two comparisons fail together, which is exactly the signature seen:

lightBackground.brightness → 1.0    darkBackground.brightness → 1.0
lightBorder.brightness → 0.4536     darkBorder.brightness → 0.4536

It is intermittent because where an autorelease pool drains between two statements is not the test's to decide. A first probe that did not drain a pool showed the window surviving and the callback firing normally, which is why this looked unreproducible.

The fix

Hold the window across the flip with withExtendedLifetime(window), and assert the precondition the test was silently relying on: that the container is still in that window, and that its effectiveAppearance actually moved to darkAqua. If this ever fails again it now names its own cause instead of reporting two colours that happen to match.

The test keeps NSApp.appearance as its channel on purpose. Its own doc comment explains why: on that path a view's effectiveAppearance is already the new one while NSAppearance.currentDrawing() is still the old one, so a bare .cgColor resolves the old colour, and setting container.appearance directly would pass even without the performAsCurrentDrawingAppearance wrapper that is the thing under test. Nothing here weakens what it guards.

Also sets isReleasedWhenClosed = false, matching the other window-building suites.

Verification

  • test PASS, 13 cases across the three overlay suites, and the hardened test run 4 times in isolation, 4/4.
  • lint 0 SwiftLint violations.

Repeats cannot prove a fix for an intermittent failure, so the argument here rests on the measurement above rather than on the reruns: the failure mode is reproduced deterministically with a pool drain, and withExtendedLifetime is what removes it.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
@datlechin
datlechin merged commit 12a41f0 into main Aug 26, 2026
6 checks passed
@datlechin
datlechin deleted the test/overlay-appearance-flake branch August 26, 2026 14:59
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