From e48ba5a83ecea8ccf40e2eb8e75e09aa0d3e311d Mon Sep 17 00:00:00 2001 From: garrytan Date: Sun, 1 Nov 2026 16:30:23 +0000 Subject: [PATCH] fix: real-Edge test bounds follow NATIVE_CLEANUP_MS; Aside absence discounted only beside gstack's own browser (review pass 1) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- browse/test/cookie-import-native-job.test.ts | 20 +++++++++++--------- changelog.d/capy-gsta240-followup.md | 4 ++-- test/helpers/third-party-actions.ts | 5 +++-- test/third-party-actions.test.ts | 3 ++- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/browse/test/cookie-import-native-job.test.ts b/browse/test/cookie-import-native-job.test.ts index 0b2ab14f4..c835f3751 100644 --- a/browse/test/cookie-import-native-job.test.ts +++ b/browse/test/cookie-import-native-job.test.ts @@ -334,6 +334,8 @@ afterAll(() => { } }, 15_000); +const NATIVE_SUPERVISOR_MS = 25_000 + NATIVE_CLEANUP_MS; + const request: NativeCookieRequest = { nodeExecutable: 'C:\\fixture\\node.exe', nodeArchitecture: process.arch, @@ -1081,11 +1083,11 @@ function safeLaunchEvidence(file: string): object { } function nativeSupervisor(input: NativeCookieRequest, env: NodeJS.ProcessEnv) { - const cleanupDeadline = performance.now() + 25_000 + NATIVE_CLEANUP_MS; + const cleanupDeadline = performance.now() + NATIVE_SUPERVISOR_MS; const child = ownFixtureChild(spawn(process.execPath, ['--no-env-file', '--no-install', '--no-macros', '--config=NUL', path.resolve(import.meta.dir, '../src/cookie-import-native-worker.ts')], { env, stdio: ['pipe', 'pipe', 'ignore'], windowsHide: true })); let output = ''; child.stdout.on('data', chunk => { output += chunk; }); - const timer = setTimeout(() => child.kill(), 25_000 + NATIVE_CLEANUP_MS); + const timer = setTimeout(() => child.kill(), NATIVE_SUPERVISOR_MS); const done = new Promise((resolve, reject) => { child.once('error', reject); child.once('close', () => { @@ -1183,7 +1185,7 @@ describe('native Windows process qualification', () => { } for (const parent of createdParents.reverse()) rmdirSync(parent); } - }, 65_000); + }, 2 * NATIVE_SUPERVISOR_MS + 5_000); test.skipIf(process.platform !== 'win32')('a locked real Edge profile leaves its existing owner alive', async () => { const node = Bun.which('node'); @@ -1218,7 +1220,7 @@ describe('native Windows process qualification', () => { await contender?.done.catch(() => {}); await owner.done.catch(() => {}); } - }, 65_000); + }, 2 * NATIVE_SUPERVISOR_MS + 5_000); for (const mode of ['normal-close', 'stalled-close']) { test.skipIf(process.platform !== 'win32')(`real Edge synthetic profile: ${mode} returns only after the owned browser exits`, async () => { @@ -1236,7 +1238,7 @@ describe('native Windows process qualification', () => { let output = ''; supervisor.stdout.on('data', chunk => { output += chunk; }); const started = Date.now(); - const deadline = setTimeout(() => supervisor.kill(), 30_000); + const deadline = setTimeout(() => supervisor.kill(), NATIVE_SUPERVISOR_MS); supervisor.stdin.write(JSON.stringify({ ...request, nodeExecutable: node, playwrightEntry, executablePath: edge, userDataDir: path.join(fixture, 'User Data'), deadline: started + 25_000, qualifiedBunVersions: [Bun.version] }) + '\n'); try { await closed; @@ -1250,13 +1252,13 @@ describe('native Windows process qualification', () => { expect(browser.args.some((arg: string) => arg.startsWith('--remote-debugging-port'))).toBe(false); expect(browser.args.some((arg: string) => /^--(?:no-sandbox|disable-setuid-sandbox)(?:=|$)/.test(arg))).toBe(false); expect(alive(browser.pid)).toBe(false); - expect(Date.now() - started).toBeLessThan(30_000); + expect(Date.now() - started).toBeLessThan(NATIVE_SUPERVISOR_MS); } finally { clearTimeout(deadline); supervisor.kill(); await closed; } - }, 35_000); + }, NATIVE_SUPERVISOR_MS + 5_000); } for (const mode of ['timeout', 'owner-exit', 'worker-crash']) { @@ -1637,7 +1639,7 @@ describe('native Windows launch diagnostics', () => { expect(directObservation.observedCommandLine).toMatchObject({ available: true }); expect(containedObservation.folderEvidence.directoriesAfterProbe).toEqual(containedObservation.folderEvidence.directoriesBefore); expect(directObservation.folderEvidence.directoriesAfterProbe).toEqual(directObservation.folderEvidence.directoriesBefore); - }, 65_000); + }, 2 * NATIVE_SUPERVISOR_MS + 5_000); } for (const state of ['preserved', 'fresh']) { @@ -1692,6 +1694,6 @@ describe('native Windows launch diagnostics', () => { expect(secondObservation.observedCommandLine).toMatchObject({ available: true }); expect('error' in second ? second.error : null).not.toBe('native_cleanup_failed'); if (state === 'fresh') expect(firstObservation.folderEvidence.directoriesBefore).toEqual(secondObservation.folderEvidence.directoriesBefore); - }, 65_000); + }, 2 * NATIVE_SUPERVISOR_MS + 5_000); } }); diff --git a/changelog.d/capy-gsta240-followup.md b/changelog.d/capy-gsta240-followup.md index ffce59b29..4d46c5d9a 100644 --- a/changelog.d/capy-gsta240-followup.md +++ b/changelog.d/capy-gsta240-followup.md @@ -1,13 +1,13 @@ **Native cookie import now waits for its browser to actually exit before it reports cleanup, and four more flaky tests from the 2026-11-01 flaky pass are repaired.** Windows drops a Job's process count to zero while terminated Edge processes are still shutting down and still hold the profile's files, so cleanup that trusted the count returned early and the next profile reset failed with a sharing violation. ### Fixed -- **`browse/src/cookie-import-native-job.ts`, `browse/src/cookie-import-native-worker.ts`, `browse/src/cookie-import-native.ts`**: the supervisor holds a handle to every process it sees in the import's Job and counts a process as active until that handle signals exit, not only until the Job's counter drops. The cleanup budget is now 20 s (was 5 s), because terminated Edge took up to 17.8 s to exit on a loaded Windows runner and 2 to 57 ms idle; an idle import returns as soon as its processes are gone. Cleanup that still cannot confirm the exit reports `native_cleanup_failed`, as before. +- **`browse/src/cookie-import-native-job.ts`, `browse/src/cookie-import-native-worker.ts`, `browse/src/cookie-import-native.ts`**: the supervisor holds a handle to every process it sees in the import's Job and counts a process as active until that handle signals exit, not only until the Job's counter drops. The cleanup budget is now 20 s (was 5 s), because terminated Edge took up to 17.8 s to exit on a loaded Windows runner and 2 to 57 ms idle; an idle import returns as soon as its processes are gone. Cleanup that still cannot confirm the exit reports `native_cleanup_failed`, as before. The real-Edge tests take their kill timers and time limits from the same constant. - **`test/skill-e2e-autoplan-guard-pty.test.ts`**: the live actor is Sonnet 5.5 instead of Haiku 4.5. Haiku rewrote the 64-character content hash when it retyped the Phase 2 dispatch prompt, so the publication guard correctly refused a prompt the actor had changed. The guard, the case kind and its single trial are unchanged. - **`.gstack/pregate.json`**: the local pregate lane runs `bun test --timeout=30000 {files}`, the 30 s per-test timeout CI and the remote pregate already use, so a case that takes 5 s on a busy machine no longer fails only locally. - **`test/gstack-ship-receipt.test.ts`**: the full `write` case, about 62 git calls, gets a 15 s timeout instead of bun's 5 s default. - **`test/helpers/coverage-audit-evidence.ts`**: the plan-eng coverage audit detector credits complete reads proved by a literal `wc -c` byte count, and by a bounded display (`grep | head`) when exactly one copy of the reads follows; an unbounded display, a second copy or a tampered read still gets no credit (train gates 41209768188, 41238366045). -- **`test/helpers/third-party-actions.ts`**: a statement that Aside is not available ("Recommended, since Aside isn't available") no longer counts as offering an Aside drive in the tpa-broken check; any other Aside mention in the option still does (gate 41246728073). +- **`test/helpers/third-party-actions.ts`**: a statement that Aside is not available ("Recommended, since Aside isn't available") on an option that names gstack's own browser no longer counts as offering an Aside drive in the tpa-broken check; any other Aside mention still does, and so does an absence clause followed by an offer to launch and drive Aside (gate 41246728073). ### Changed - `test/helpers/docsync-contract.ts` names the missing `documentation_section` when a document-release completion fails the contract, so the next paid red says which field was absent. diff --git a/test/helpers/third-party-actions.ts b/test/helpers/third-party-actions.ts index 8b4b25abb..3b878c310 100644 --- a/test/helpers/third-party-actions.ts +++ b/test/helpers/third-party-actions.ts @@ -37,14 +37,15 @@ export function letteredOptions(text: string): string[] { /** * A statement that Aside is absent, the probe's result, names no Aside drive: "(Recommended, since Aside isn't * available)" on the gstack drive option (gate 41246728073). Only Aside as the subject of a negated availability - * predicate is discounted, so any other Aside mention in the option still counts. + * predicate on an option that names gstack's own browser is discounted, so any other Aside mention still counts and + * an absence clause before an offer to launch and drive Aside is still an Aside drive. */ const ASIDE_ABSENCE = /\bAside(?: browser| app)?\s+(?:(?:is|was)(?:n['’]t|\s+not)\s+(?:available|installed|detected|running|ready|reachable)|is\s+unavailable)\b/gi; /** Inspect lettered consent options, not narration that quotes an unavailable option. */ export function asideDriveOptions(text: string): string[] { return letteredOptions(text).filter(option => { - const named = option.replace(ASIDE_ABSENCE, ''); + const named = /\bgstack['’]s own\b/i.test(option) ? option.replace(ASIDE_ABSENCE, '') : option; const currentOffer = named.replace( /\b(?:I|we)(?:['’]ll| will) (?:re[- ]?ask|ask again)(?: (?:you|this question))? with (?:the )?(?:"[^"]+"|“[^”]+”|[\w -]+?) option(?: included)?\b/gi, '', diff --git a/test/third-party-actions.test.ts b/test/third-party-actions.test.ts index 01c5e6caf..92771a847 100644 --- a/test/third-party-actions.test.ts +++ b/test/third-party-actions.test.ts @@ -634,7 +634,8 @@ describe('consent offer extraction: an Aside-absence reason on the gstack drive for (const option of ["A) I drive it in your Aside browser, even though Aside isn't running yet.", "B) I drive it in gstack's own browser, or in Aside once Aside is not busy.", "B) Since Aside isn't available, I drive it in Aside's fallback window.", "A) Aside isn't available yet, so open it; then I drive it in your Aside browser.", "A) I drive it in Aside; gstack isn't available.", - "B) I drive it in gstack's own browser and switch to Aside if it's running."]) + "B) I drive it in gstack's own browser and switch to Aside if it's running.", + "A) Aside isn't running, so I'll launch it and drive it there.", "A) Aside app is not running yet; I'll start it and drive the dashboard for you."]) expect(asideDriveOptions(option), option).toHaveLength(1); }); });