mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
fix(examples): run coverage subprocesses from built libs
This commit is contained in:
@@ -33,6 +33,8 @@ import { resolveExampleLaunch } from '@deepseek-ai/dsh-loader-smoke'
|
||||
export interface AgentUnderTest {
|
||||
/** The agent bin's SOURCE entry (e.g. `packages/examples/acp-demo/src/bin.ts`); the `lib` bin is derived from it. */
|
||||
binScript: string
|
||||
/** Explicit plain-Node entry for `lib` mode; intended for test fixtures outside a package `src/` tree. */
|
||||
libBinScript?: string | undefined
|
||||
/**
|
||||
* The example's live `cordis.yml`. Under `DSH_SNAPSHOT=replay` the bin swaps
|
||||
* it for the sibling `cordis.snapshot.yml` (the keyless replay overlay), so
|
||||
@@ -181,6 +183,7 @@ export async function runScenario(input: InputScript, opts: RunOptions): Promise
|
||||
// Node, resolving plugins through the example's workspace node_modules → lib.
|
||||
const launch = resolveExampleLaunch({
|
||||
srcBin: opts.agent.binScript,
|
||||
libBin: opts.agent.libBinScript,
|
||||
configArgs: ['--config', opts.configPath ?? opts.agent.configPath],
|
||||
tsconfigPath: opts.agent.tsconfigPath,
|
||||
env: {
|
||||
|
||||
@@ -14,10 +14,12 @@ import { runScenario, type AgentUnderTest, type InputStep } from '../src/harness
|
||||
* assertions read plain `rawStdout`.
|
||||
*/
|
||||
|
||||
const fakeAgent = fileURLToPath(new URL('./fixtures/fake-acp-agent.ts', import.meta.url))
|
||||
const AGENT: AgentUnderTest = {
|
||||
binScript: fileURLToPath(new URL('./fixtures/fake-acp-agent.ts', import.meta.url)),
|
||||
binScript: fakeAgent,
|
||||
libBinScript: fakeAgent,
|
||||
// The fake bin ignores its config argv; any real path documents the shape.
|
||||
configPath: fileURLToPath(new URL('./fixtures/fake-acp-agent.ts', import.meta.url)),
|
||||
configPath: fakeAgent,
|
||||
tsconfigPath: fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url)),
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,11 @@ import {
|
||||
* spec once with `ACP_SNAPSHOT_SPEC_BOOTSTRAP=1`, then review and commit the resulting tree.
|
||||
*/
|
||||
|
||||
const fakeAgent = fileURLToPath(new URL('./fixtures/fake-acp-agent.ts', import.meta.url))
|
||||
const AGENT = {
|
||||
binScript: fileURLToPath(new URL('./fixtures/fake-acp-agent.ts', import.meta.url)),
|
||||
configPath: fileURLToPath(new URL('./fixtures/fake-acp-agent.ts', import.meta.url)),
|
||||
binScript: fakeAgent,
|
||||
libBinScript: fakeAgent,
|
||||
configPath: fakeAgent,
|
||||
tsconfigPath: fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url)),
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import { spawn } from 'node:child_process'
|
||||
import { mkdtemp, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const DEFAULT_PROCESS_TIMEOUT_MS = 30_000
|
||||
|
||||
@@ -54,6 +53,8 @@ export function resolveExampleMode(raw: string | undefined = process.env[EXAMPLE
|
||||
export interface ExampleLaunchOptions {
|
||||
/** Absolute path to the example bin's TypeScript source entry (`<pkg>/src/bin.ts`); the `lib` bin is derived from it. */
|
||||
readonly srcBin: string
|
||||
/** Explicit plain-Node entry for `lib` mode; test fixtures may point this at Node-type-strippable TypeScript. */
|
||||
readonly libBin?: string | undefined
|
||||
/** Arguments passed after the bin — the config, positional (`[configPath]`) or flagged (`['--config', configPath]`). */
|
||||
readonly configArgs?: readonly string[]
|
||||
/** The mode to launch in; defaults to {@link resolveExampleMode} of the environment. */
|
||||
@@ -78,11 +79,14 @@ export interface ExampleLaunch {
|
||||
|
||||
/** Derive the built-lib bin (`<pkg>/lib/<name>.js`) from a source bin (`<pkg>/src/<name>.ts`). */
|
||||
function toLibBin(srcBin: string): string {
|
||||
const marker = '/src/'
|
||||
const cut = srcBin.lastIndexOf(marker)
|
||||
if (cut === -1) throw new Error(`resolveExampleLaunch: expected a "/src/" segment in bin path ${JSON.stringify(srcBin)}.`)
|
||||
const tail = srcBin.slice(cut + marker.length).replace(/\.ts$/, '.js')
|
||||
return `${srcBin.slice(0, cut)}/lib/${tail}`
|
||||
const markerLength = '/src/'.length
|
||||
const cut = Math.max(srcBin.lastIndexOf('/src/'), srcBin.lastIndexOf('\\src\\'))
|
||||
if (cut === -1) {
|
||||
throw new Error(`resolveExampleLaunch: expected a "/src/" segment or Windows equivalent in bin path ${JSON.stringify(srcBin)}.`)
|
||||
}
|
||||
const separator = srcBin.slice(cut, cut + 1)
|
||||
const tail = srcBin.slice(cut + markerLength).replace(/\.ts$/, '.js')
|
||||
return `${srcBin.slice(0, cut)}${separator}lib${separator}${tail}`
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,12 +112,12 @@ export function resolveExampleLaunch(options: ExampleLaunchOptions): ExampleLaun
|
||||
if (options.tsconfigPath === undefined) {
|
||||
throw new Error("resolveExampleLaunch: 'src' mode needs tsconfigPath for the workspace paths map.")
|
||||
}
|
||||
const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
|
||||
const tsxLoader = import.meta.resolve('tsx')
|
||||
env.TSX_TSCONFIG_PATH = options.tsconfigPath
|
||||
return { command: process.execPath, args: [...flags, '--import', tsxLoader, options.srcBin, ...configArgs], env }
|
||||
}
|
||||
|
||||
return { command: process.execPath, args: [...flags, toLibBin(options.srcBin), ...configArgs], env }
|
||||
return { command: process.execPath, args: [...flags, options.libBin ?? toLibBin(options.srcBin), ...configArgs], env }
|
||||
}
|
||||
|
||||
/** Inputs that vary between real-Loader example smokes. */
|
||||
@@ -124,6 +128,8 @@ export interface LoaderSmokeOptions {
|
||||
readonly tempDirPrefix: string
|
||||
/** Absolute stdio-agent bin SOURCE path (`<pkg>/src/bin.ts`); the `lib` bin is derived from it. */
|
||||
readonly binScript: string
|
||||
/** Explicit plain-Node entry for `lib` mode; intended for test fixtures outside a package `src/` tree. */
|
||||
readonly libBinScript?: string | undefined
|
||||
/** Absolute real Loader config path. */
|
||||
readonly configPath: string
|
||||
/** Absolute repo tsconfig path used for unbuilt workspace-package resolution (required in `src` mode). */
|
||||
@@ -158,6 +164,7 @@ export async function runLoaderSmoke(options: LoaderSmokeOptions): Promise<Loade
|
||||
const processTimeoutMs = options.processTimeoutMs ?? DEFAULT_PROCESS_TIMEOUT_MS
|
||||
const launch = resolveExampleLaunch({
|
||||
srcBin: options.binScript,
|
||||
libBin: options.libBinScript,
|
||||
configArgs: [options.configPath],
|
||||
...options.mode !== undefined ? { mode: options.mode } : {},
|
||||
tsconfigPath: options.tsconfigPath,
|
||||
|
||||
@@ -16,7 +16,8 @@ afterEach(() => {
|
||||
|
||||
describe('resolveExampleMode', () => {
|
||||
it('defaults absent/empty/src to src', () => {
|
||||
expect(resolveExampleMode(undefined)).toBe('src')
|
||||
Reflect.deleteProperty(process.env, EXAMPLE_MODE_ENV)
|
||||
expect(resolveExampleMode()).toBe('src')
|
||||
expect(resolveExampleMode('')).toBe('src')
|
||||
expect(resolveExampleMode('src')).toBe('src')
|
||||
})
|
||||
@@ -71,6 +72,12 @@ describe('resolveExampleLaunch', () => {
|
||||
expect(env.DSH_HOME).toBe('/tmp/home')
|
||||
})
|
||||
|
||||
it('lib mode: uses an explicit plain-Node bin when provided', () => {
|
||||
const fixture = '/repo/packages/support/loader-smoke/tests/fixture.ts'
|
||||
const { args } = resolveExampleLaunch({ srcBin: fixture, libBin: fixture, mode: 'lib' })
|
||||
expect(args).toContain(fixture)
|
||||
})
|
||||
|
||||
it('prepends --expose-internals when requested', () => {
|
||||
const { args } = resolveExampleLaunch({ srcBin: SRC_BIN, mode: 'lib', exposeInternals: true })
|
||||
expect(args[0]).toBe('--expose-internals')
|
||||
@@ -84,6 +91,14 @@ describe('resolveExampleLaunch', () => {
|
||||
expect(args).toContain('/repo/src/packages/examples/acp-demo/lib/bin.js')
|
||||
})
|
||||
|
||||
it('lib mode: derives the built bin from a Windows source path', () => {
|
||||
const { args } = resolveExampleLaunch({
|
||||
srcBin: String.raw`D:\repo\src\packages\examples\acp-demo\src\bin.ts`,
|
||||
mode: 'lib',
|
||||
})
|
||||
expect(args).toContain(String.raw`D:\repo\src\packages\examples\acp-demo\lib\bin.js`)
|
||||
})
|
||||
|
||||
it('lib mode: throws when the bin has no /src/ segment', () => {
|
||||
expect(() => resolveExampleLaunch({ srcBin: '/repo/lib/bin.js', mode: 'lib' })).toThrow(/"\/src\/" segment/)
|
||||
})
|
||||
|
||||
@@ -44,6 +44,7 @@ describe('runLoaderSmoke', () => {
|
||||
label: 'failure fixture',
|
||||
tempDirPrefix: 'loader-smoke-fail-',
|
||||
binScript: fixture('fail'),
|
||||
libBinScript: fixture('fail'),
|
||||
configPath,
|
||||
tsconfigPath,
|
||||
})).rejects.toThrow('failure fixture exited 7. stdout:\n\nstderr:\nfixture failed')
|
||||
@@ -54,6 +55,7 @@ describe('runLoaderSmoke', () => {
|
||||
label: 'hanging fixture',
|
||||
tempDirPrefix: 'loader-smoke-hang-',
|
||||
binScript: fixture('hang'),
|
||||
libBinScript: fixture('hang'),
|
||||
configPath,
|
||||
tsconfigPath,
|
||||
processTimeoutMs: 100,
|
||||
|
||||
@@ -163,6 +163,7 @@ function gatesForMode(selected: Mode): Gate[] {
|
||||
]
|
||||
case 'ci-coverage':
|
||||
return [
|
||||
pnpmScript('build', 'build'),
|
||||
coverageGate(),
|
||||
]
|
||||
case 'ci-snapshot':
|
||||
@@ -282,6 +283,8 @@ function coverageGate(): Gate {
|
||||
...positiveIntArg('DSH_COVERAGE_MAX_WORKERS', '--maxWorkers'),
|
||||
], {
|
||||
label: 'test:coverage',
|
||||
env: { DSH_EXAMPLE_MODE: 'lib' },
|
||||
needs: ['build'],
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user