52 lines
3.9 KiB
JavaScript
52 lines
3.9 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { spawn } from 'node:child_process';
|
|
import { firstOutputMarker } from '../src/first-output.mjs';
|
|
test('stdout marker contains a fixed token only, never output text; emitted once after nonempty output', t => {
|
|
const root = fs.mkdtempSync(join(tmpdir(), 'p2-output-')); t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
const observe = firstOutputMarker(root), file = join(root, 'proxy-first-output');
|
|
observe(''); assert.equal(fs.existsSync(file), false);
|
|
observe('fx331-private-output');
|
|
assert.equal(fs.readFileSync(file, 'utf8'), 'output\n'); assert.equal(fs.statSync(file).mode & 0o777, 0o600);
|
|
const before = fs.statSync(file).mtimeMs;
|
|
observe('other output'); assert.equal(fs.statSync(file).mtimeMs, before);
|
|
assert.equal(fs.readFileSync(file, 'utf8').includes('fx331-private-output'), false);
|
|
});
|
|
|
|
// A signalled model child keeps the shell status 128+n; a signal name with no known number fails 1.
|
|
test('a signalled model child exits 128 plus its signal number, an unmapped signal exits 1', async t => {
|
|
const root = fs.mkdtempSync(join(tmpdir(), 'p2-signal-')); t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
const fake = join(root, 'fake-spawn.cjs');
|
|
fs.writeFileSync(fake, "const cp = require('node:child_process'); const { EventEmitter } = require('node:events');\n"
|
|
+ "cp.spawn = () => { const child = new EventEmitter(); child.stdout = new EventEmitter(); child.stdout.setEncoding = () => {}; child.kill = () => {};\n"
|
|
+ " setImmediate(() => child.emit('close', null, process.env.FAKE_SIGNAL)); return child; };\n"
|
|
+ "require('node:module').syncBuiltinESMExports();\n");
|
|
const entry = new URL('../../plugins/harness/pi/model-stream.mjs', import.meta.url).pathname;
|
|
for (const [signal, expected] of [['SIGKILL', 137], ['SIGTERM', 143], ['SIGUNKNOWN', 1]]) {
|
|
const child = spawn(process.execPath, ['--require', fake, entry, 'pi', root, 'unused'], { env: { PATH: process.env.PATH, FAKE_SIGNAL: signal }, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
let stdout = '', stderr = '';
|
|
child.stdout.on('data', x => { stdout += x; }); child.stderr.on('data', x => { stderr += x; });
|
|
const code = await new Promise(done => child.on('close', done));
|
|
assert.equal(code, expected, signal);
|
|
assert.equal(stdout, '', signal);
|
|
assert.equal(stderr, `model child killed by ${signal}\n`, signal);
|
|
}
|
|
});
|
|
|
|
test('Claude helper retains child signal receipts and rejects unmapped signals', async t => {
|
|
const root = fs.mkdtempSync(join(tmpdir(), 'p2-claude-signal-'));
|
|
t.after(() => fs.rmSync(root, {recursive:true, force:true}));
|
|
const fake = join(root, 'fake-spawn.cjs');
|
|
fs.writeFileSync(fake, "const cp=require('node:child_process');const {EventEmitter}=require('node:events');cp.spawn=()=>{const c=new EventEmitter();c.stdout=new EventEmitter();c.stdout.setEncoding=()=>{};c.kill=()=>{};setImmediate(()=>c.emit('close',null,process.env.FAKE_SIGNAL));return c};require('node:module').syncBuiltinESMExports();\n");
|
|
const entry = new URL('../../../adapters/claude/stream.mjs', import.meta.url).pathname;
|
|
for (const [signal, expected] of [['SIGKILL',137], ['SIGTERM',143], ['SIGUNKNOWN',1]]) {
|
|
const child = spawn(process.execPath, ['--require',fake,entry,'unused'], {env:{PATH:process.env.PATH,FAKE_SIGNAL:signal},stdio:['ignore','pipe','pipe','pipe','ignore','pipe']});
|
|
let output='', receipt='', error=''; child.stdout.on('data',x=>output+=x); child.stdio[3].on('data',x=>receipt+=x); child.stdio[5].on('data',x=>error+=x);
|
|
const code = await new Promise(done=>child.on('close',done));
|
|
assert.equal(code,expected,signal); assert.equal(output,'',signal); assert.equal(receipt,'invalid',signal); assert.equal(error,`model child killed by ${signal}\n`,signal);
|
|
}
|
|
});
|