From 7d4a00458a6af38c94bfc6bf17f5686dd0ca6e50 Mon Sep 17 00:00:00 2001 From: Alex Kroman Date: Fri, 9 Oct 2026 12:28:00 -0700 Subject: [PATCH] Make SelectionCopy's clear-then-write test deterministic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test modelled an app's late clipboard write with a detached Task that slept 30 ms, racing SelectionCopy's 500 ms wall-clock deadline. On a loaded CI runner the Task ran after the deadline: the copier gave up, restored the user's clipboard, and the late write then clobbered it — both expectations failed (seen on the v0.1.63 release PR's check run). The fake pasteboard now applies the late write the moment the cleared clipboard is first read, so the copier always sees the clear on one poll and the write on a later one. Verified the test still fails when SelectionCopy stops at the first change instead of waiting for a string. Co-Authored-By: Claude Opus 5.5 --- .../BlurtEngineTests/SelectionCopyTests.swift | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/Tests/BlurtEngineTests/SelectionCopyTests.swift b/Tests/BlurtEngineTests/SelectionCopyTests.swift index 7d729e05..79136962 100644 --- a/Tests/BlurtEngineTests/SelectionCopyTests.swift +++ b/Tests/BlurtEngineTests/SelectionCopyTests.swift @@ -11,6 +11,10 @@ final class FakeCopyPasteboard: CopyPasteboard { var changeCount = 0 var string: String? = "user's clipboard" var restoreCount = 0 + /// A write the app makes just after its clear: applied the moment the + /// cleared clipboard is first read, so the copier is guaranteed to see the + /// clear on one poll and the write on a later one — without racing a timer. + var lateWrite: String? } let state = Mutex(State()) @@ -23,7 +27,17 @@ final class FakeCopyPasteboard: CopyPasteboard { guard readable else { return nil } return PasteboardSnapshot(items: [], plainText: state.withLock(\.string)) } - func currentString() -> String? { state.withLock(\.string) } + func currentString() -> String? { + state.withLock { + let current = $0.string + if current == nil, let late = $0.lateWrite { + $0.lateWrite = nil + $0.string = late + $0.changeCount += 1 + } + return current + } + } func restore(_ saved: PasteboardSnapshot, ifChangeCountIs expected: Int) { state.withLock { guard $0.changeCount == expected else { return } @@ -102,17 +116,11 @@ struct SelectionCopyTests { pasteboard.state.withLock { $0.string = nil $0.changeCount += 1 - } - Task { - try? await Task.sleep(for: .milliseconds(30)) - pasteboard.state.withLock { - $0.string = "late copy" - $0.changeCount += 1 - } + $0.lateWrite = "late copy" } return true }, - timeout: .milliseconds(500)) + timeout: .seconds(5)) #expect(await copy.copySelection(maxCharacters: 100) == "late copy") #expect(pasteboard.string == "user's clipboard") }