fix(harness): fail 1 on an unmapped child signal (#797)
A signal name with no number in os.constants.signals now exits 1 instead of a made-up 128. Known signals keep 128+n (SIGKILL 137, SIGTERM 143) and the content-free stderr receipt. Test covers all three through the real entrypoint.
This commit is contained in:
1 parent
a3f4551406
commit
6b2c925b95
2 files changed
+23
-1
No files matched your search
@@ -96,7 +96,8 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur
|
||||
if (bad) { process.stderr.write('model event stream failed\n'); process.exitCode = 1; return; }
|
||||
if (signal) {
|
||||
process.stderr.write(`model child killed by ${signal}\n`);
|
||||
process.exitCode = 128 + (constants.signals[signal] ?? 0);
|
||||
const number = constants.signals[signal];
|
||||
process.exitCode = Number.isInteger(number) && number > 0 ? 128 + number : 1;
|
||||
return;
|
||||
}
|
||||
if (code !== 0) { process.exitCode = code ?? 1; return; }
|
||||
|
||||
@@ -3,6 +3,7 @@ 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 }));
|
||||
@@ -14,3 +15,23 @@ test('stdout marker contains a fixed token only, never output text; emitted once
|
||||
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('../src/first-output.mjs', import.meta.url).pathname;
|
||||
for (const [signal, expected] of [['SIGKILL', 137], ['SIGTERM', 143], ['SIGUNKNOWN', 1]]) {
|
||||
const child = spawn(process.execPath, ['--require', fake, entry, 'claude', 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);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user