test(windows): close final native coverage gaps

This commit is contained in:
Tianyi Cui
2026-08-09 00:48:46 +08:00
parent d2928584e7
commit 9e724dcad7
11 changed files with 88 additions and 30 deletions
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md
2026-08-08-native-windows-pull-request-ci.md: 5aafb8b9c7557157135dfa6ecedffed42eb76183
2026-08-08-native-windows-pull-request-ci.zh.md: 4c71f3eeda86fdc537c22629962b0eccbb994b65
2026-08-08-native-windows-pull-request-ci.md: 8b657e3bffa7279560f3f87e5c35e7f462f2cac1
2026-08-08-native-windows-pull-request-ci.zh.md: 6f23dd1df1ef31a8952425bdc6ed00a720d162dd
@@ -32,7 +32,9 @@ The final root-probe failure came from applying the extended-length namespace to
The next complete coverage run reached six independent late failures rather than one shared cascade. React queue-action coverage now resolves its mocked request inside an awaited `act()` before observing the settled render. The unclosed-Markdown workload keeps all 6,400 candidates under an explicit three-second coverage budget, and the asynchronous workspace-projection warning test gives its outer case a twenty-second budget larger than its ten-second poll. Real Claude Code teardown uses asynchronous recursive removal with ten bounded retries after every managed handle reports exit, accommodating Windows' delayed handle release without weakening the quiescence assertions.
Two product boundaries required foundation repairs. Include's debounced config persistence previously launched an unobserved promise from a timer; a transient Windows `EPERM` while replacing `cordis.yml` could therefore lose the disabled row and escape as an unhandled rejection. The vendored writer now serializes writes, retries only transient access/busy failures with bounded backoff, observes every rejection, and drains the latest write at teardown; the real Loader composition injects one `EPERM` and proves the durable retry. Codex 0.146 advertised `exec_command` to the loopback Responses model on Windows but rejected the returned call in its own router, the same upstream failure class tracked in [openai/codex#31665](https://github.com/openai/codex/issues/31665). Development evidence is pinned to the current stable 0.147.0 release: regenerated upstream schemas preserve the provider-owned handshake, thread/turn, approval, user-input, and elicitation contract, while the real-product suite again proves command rejection without a side effect and whole-tree exit.
Two product boundaries required foundation repairs. Include's debounced config persistence previously launched an unobserved promise from a timer; a transient Windows `EPERM` while replacing `cordis.yml` could therefore lose the disabled row and escape as an unhandled rejection. The vendored writer now serializes writes, retries only transient access/busy failures with bounded backoff, observes every rejection, and drains the latest write at teardown; the real Loader composition injects one `EPERM` and proves the durable retry. Codex 0.146 advertised `exec_command` to the loopback Responses model on Windows but rejected the returned call in its own router, the same upstream failure class tracked in [openai/codex#31665](https://github.com/openai/codex/issues/31665). Development evidence is pinned to the current stable 0.147.0 release: regenerated upstream schemas preserve the provider-owned handshake, thread/turn, approval, user-input, and elicitation contract. Because Codex can advertise the legacy `shell_command` instead when unified exec is unavailable on the host, the loopback model now selects an advertised command tool and supplies that tool's argument shape rather than injecting `exec_command` unconditionally. The real-product suite therefore proves unattended rejection without a side effect and whole-tree exit through the product's actual default tool inventory on each host.
The subsequent exact hosted run isolated seven other fixture contracts. The PowerShell background-output case now waits for process completion before draining and comparing the final delta, while the pi-ai idle-watchdog case retains a bounded one-second close deadline that accommodates the delayed Windows socket notification. The asynchronous workspace projection seeds its in-memory filesystem at the host-resolved root. The Include retry acceptance asserts the injected failure and eventual persistence rather than an incidental total rename count, which may include another valid serialized write. LSP's bare-command fixture supplies a `.cmd` executable through `PATHEXT` on Windows, and URI rendering expectations distinguish the execution world's path convention from the test host's separators. None of these changes skips a supported path or weakens the asserted outcome.
POSIX mode bits, chmod-based unreadability, and chmod-based writer-lock refusal do not exist as equivalent Windows facilities. Those acceptance cases remain enforced on POSIX and are skipped on Windows; content, atomic replacement, symlink safety, rollback and recovery through platform-independent filesystem conflicts, and native Windows long-path behavior remain covered. No supported product source is excluded from Windows coverage to accommodate these differences.
@@ -32,7 +32,9 @@ Status: implemented
下一次完整覆盖率运行触及的是 6 项相互独立的末端故障,不再是同一问题的连锁结果。React 队列动作覆盖现在会在 awaited `act()` 中解析模拟请求,再观察渲染完成后的状态。未闭合 Markdown 工作负载保留全部 6,400 个候选项,并采用显式的 3 秒覆盖率预算;异步工作区投影告警测试则为外层用例设置 20 秒预算,大于其 10 秒轮询预算。真实 Claude Code 拆卸会在所有受管句柄均报告退出后,采用带 10 次有界重试的异步递归删除,以容纳 Windows 延迟释放句柄的行为,同时不削弱完全停稳断言。
另有两个产品边界需要基础性修复。Include 的防抖配置持久化此前会从计时器启动一个无人观察的 Promise;Windows 在替换 `cordis.yml` 时若瞬时返回 `EPERM`,既可能丢失已禁用行,也会让 rejection 以未处理形式逸出。现在,vendored writer 会串行化写入,只对瞬时的访问/忙碌错误执行有界退避重试,观察每个 rejection,并在拆卸时排空最新写入;真实 Loader 组合测试会注入一次 `EPERM` 并证明持久化重试。Codex 0.146 在 Windows 上会把 `exec_command` 提供给回环 Responses 模型,却在自身路由器中拒绝模型返回的调用;这与 [openai/codex#31665](https://github.com/openai/codex/issues/31665) 跟踪的上游故障属于同一类。开发证据现锁定当前稳定版 0.147.0:重新生成的上游 schema 保留了提供方拥有的握手、线程/轮次、审批、用户输入和 elicitation 契约;真实产品测试则再次证明命令会在不产生副作用的情况下被拒绝,且整棵进程树退出。
另有两个产品边界需要基础性修复。Include 的防抖配置持久化此前会从计时器启动一个无人观察的 Promise;Windows 在替换 `cordis.yml` 时若瞬时返回 `EPERM`,既可能丢失已禁用行,也会让 rejection 以未处理形式逸出。现在,vendored writer 会串行化写入,只对瞬时的访问/忙碌错误执行有界退避重试,观察每个 rejection,并在拆卸时排空最新写入;真实 Loader 组合测试会注入一次 `EPERM` 并证明持久化重试。Codex 0.146 在 Windows 上会把 `exec_command` 提供给回环 Responses 模型,却在自身路由器中拒绝模型返回的调用;这与 [openai/codex#31665](https://github.com/openai/codex/issues/31665) 跟踪的上游故障属于同一类。开发证据现锁定当前稳定版 0.147.0:重新生成的上游 schema 保留了提供方拥有的握手、线程/轮次、审批、用户输入和 elicitation 契约。当宿主无法使用 unified exec 时,Codex 可能改为提供旧版 `shell_command`;因此,回环模型现在会选择实际提供的命令工具,并使用该工具对应的参数形态,而不再无条件注入 `exec_command`。真实产品测试由此会通过各宿主的实际默认工具清单,证明无人值守拒绝不产生副作用,且整棵进程树退出。
随后的分支头精确托管运行又隔离出另外 7 项 fixture 契约。PowerShell 后台输出场景现在会等待进程完成,再排空并比较最后一段增量;pi-ai 空闲 watchdog 场景则保留 1 秒的有界关闭期限,以容纳 Windows 延迟送达的 socket 通知。异步工作区投影会在宿主解析后的根目录上填充内存文件系统。Include 重试验收现在断言注入的故障与最终持久化结果,而不再断言可能包含另一项合法串行写入的偶然 rename 总次数。LSP 的裸命令 fixture 会在 Windows 上通过 `PATHEXT` 提供 `.cmd` 可执行文件,URI 渲染预期也会区分执行环境的路径约定与测试宿主的分隔符。上述修改既没有跳过受支持路径,也没有削弱结果断言。
POSIX 模式位、基于 chmod 的不可读状态和基于 chmod 的 writer lock 拒绝在 Windows 上没有等价机制。这些验收场景继续在 POSIX 上强制执行,并在 Windows 上跳过;内容、原子替换、符号链接安全、通过平台无关文件系统冲突验证的回滚与恢复,以及原生 Windows 长路径行为仍保有覆盖。没有任何受支持的产品源码为适应这些差异而从 Windows 覆盖率中排除。
@@ -314,9 +314,10 @@ describe.skipIf(!hasPwsh)('PwshLocalExecutor.start (background process handles)'
env: { BG_VAR: 'bg-env' },
dshEnv: { DSH_BG_VAR: 'bg-dsh-env' },
}))
const output = await readUntil(proc, '[bg-env][bg-dsh-env]')
expect(output).toBe('bg-stdin\n[bg-env][bg-dsh-env]\n')
const partialOutput = await readUntil(proc, '[bg-env][bg-dsh-env]')
await proc.done
const output = partialOutput + lf(proc.readOutput().delta)
expect(output).toBe('bg-stdin\n[bg-env][bg-dsh-env]\n')
expect(proc.exitCode).toBe(0)
})
@@ -4113,11 +4113,12 @@ describe('dynamic nested workspace context injection', () => {
await ctx.plugin(RecordingFileSystem)
await ctx.plugin(workspaceContext, { maxBytes: 65536 })
const fs = ctx.fs as RecordingFileSystem
const agent = stubAgent('/')
const root = resolve('/')
const agent = stubAgent(root)
const failure = new Error('projection failed')
const warn = vi.spyOn(ctx.logger, 'warn').mockImplementation(() => undefined)
fs.entries.set('/.git', { type: 'directory' })
fs.entries.set('/AGENTS.md', { type: 'file', content: 'workspace rule' })
fs.entries.set(join(root, '.git'), { type: 'directory' })
fs.entries.set(join(root, 'AGENTS.md'), { type: 'file', content: 'workspace rule' })
vi.spyOn(agent.inbox, 'prepend').mockImplementationOnce(() => { throw failure })
ctx.emit('tools/result', stubToolExecution({
@@ -22,7 +22,7 @@ import BrowseDirectoryPicker from '@deepseek-ai/dsh-host-directory-picker-browse
import NativeDirectoryPicker from '@deepseek-ai/dsh-host-directory-picker-native'
import * as DirectoryPickerAuto from '../src/index.ts'
const renameControl = vi.hoisted(() => ({ attempts: 0, remainingFailures: 0 }))
const renameControl = vi.hoisted(() => ({ attempts: 0, injectedFailures: 0, remainingFailures: 0 }))
vi.mock('node:fs/promises', async (importOriginal) => {
const actual = await importOriginal<typeof import('node:fs/promises')>()
@@ -32,6 +32,7 @@ vi.mock('node:fs/promises', async (importOriginal) => {
renameControl.attempts++
if (renameControl.remainingFailures > 0) {
renameControl.remainingFailures--
renameControl.injectedFailures++
throw Object.assign(new Error(`transient rename failure for ${newPath}`), { code: 'EPERM' })
}
await actual.rename(oldPath, newPath)
@@ -59,6 +60,7 @@ afterEach(async () => {
root = undefined
fakeBin = undefined
renameControl.attempts = 0
renameControl.injectedFailures = 0
renameControl.remainingFailures = 0
})
@@ -187,6 +189,8 @@ describe('real Loader composition', () => {
expect(entryNames(ctx)).not.toContain(NATIVE)
// Same self-dispose persistence as above: let the write land before teardown.
await expect.poll(async () => await readFile(configPath, 'utf8')).toContain('disabled: true')
expect(renameControl.attempts).toBe(2)
expect(renameControl.injectedFailures).toBe(1)
expect(renameControl.remainingFailures).toBe(0)
expect(renameControl.attempts).toBeGreaterThanOrEqual(2)
})
})
+1 -1
View File
@@ -272,7 +272,7 @@ describe('PiAiAdapter provider routing', () => {
await Promise.race([
server.responseClosed,
new Promise<never>((_resolve, reject) => {
setTimeout(() => { reject(new Error('SDK request did not close after idle timeout')) }, 100)
setTimeout(() => { reject(new Error('SDK request did not close after idle timeout')) }, 1_000)
}),
])
@@ -38,9 +38,9 @@ describe('lsp-local provider resolution', () => {
// A tiny executable script placed on a custom PATH dir: the load-time resolver must find it.
const bin = join(root, 'bin')
await mkdir(bin)
const exe = join(bin, 'fake-lsp')
await writeFile(exe, '#!/bin/sh\nexit 0\n')
await chmod(exe, 0o755)
const exe = join(bin, process.platform === 'win32' ? 'fake-lsp.cmd' : 'fake-lsp')
await writeFile(exe, process.platform === 'win32' ? '@exit /b 0\r\n' : '#!/bin/sh\nexit 0\n')
if (process.platform !== 'win32') await chmod(exe, 0o755)
const ctx = new Context()
await ctx.plugin(Lsp)
@@ -49,7 +49,7 @@ describe('lsp-local provider resolution', () => {
await expect(ctx.plugin(LspLocal, config('onpath', {
command: 'fake-lsp',
args: [],
env: { PATH: bin },
env: { PATH: bin, ...process.platform === 'win32' ? { PATHEXT: '.CMD' } : {} },
extensionToLanguage: { '.ts': 'typescript' },
}))).resolves.toBeDefined()
await ctx.fiber.dispose()
+2 -2
View File
@@ -55,7 +55,7 @@ describe('renderUri', () => {
it('returns an absolute path for a file: URI outside the workspace', () => {
const outside = resolve(WS, '..', 'other', 'lib', 'b.ts')
const uri = pathToFileURL(outside).href
expect(renderUri(uri, WS_URI)).toBe(outside)
expect(renderUri(uri, WS_URI)).toBe(outside.replaceAll('\\', '/'))
})
it('renders the workspace root itself as "."', () => {
@@ -83,7 +83,7 @@ describe('renderUri', () => {
})
it('preserves backslashes as ordinary POSIX filename characters', () => {
expect(renderUri('file:///home/u/proj/dir%5Cname/a.ts', WS_URI)).toBe('dir\\name/a.ts')
expect(renderUri('file:///home/u/proj/dir%5Cname/a.ts', 'file:///home/u/proj')).toBe('dir\\name/a.ts')
})
it('keeps malformed or mismatched URI coordinates verbatim', () => {
@@ -174,18 +174,32 @@ describe('real @openai/codex 0.147.0 product', () => {
}, 60_000)
it('cancels a real app-server command approval without executing the command', async () => {
const { harness, fixture } = await realHarness([
const command = process.platform === 'win32'
? 'cmd /c type nul > approval-side-effect'
: 'touch approval-side-effect'
const commandCalls = [
{
kind: 'functionCall',
name: 'exec_command',
arguments: {
cmd: process.platform === 'win32'
? 'cmd /c type nul > approval-side-effect'
: 'touch approval-side-effect',
cmd: command,
sandbox_permissions: 'require_escalated',
justification: 'exercise the unattended approval boundary',
},
},
{
name: 'shell_command',
arguments: {
command,
sandbox_permissions: 'require_escalated',
justification: 'exercise the unattended approval boundary',
},
},
] as const
const { harness, fixture } = await realHarness([
{
kind: 'advertisedFunctionCall',
choices: commandCalls,
},
])
const sideEffect = join(harness.workspace, 'approval-side-effect')
const run = await harness.ctx.subagents.start('codex', {
@@ -202,9 +216,9 @@ describe('real @openai/codex 0.147.0 product', () => {
expect(existsSync(sideEffect)).toBe(false)
expect(fixture.requests).toHaveLength(1)
const tools = fixture.requests[0]!.body.tools as Array<Record<string, unknown>>
expect(tools).toEqual(expect.arrayContaining([
expect.objectContaining({ type: 'function', name: 'exec_command' }),
]))
expect(commandCalls.some(call => tools.some(tool => (
tool.type === 'function' && tool.name === call.name
)))).toBe(true)
expect(fixture.requests.every(requestEntry =>
requestEntry.headers.authorization === 'Bearer dsh-fake-openai-key',
)).toBe(true)
@@ -22,6 +22,13 @@ export type ResponsesBehavior =
readonly name: string
readonly arguments: Record<string, unknown>
}
| {
readonly kind: 'advertisedFunctionCall'
readonly choices: readonly {
readonly name: string
readonly arguments: Record<string, unknown>
}[]
}
| { readonly kind: 'hold' }
/** Running package-private Responses fixture. */
@@ -218,6 +225,18 @@ function closeServer(server: Server): Promise<void> {
})
}
function advertisedFunctionNames(body: Record<string, unknown>): Set<string> {
if (!Array.isArray(body.tools)) return new Set()
return new Set(body.tools.flatMap((tool): string[] => (
tool !== null
&& typeof tool === 'object'
&& (tool as Record<string, unknown>).type === 'function'
&& typeof (tool as Record<string, unknown>).name === 'string'
? [(tool as Record<string, unknown>).name as string]
: []
)))
}
/**
* Start a loopback-only Responses SSE fixture.
* @param script - one behavior per expected Responses request.
@@ -234,11 +253,12 @@ export async function startResponsesFixture(
openResponses.add(response)
response.on('close', () => { openResponses.delete(response) })
void readRequest(request).then((body) => {
const parsedBody = JSON.parse(body) as Record<string, unknown>
requests.push({
method: request.method,
path: request.url,
headers: request.headers,
body: JSON.parse(body) as Record<string, unknown>,
body: parsedBody,
})
started.resolve(undefined)
const behavior = behaviors.shift()
@@ -247,6 +267,14 @@ export async function startResponsesFixture(
response.end(JSON.stringify({ error: { message: 'fixture script exhausted' } }))
return
}
const advertisedCall = behavior.kind === 'advertisedFunctionCall'
? behavior.choices.find(choice => advertisedFunctionNames(parsedBody).has(choice.name))
: undefined
if (behavior.kind === 'advertisedFunctionCall' && advertisedCall === undefined) {
response.writeHead(500, { 'content-type': 'application/json' })
response.end(JSON.stringify({ error: { message: 'none of the fixture function calls was advertised' } }))
return
}
response.writeHead(200, {
'content-type': 'text/event-stream',
'cache-control': 'no-cache',
@@ -254,9 +282,15 @@ export async function startResponsesFixture(
'x-request-id': 'req_fixture',
})
if (behavior.kind === 'hold') return
const events = behavior.kind === 'complete'
? completeResponsesEvents(behavior.text)
: functionCallEvents(behavior.name, behavior.arguments)
let events: Record<string, unknown>[]
if (behavior.kind === 'complete') {
events = completeResponsesEvents(behavior.text)
} else {
const call = behavior.kind === 'functionCall'
? behavior
: advertisedCall!
events = functionCallEvents(call.name, call.arguments)
}
for (const event of events) {
response.write(`data: ${JSON.stringify(event)}\n\n`)
}