mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
[CI] Improve test clocks and dependency fixtures (#27574)
This commit is contained in:
@@ -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
|
||||
}),
|
||||
|
||||
+9
-4
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user