From d65e9bed9f9eea4ee7511127d013f4a82aedf82e Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sun, 11 Oct 2026 14:01:07 -0700 Subject: [PATCH] [CI] Improve test clocks and dependency fixtures (#27574) --- .../scripts/process-host-build-dist.test.mjs | 41 +++++++++++-------- .../codex/codex-app-server-connection.test.ts | 23 ++++++----- .../daemon/daemon-init-dependency-mocks.ts | 19 +++++---- ...d-agent-session-claude-queued-stop.test.ts | 13 ++++-- 4 files changed, 59 insertions(+), 37 deletions(-) diff --git a/config/scripts/process-host-build-dist.test.mjs b/config/scripts/process-host-build-dist.test.mjs index 3269992a3f6..c79b912606d 100644 --- a/config/scripts/process-host-build-dist.test.mjs +++ b/config/scripts/process-host-build-dist.test.mjs @@ -1,5 +1,6 @@ import { copyFileSync, + cpSync, existsSync, mkdirSync, mkdtempSync, @@ -17,7 +18,7 @@ import { tmpdir } from 'node:os' import { dirname, join, resolve } from 'node:path' import { pathToFileURL } from 'node:url' import { runProcess } from '@orca/process-host' -import { afterEach, describe, expect, it } from 'vitest' +import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest' const realPackageDir = resolve('src/packages/process-host') const buildScript = join(realPackageDir, 'scripts', 'build-dist.mjs') @@ -26,6 +27,8 @@ const tsxCli = packageRequire.resolve('tsx/cli') const realCompilerDir = dirname(packageRequire.resolve('typescript/package.json')) const nodeExecutable = process.env.ORCA_TEST_NODE_EXECUTABLE ?? process.execPath const temporaryRoots = [] +let seededDist +let releaseSeed afterEach(() => { for (const root of temporaryRoots.splice(0)) { @@ -33,7 +36,7 @@ afterEach(() => { } }) -function fixture(onTestFinished) { +function fixture(onTestFinished, distSeed) { const root = mkdtempSync(join(tmpdir(), 'orca-process-host-build-')) if (onTestFinished) { onTestFinished(() => rmSync(root, { recursive: true, force: true })) @@ -66,6 +69,9 @@ function fixture(onTestFinished) { ) writeFileSync(join(packageDir, 'src', 'entry.ts'), "export { value } from './internal'\n") writeFileSync(join(packageDir, 'src', 'internal.ts'), 'export const value = 1\n') + if (distSeed) { + cpSync(distSeed, join(packageDir, 'dist'), { recursive: true, preserveTimestamps: true }) + } return packageDir } @@ -121,6 +127,16 @@ function leftovers(packageDir) { return readdirSync(packageDir).filter((name) => name.startsWith('.dist-')) } +beforeAll(async () => { + const packageDir = fixture((release) => { + releaseSeed = release + }) + await build(packageDir) + seededDist = join(packageDir, 'dist') +}) + +afterAll(() => releaseSeed?.()) + describe('process-host dist build', () => { it('leaves an up-to-date dist untouched on repeated builds', async () => { const packageDir = fixture() @@ -135,8 +151,7 @@ describe('process-host dist build', () => { }) it('rewrites only changed outputs and keeps source maps pointing at src', async () => { - const packageDir = fixture() - await build(packageDir) + const packageDir = fixture(undefined, seededDist) const before = distSnapshot(packageDir) writeFileSync(join(packageDir, 'src', 'internal.ts'), 'export const value = 2\n') @@ -153,8 +168,7 @@ describe('process-host dist build', () => { }) it('removes outputs of renamed sources', async () => { - const packageDir = fixture() - await build(packageDir) + const packageDir = fixture(undefined, seededDist) renameSync(join(packageDir, 'src', 'internal.ts'), join(packageDir, 'src', 'renamed.ts')) writeFileSync(join(packageDir, 'src', 'entry.ts'), "export { value } from './renamed'\n") @@ -173,8 +187,7 @@ describe('process-host dist build', () => { }) it('preserves the previous dist when compilation fails', async () => { - const packageDir = fixture() - await build(packageDir) + const packageDir = fixture(undefined, seededDist) const before = distSnapshot(packageDir) writeFileSync(join(packageDir, 'src', 'internal.ts'), "export const value: number = 'bad'\n") @@ -187,8 +200,7 @@ describe('process-host dist build', () => { }) it('publishes new dependencies before replacing their importer and removes stale output last', async () => { - const packageDir = fixture() - await build(packageDir) + const packageDir = fixture(undefined, seededDist) renameSync(join(packageDir, 'src', 'internal.ts'), join(packageDir, 'src', 'z-new.ts')) writeFileSync(join(packageDir, 'src', 'z-new.ts'), 'export const value = 2\n') writeFileSync(join(packageDir, 'src', 'entry.ts'), "export { value } from './z-new'\n") @@ -230,8 +242,7 @@ await buildPackageDist(process.argv[2]) }) it('keeps dist loadable while concurrent builds and a reader run', async () => { - const packageDir = fixture() - await build(packageDir) + const packageDir = fixture(undefined, seededDist) writeFileSync( join(packageDir, 'src', 'internal.ts'), `export const value = ${'2 + '.repeat(10_000)}2\n` @@ -269,8 +280,7 @@ while (!existsSync(${JSON.stringify(finished)}) || reads === 0) { }) it('serializes builds so a later source snapshot is the one published', async () => { - const packageDir = fixture() - await build(packageDir) + const packageDir = fixture(undefined, seededDist) // Hold the build lock while the source changes, as an in-flight earlier build would. mkdirSync(join(packageDir, '.dist-build.lock')) const waiting = runBuild(packageDir) @@ -289,8 +299,7 @@ while (!existsSync(${JSON.stringify(finished)}) || reads === 0) { 'fails without publishing when the held build lock is %s', { timeout: 30_000 }, async ([_label, recreateLock], { expect: expectResult, onTestFinished }) => { - const packageDir = fixture(onTestFinished) - await build(packageDir, expectResult) + const packageDir = fixture(onTestFinished, seededDist) const before = distSnapshot(packageDir) const entry = readFileSync(join(packageDir, 'dist', 'entry.js'), 'utf8') installLockStealingCompiler(packageDir, { recreateLock }) diff --git a/src/main/codex/codex-app-server-connection.test.ts b/src/main/codex/codex-app-server-connection.test.ts index afe50c954bc..3f90182acd4 100644 --- a/src/main/codex/codex-app-server-connection.test.ts +++ b/src/main/codex/codex-app-server-connection.test.ts @@ -573,7 +573,7 @@ describe('openCodexAppServerConnection', () => { // A root that outlived one kill is killed again by the next ask, which then proves it gone. it('kills the root again on a close after an unproven attempt', async () => { - vi.useFakeTimers() + vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }) const { child, spawnImpl } = stubChild({ exitOnStdinEnd: false }) answerInitialize(child) const connection = await openCodexAppServerConnection( @@ -582,18 +582,21 @@ describe('openCodexAppServerConnection', () => { spawnImpl ) - const first = connection.close() - await vi.advanceTimersByTimeAsync(GRACEFUL_EXIT_MS + 3_500) - await expect(first).resolves.toBe(false) - child.kill.mockImplementation((signal) => { - if (signal === 'SIGKILL') { - setTimeout(() => child.emit('exit', null, 'SIGKILL'), 10) - } - return true + await expect(closeWithoutObservedExit(() => connection.close(), child)).resolves.toBe(false) + const secondForcedKill = new Promise((resolve) => { + child.kill.mockImplementation((signal) => { + if (signal === 'SIGKILL') { + setTimeout(() => child.emit('exit', null, 'SIGKILL'), 10) + resolve() + } + return true + }) }) const second = connection.close() - await vi.advanceTimersByTimeAsync(GRACEFUL_EXIT_MS + 3_500) + await flushStreams().then(() => vi.advanceTimersByTimeAsync(GRACEFUL_EXIT_MS)) + await secondForcedKill + await vi.advanceTimersByTimeAsync(10) await expect(second).resolves.toBe(true) expect(child.kill.mock.calls.filter(([signal]) => signal === 'SIGKILL')).toHaveLength(2) }) diff --git a/src/main/daemon/daemon-init-dependency-mocks.ts b/src/main/daemon/daemon-init-dependency-mocks.ts index d7217d4df63..a76278a7a79 100644 --- a/src/main/daemon/daemon-init-dependency-mocks.ts +++ b/src/main/daemon/daemon-init-dependency-mocks.ts @@ -1,4 +1,5 @@ import { vi } from 'vitest' +import type * as NodeFs from 'node:fs' import { PROTOCOL_VERSION } from './types' import type { Mock } from 'vitest' import type { @@ -162,13 +163,17 @@ export function createDaemonInitModuleFactories(state: DaemonInitMockState) { } return { - fs: () => ({ - mkdirSync: vi.fn<(...args: unknown[]) => void>(), - existsSync: (p: string) => probeSocketExistsMock(p) || p.includes('.pid'), - unlinkSync: unlinkSyncMock, - readFileSync: readFileSyncMock, - writeFileSync: writeFileSyncMock - }), + fs: async () => { + const actual = await vi.importActual('node:fs') + return { + constants: actual.constants, + mkdirSync: vi.fn<(...args: unknown[]) => void>(), + existsSync: (p: string) => probeSocketExistsMock(p) || p.includes('.pid'), + unlinkSync: unlinkSyncMock, + readFileSync: readFileSyncMock, + writeFileSync: writeFileSyncMock + } + }, net: () => ({ connect: netConnectMock }), diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-claude-queued-stop.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-claude-queued-stop.test.ts index e6e2524841e..d08b6fff57f 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-claude-queued-stop.test.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-claude-queued-stop.test.ts @@ -46,6 +46,7 @@ let queued: string[] let claude: ReturnType beforeEach(async () => { + vi.useFakeTimers() root = await mkdtemp(join(tmpdir(), 'orca-claude-queued-stop-')) resetHostTestOperationIds() queued = [] @@ -107,9 +108,13 @@ beforeEach(async () => { }) afterEach(async () => { - await adapter.closeAll() - await host.flushAllStreamedEvents() - await rm(root, { recursive: true, force: true }) + try { + await adapter.closeAll() + await host.flushAllStreamedEvents() + await rm(root, { recursive: true, force: true }) + } finally { + vi.useRealTimers() + } }) function eventually(assertion: () => unknown): Promise { @@ -368,7 +373,7 @@ it('a card sent now into the running turn comes back paused when Stop withdraws }, 5_000) // A drain ignoring the pause re-sends only after the stopped turn ends: watch past that. await eventually(async () => expect(await liveTurnId()).toBeNull()) - await new Promise((resolve) => setTimeout(resolve, 2_500)) + await vi.advanceTimersByTimeAsync(2_500) expect(connection.sent).toHaveLength(2) expect(await sends()).toEqual([{ state: 'rejected', reason: DISPATCH_REJECTED_CANCELLED }]) }, 20_000)