[CI] Improve test clocks and dependency fixtures (#27574)

This commit is contained in:
Neil
2026-10-11 14:01:07 -07:00
committed by GitHub
parent 8e536f9514
commit d65e9bed9f
4 changed files with 59 additions and 37 deletions
+25 -16
View File
@@ -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 })
@@ -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<void>((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)
})
@@ -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<typeof NodeFs>('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
}),
@@ -46,6 +46,7 @@ let queued: string[]
let claude: ReturnType<typeof fakeClaude>
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<unknown> {
@@ -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)