test: preserve ACP cleanup failures
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { mkdtemp, rm, readFile } from 'node:fs/promises'
|
import { mkdtemp, readFile } from 'node:fs/promises'
|
||||||
import { tmpdir } from 'node:os'
|
import { tmpdir } from 'node:os'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
type AgentUnderTest,
|
type AgentUnderTest,
|
||||||
type LaunchedAcpTestAgent,
|
type LaunchedAcpTestAgent,
|
||||||
} from '@deepseek-ai/dsh-acp-snapshot'
|
} from '@deepseek-ai/dsh-acp-snapshot'
|
||||||
|
import { cleanupAcpExampleTest } from './cleanup.ts'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* End-to-end: boot examples/acp-agent as a real subprocess speaking ACP over
|
* End-to-end: boot examples/acp-agent as a real subprocess speaking ACP over
|
||||||
@@ -31,16 +32,11 @@ let spawned: LaunchedAcpTestAgent | undefined
|
|||||||
let workdir: string | undefined
|
let workdir: string | undefined
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
try {
|
const ownedSpawned = spawned
|
||||||
await spawned?.close('SIGKILL')
|
const ownedWorkdir = workdir
|
||||||
} finally {
|
spawned = undefined
|
||||||
spawned = undefined
|
workdir = undefined
|
||||||
try {
|
await cleanupAcpExampleTest(ownedSpawned, ownedWorkdir)
|
||||||
if (workdir !== undefined) await rm(workdir, { recursive: true, force: true })
|
|
||||||
} finally {
|
|
||||||
workdir = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('acp-agent over real stdio (no key required)', () => {
|
describe('acp-agent over real stdio (no key required)', () => {
|
||||||
|
|||||||
38
examples/acp-agent/tests/cleanup.e2e.ts
Normal file
38
examples/acp-agent/tests/cleanup.e2e.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/** Regression coverage for ACP example teardown. */
|
||||||
|
|
||||||
|
import { access, mkdtemp, rm } from 'node:fs/promises'
|
||||||
|
import { tmpdir } from 'node:os'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { cleanupAcpExampleTest } from './cleanup.ts'
|
||||||
|
|
||||||
|
let fallbackWorkdir: string | undefined
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
if (fallbackWorkdir !== undefined) await rm(fallbackWorkdir, { recursive: true, force: true })
|
||||||
|
fallbackWorkdir = undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('cleanupAcpExampleTest', () => {
|
||||||
|
it('removes the workspace after process shutdown fails', async () => {
|
||||||
|
fallbackWorkdir = await mkdtemp(join(tmpdir(), 'acp-cleanup-'))
|
||||||
|
const closeFailure = new Error('close failed')
|
||||||
|
const spawned = { close: vi.fn().mockRejectedValue(closeFailure) }
|
||||||
|
|
||||||
|
await expect(cleanupAcpExampleTest(spawned, fallbackWorkdir))
|
||||||
|
.rejects.toMatchObject({ errors: [closeFailure] })
|
||||||
|
await expect(access(fallbackWorkdir)).rejects.toThrow()
|
||||||
|
fallbackWorkdir = undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
it('reports process and workspace failures together', async () => {
|
||||||
|
const closeFailure = new Error('close failed')
|
||||||
|
const spawned = { close: vi.fn().mockRejectedValue(closeFailure) }
|
||||||
|
|
||||||
|
const failure = await cleanupAcpExampleTest(spawned, '\0').catch((error: unknown) => error)
|
||||||
|
|
||||||
|
expect(failure).toBeInstanceOf(AggregateError)
|
||||||
|
expect((failure as AggregateError).errors).toHaveLength(2)
|
||||||
|
expect((failure as AggregateError).errors[0]).toBe(closeFailure)
|
||||||
|
})
|
||||||
|
})
|
||||||
23
examples/acp-agent/tests/cleanup.ts
Normal file
23
examples/acp-agent/tests/cleanup.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/** Shared teardown for ACP example tests. */
|
||||||
|
|
||||||
|
import { rm } from 'node:fs/promises'
|
||||||
|
import type { LaunchedAcpTestAgent } from '@deepseek-ai/dsh-acp-snapshot'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the test agent, then remove its workspace, attempting both operations
|
||||||
|
* and reporting every failure instead of allowing the later one to mask the
|
||||||
|
* earlier one.
|
||||||
|
*/
|
||||||
|
export async function cleanupAcpExampleTest(
|
||||||
|
spawned: Pick<LaunchedAcpTestAgent, 'close'> | undefined,
|
||||||
|
workdir: string | undefined,
|
||||||
|
): Promise<void> {
|
||||||
|
const results: PromiseSettledResult<unknown>[] = []
|
||||||
|
if (spawned !== undefined) results.push(...await Promise.allSettled([spawned.close('SIGKILL')]))
|
||||||
|
if (workdir !== undefined) results.push(...await Promise.allSettled([rm(workdir, { recursive: true, force: true })]))
|
||||||
|
|
||||||
|
const failures = results
|
||||||
|
.filter((result): result is PromiseRejectedResult => result.status === 'rejected')
|
||||||
|
.map(result => result.reason as unknown)
|
||||||
|
if (failures.length > 0) throw new AggregateError(failures, 'ACP example cleanup failed')
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { spawnSync } from 'node:child_process'
|
import { spawnSync } from 'node:child_process'
|
||||||
import { mkdtemp, readFile, rm } from 'node:fs/promises'
|
import { mkdtemp, readFile } from 'node:fs/promises'
|
||||||
import { tmpdir } from 'node:os'
|
import { tmpdir } from 'node:os'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
type AgentUnderTest,
|
type AgentUnderTest,
|
||||||
type LaunchedAcpTestAgent,
|
type LaunchedAcpTestAgent,
|
||||||
} from '@deepseek-ai/dsh-acp-snapshot'
|
} from '@deepseek-ai/dsh-acp-snapshot'
|
||||||
|
import { cleanupAcpExampleTest } from './cleanup.ts'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default ACP composition (`cordis.yml`) end to end.
|
* The default ACP composition (`cordis.yml`) end to end.
|
||||||
@@ -81,16 +82,11 @@ let spawned: Spawned | undefined
|
|||||||
let workdir: string | undefined
|
let workdir: string | undefined
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
try {
|
const ownedSpawned = spawned
|
||||||
await spawned?.close('SIGKILL')
|
const ownedWorkdir = workdir
|
||||||
} finally {
|
spawned = undefined
|
||||||
spawned = undefined
|
workdir = undefined
|
||||||
try {
|
await cleanupAcpExampleTest(ownedSpawned, ownedWorkdir)
|
||||||
if (workdir !== undefined) await rm(workdir, { recursive: true, force: true })
|
|
||||||
} finally {
|
|
||||||
workdir = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('default sandbox composition keyless smoke (real cordis.yml via the Loader)', () => {
|
describe('default sandbox composition keyless smoke (real cordis.yml via the Loader)', () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { mkdtemp, rm, writeFile, access } from 'node:fs/promises'
|
import { mkdtemp, writeFile, access } from 'node:fs/promises'
|
||||||
import { tmpdir } from 'node:os'
|
import { tmpdir } from 'node:os'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
type AgentUnderTest,
|
type AgentUnderTest,
|
||||||
type LaunchedAcpTestAgent,
|
type LaunchedAcpTestAgent,
|
||||||
} from '@deepseek-ai/dsh-acp-snapshot'
|
} from '@deepseek-ai/dsh-acp-snapshot'
|
||||||
|
import { cleanupAcpExampleTest } from './cleanup.ts'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* With-key e2e: the Claude Code hook bridge running against the REAL acp-agent
|
* With-key e2e: the Claude Code hook bridge running against the REAL acp-agent
|
||||||
@@ -38,16 +39,11 @@ let spawned: LaunchedAcpTestAgent | undefined
|
|||||||
let workdir: string | undefined
|
let workdir: string | undefined
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
try {
|
const ownedSpawned = spawned
|
||||||
await spawned?.close('SIGKILL')
|
const ownedWorkdir = workdir
|
||||||
} finally {
|
spawned = undefined
|
||||||
spawned = undefined
|
workdir = undefined
|
||||||
try {
|
await cleanupAcpExampleTest(ownedSpawned, ownedWorkdir)
|
||||||
if (workdir !== undefined) await rm(workdir, { recursive: true, force: true })
|
|
||||||
} finally {
|
|
||||||
workdir = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.skipIf(!process.env.DEEPSEEK_API_KEY)('acp-agent e2e: a PreToolUse hook blocks bash (real model)', () => {
|
describe.skipIf(!process.env.DEEPSEEK_API_KEY)('acp-agent e2e: a PreToolUse hook blocks bash (real model)', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user