230 lines · 9.1 KB · TypeScript
RawDownload
// Nightly: requires non-core registry commands./** * Integration tests for codex-exec command WASM binary. * * Verifies the codex-exec binary running in WasmVM can: *   - Print usage via --help *   - Print version via --version *   - Validate WASI stub crates via --stub-test *   - Accept a prompt argument and exit cleanly *   - Capture stdout/stderr correctly through the kernel *   - Be spawned from the shell (sh -c) via the kernel pipeline *   - Fail fast for session-turn mode until the real Codex agent is wired * * WASM binary tests are gated behind hasWasmBinaries. */ import { describe, it, expect, afterEach } from 'vitest';import { existsSync } from 'node:fs';import { resolve } from 'node:path';import { createWasmVmRuntime } from '@rivet-dev/agentos-test-harness';import { COMMANDS_DIR, createKernel, describeIf, hasWasmBinaries } from '@rivet-dev/agentos-test-harness';import type { Kernel } from '@rivet-dev/agentos-test-harness'; const hasCodexExec =  process.env.AGENTOS_CODEX_WASM_E2E === '1' &&  hasWasmBinaries &&  existsSync(resolve(COMMANDS_DIR, 'codex-exec')); // Minimal in-memory VFS for kernel testsclass SimpleVFS {  private files = new Map<string, Uint8Array>();  private dirs = new Set<string>(['/']);   async readFile(path: string): Promise<Uint8Array> {    const data = this.files.get(path);    if (!data) throw new Error(`ENOENT: ${path}`);    return data;  }  async readTextFile(path: string): Promise<string> {    return new TextDecoder().decode(await this.readFile(path));  }  async readDir(path: string): Promise<string[]> {    const prefix = path === '/' ? '/' : path + '/';    const entries: string[] = [];    for (const p of [...this.files.keys(), ...this.dirs]) {      if (p !== path && p.startsWith(prefix)) {        const rest = p.slice(prefix.length);        if (!rest.includes('/')) entries.push(rest);      }    }    return entries;  }  async readDirWithTypes(path: string) {    return (await this.readDir(path)).map(name => ({      name,      isDirectory: this.dirs.has(path === '/' ? `/${name}` : `${path}/${name}`),    }));  }  async writeFile(path: string, content: string | Uint8Array): Promise<void> {    const data = typeof content === 'string' ? new TextEncoder().encode(content) : content;    this.files.set(path, new Uint8Array(data));    const parts = path.split('/').filter(Boolean);    for (let i = 1; i < parts.length; i++) {      this.dirs.add('/' + parts.slice(0, i).join('/'));    }  }  async createDir(path: string) { this.dirs.add(path); }  async mkdir(path: string, _options?: { recursive?: boolean }) {    this.dirs.add(path);    const parts = path.split('/').filter(Boolean);    for (let i = 1; i < parts.length; i++) {      this.dirs.add('/' + parts.slice(0, i).join('/'));    }  }  async exists(path: string): Promise<boolean> {    return this.files.has(path) || this.dirs.has(path);  }  async stat(path: string) {    const isDir = this.dirs.has(path);    const data = this.files.get(path);    if (!isDir && !data) throw new Error(`ENOENT: ${path}`);    return {      mode: isDir ? 0o40755 : 0o100644,      size: data?.length ?? 0,      isDirectory: isDir,      isSymbolicLink: false,      atimeMs: Date.now(),      mtimeMs: Date.now(),      ctimeMs: Date.now(),      birthtimeMs: Date.now(),      ino: 0,      nlink: 1,      uid: 1000,      gid: 1000,    };  }  async chmod(_path: string, _mode: number) {}  async lstat(path: string) { return this.stat(path); }  async removeFile(path: string) { this.files.delete(path); }  async removeDir(path: string) { this.dirs.delete(path); }  async rename(oldPath: string, newPath: string) {    const data = this.files.get(oldPath);    if (data) {      this.files.set(newPath, data);      this.files.delete(oldPath);    }  }  async pread(path: string, buffer: Uint8Array, offset: number, length: number, position: number): Promise<number> {    const data = this.files.get(path);    if (!data) throw new Error(`ENOENT: ${path}`);    const available = Math.min(length, data.length - position);    if (available <= 0) return 0;    buffer.set(data.subarray(position, position + available), offset);    return available;  }} async function createTestKernel(): Promise<{ kernel: Kernel; vfs: SimpleVFS }> {  const vfs = new SimpleVFS();  const kernel = createKernel({ filesystem: vfs as any });  await kernel.mount(createWasmVmRuntime({ commandDirs: [COMMANDS_DIR] }));  return { kernel, vfs };} // TODO(P6): requires codex-exec WASM artifact from an external fork build, intentionally excluded from the fast software-build gate.describeIf(hasCodexExec, 'codex-exec command (WasmVM)', { timeout: 30_000 }, () => {  let kernel: Kernel;   afterEach(async () => {    await kernel?.dispose();  });   it('--help prints usage without errors', async () => {    ({ kernel } = await createTestKernel());    const result = await kernel.exec('codex-exec --help');    expect(result.stdout).toContain('codex-exec');    expect(result.stdout).toContain('USAGE');    expect(result.stdout).toContain('headless');    expect(result.stdout).toContain('--help');    expect(result.stdout).toContain('--version');  });   it('--version prints version', async () => {    ({ kernel } = await createTestKernel());    const result = await kernel.exec('codex-exec --version');    expect(result.stdout).toMatch(/codex-exec \d+\.\d+\.\d+/);  });   it('--stub-test validates WASI stub crates', async () => {    ({ kernel } = await createTestKernel());    const result = await kernel.exec('codex-exec --stub-test');    expect(result.stdout).toContain('network-proxy');    expect(result.stdout).toContain('otel');    expect(result.stdout).toContain('stub-test: all stubs validated successfully');  });   it('accepts prompt as argument and exits cleanly', async () => {    ({ kernel } = await createTestKernel());    const result = await kernel.exec('codex-exec "list all files"');    // Prompt mode is currently a placeholder that accepts the prompt without echoing it.    expect(result.stderr).toContain('headless prompt mode is not wired to the provider yet');    expect(result.stderr).toContain('prompt received');    expect(result.stderr).not.toContain('list all files');  });   it('accepts prompt from stdin without echoing it', async () => {    ({ kernel } = await createTestKernel());    const result = await kernel.exec('codex-exec', { stdin: 'stdin secret prompt\n' });    expect(result.exitCode).toBe(0);    expect(result.stderr).toContain('headless prompt mode is not wired to the provider yet');    expect(result.stderr).toContain('prompt received');    expect(result.stderr).not.toContain('stdin secret prompt');  });   it('rejects oversized stdin prompts', async () => {    ({ kernel } = await createTestKernel());    const result = await kernel.exec('codex-exec', { stdin: 'x'.repeat(64 * 1024 + 1) });    expect(result.exitCode).not.toBe(0);    expect(result.stderr).toContain('stdin prompt exceeds');  });   it('prints error when no prompt is provided via arg', async () => {    ({ kernel } = await createTestKernel());    // codex-exec with no args reads stdin; since stdin is empty pipe it gets empty prompt    const result = await kernel.exec('codex-exec');    // Should get an error about no prompt or the stdin read returns empty    expect(result.stderr).toContain('codex-exec');  });   it('can be spawned from shell via sh -c pipeline', async () => {    ({ kernel } = await createTestKernel());    const result = await kernel.exec('sh -c "codex-exec --help"');    expect(result.stdout).toContain('codex-exec');    expect(result.stdout).toContain('USAGE');  });   it('captures stdout correctly through kernel.exec()', async () => {    ({ kernel } = await createTestKernel());    const result = await kernel.exec('codex-exec --help');    // Verify stdout is non-empty and contains expected structured output    expect(result.stdout.length).toBeGreaterThan(0);    expect(result.stdout).toContain('OPTIONS');    expect(result.stdout).toContain('USAGE');  });   it('captures stderr correctly through kernel.exec()', async () => {    ({ kernel } = await createTestKernel());    const result = await kernel.exec('codex-exec "test prompt"');    // Headless mode outputs to stderr    expect(result.stderr.length).toBeGreaterThan(0);    expect(result.stderr).toContain('prompt received');    expect(result.stderr).not.toContain('test prompt');  });   it('exits cleanly after completing a single prompt', async () => {    ({ kernel } = await createTestKernel());    const result = await kernel.exec('codex-exec "hello world"');    // The process exits with code 0 (brush-shell wraps it)    // Verify it doesn't hang — the exec() call resolves    expect(result.stderr).toContain('prompt received');    expect(result.stderr).not.toContain('hello world');  });   it('session-turn mode fails fast instead of calling a bespoke provider loop', async () => {    ({ kernel } = await createTestKernel());    const result = await kernel.exec('codex-exec --session-turn');    expect(result.stdout).toContain('"type":"error"');    expect(result.stdout).toContain('real Codex agent package');  });});