From b8f72cd4de0692c9503e6b45920d2b1dfcb2562c Mon Sep 17 00:00:00 2001 From: garrytan Date: Sun, 1 Nov 2026 01:08:26 +0000 Subject: [PATCH] test(clean): tests that expect tmp removal see a quiet ps, so a codex elsewhere on the host cannot flake them Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- test/gstack-clean.test.ts | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/test/gstack-clean.test.ts b/test/gstack-clean.test.ts index 3f47d8a83..094110fe8 100644 --- a/test/gstack-clean.test.ts +++ b/test/gstack-clean.test.ts @@ -59,6 +59,8 @@ function fixture(): Fixture { const gs = path.join(repo, '.gstack'); fs.mkdirSync(path.join(gs, 'tmp'), { recursive: true }); inRoot(root, gs); + fs.mkdirSync(path.join(root, 'quiet-ps')); + fs.writeFileSync(path.join(root, 'quiet-ps', 'ps'), '#!/bin/sh\nexit 0\n', { mode: 0o755 }); return { root, home, state, repo, gs }; } @@ -87,8 +89,13 @@ function deadPid(): number { return r.pid!; } -function run(f: Fixture, args: string[], opts: { cwd?: string; env?: Record } = {}) { - const env: Record = { ...process.env, HOME: f.home, GSTACK_HOME: f.state, ...opts.env }; +/** A ps that lists no process, so an unrelated codex elsewhere on the host cannot skip tmp/; realPs keeps the host's. */ +function quietPsPath(f: Fixture): string { + return path.join(f.root, 'quiet-ps') + path.delimiter + (process.env.PATH ?? ''); +} + +function run(f: Fixture, args: string[], opts: { cwd?: string; env?: Record; realPs?: boolean } = {}) { + const env: Record = { ...process.env, HOME: f.home, GSTACK_HOME: f.state, ...(opts.realPs ? {} : { PATH: quietPsPath(f) }), ...opts.env }; for (const name of ['GSTACK_STATE_ROOT', 'GSTACK_STATE_DIR', 'CLAUDE_PLUGIN_DATA', 'BROWSE_STATE_FILE']) if (!(opts.env && name in opts.env)) delete env[name]; for (const [k, v] of Object.entries(env)) if (v === undefined) delete env[k]; const r = spawnSync(process.execPath, [...bunIsolationArgs(), BIN, ...args], { cwd: opts.cwd ?? f.repo, env: env as NodeJS.ProcessEnv, encoding: 'utf8', timeout: 30_000 }); @@ -323,7 +330,7 @@ describe('gstack-clean', () => { const child = spawn(fake, [], { stdio: 'ignore' }); try { await Bun.sleep(300); - const r = run(f, ['--apply']); + const r = run(f, ['--apply'], { realPs: true }); expect(r.code).toBe(0); expect(r.out).toMatch(/skip +tmp\/codex-err-old: codex is running/); expect(fs.existsSync(path.join(f.gs, 'tmp', 'codex-err-old'))).toBe(true); @@ -433,6 +440,23 @@ describe('gstack-clean', () => { } }); + test.skipIf(process.platform === 'win32')('tmp removal is unaffected by a codex process elsewhere on the host', async () => { + const f = fixture(); + put(path.join(f.gs, 'tmp', 'codex-err-old'), 'x', 30 * DAY); + const fake = path.join(f.root, 'fakebin', 'codex'); + put(fake, '#!/bin/sh\nsleep 30\n', 0); + fs.chmodSync(fake, 0o755); + const child = spawn(fake, [], { stdio: 'ignore' }); + try { + await Bun.sleep(300); + const r = run(f, ['--apply']); + expect(r.code).toBe(0); + expect(fs.existsSync(path.join(f.gs, 'tmp', 'codex-err-old'))).toBe(false); + } finally { + child.kill('SIGKILL'); + } + }); + describe('exit codes', () => { test('2: an unknown flag or a bad --audit-days quotes the value; the last line is usage', () => { const f = fixture();