mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-11 01:51:34 -10:00
test(pet): release rejected recorders and wait for startup readiness
Unreference the Windows test IPC channel so startup rejection can exit. Exercise that path on every host and await CLI readiness before shutdown; an archive link alone can precede replacement and signal-handler setup. The production recorder and native bundle bytes are unchanged. Validation: npm --prefix pet run check: 61 passed, 0 failed, 0 canceled. npm test: 66 package + 12 SDK + 446 web passed, 0 failed. npm run check:web: passed, 0 errors, 2 existing warnings. The prior IPC preload hangs a rejected CLI until SIGKILL; the corrected preload exits with code 1. Windows CI remains the platform proof. Signed-off-by: CodeWhale Bot <[email protected]>
This commit is contained in:
1 parent
11c6fbfc24
commit
c342329f62
3 files changed
+26
-9
No files matched your search
@@ -209,3 +209,16 @@ TUI and native bundle bytes are unchanged from the previously verified build.
|
||||
Grokbot review and current-head Windows/macOS recorder CI are separate receipts.
|
||||
Native active-session delivery, current macOS popover inspection and device
|
||||
transport remain open. No release or deployment is implied.
|
||||
|
||||
|
||||
The first restart commit `11c6fbfc` passed all 61 tests independently in
|
||||
Codewhalebot on Node 22.19.0 with no remaining concrete source finding. macOS
|
||||
recorder CI also passed. Windows passed 60 tests and canceled one on timeout:
|
||||
the test-only IPC message listener kept a correctly rejected startup alive.
|
||||
The same preload reproduced the hang locally; unreferencing its IPC channel
|
||||
lets the rejected CLI exit with code 1. The process-death/restart test now uses
|
||||
that IPC path on every host, while other POSIX cases still use actual SIGINT.
|
||||
The fixture also waits for CLI readiness before shutdown: the archive link may
|
||||
appear before replacement and before the signal handler is installed. A broad
|
||||
local run exposed this early-stop race after the IPC fix. Production recorder
|
||||
and native bundle bytes are unchanged in this follow-up.
|
||||
@@ -2,17 +2,17 @@ import { spawn } from 'node:child_process';
|
||||
|
||||
/** Windows child.kill('SIGINT') force-terminates the process. This test-only
|
||||
* preload delivers the handler notification over IPC; POSIX uses a real signal.
|
||||
* The IPC channel is unreferenced so startup rejection can exit naturally.
|
||||
* No test control channel is added to the shipped recorder. */
|
||||
export function spawnRecorder(args, env = process.env) {
|
||||
const windows = process.platform === 'win32';
|
||||
const control = `process.once('message', () => { process.disconnect(); process.emit('SIGINT'); });`;
|
||||
const preload = windows ? ['--import', `data:text/javascript,${encodeURIComponent(control)}`] : [];
|
||||
export function spawnRecorder(args, env = process.env, ipc = process.platform === 'win32') {
|
||||
const control = `process.once('message', () => { process.disconnect(); process.emit('SIGINT'); }); process.channel.unref();`;
|
||||
const preload = ipc ? ['--import', `data:text/javascript,${encodeURIComponent(control)}`] : [];
|
||||
const child = spawn(process.execPath, [...preload, 'scripts/pet.mjs', ...args], {
|
||||
cwd: new URL('../../', import.meta.url), env, stdio: windows ? ['ignore', 'pipe', 'pipe', 'ipc'] : ['ignore', 'pipe', 'pipe'],
|
||||
cwd: new URL('../../', import.meta.url), env, stdio: ipc ? ['ignore', 'pipe', 'pipe', 'ipc'] : ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
child.stopRecorder = () => {
|
||||
if (child.exitCode !== null || child.signalCode !== null) return false;
|
||||
return windows ? child.send('stop') : child.kill('SIGINT');
|
||||
return ipc ? child.send('stop') : child.kill('SIGINT');
|
||||
};
|
||||
return child;
|
||||
}
|
||||
@@ -191,7 +191,9 @@ test('the actual CLI resumes after process death at the same path without reclai
|
||||
await writeFile(input, JSON.stringify({ schemaVersion: 1, id: 'old', traceId: 'fixture', name: 'bash', category: 'code', startTime: 0, endTime: 1, attributes: {} }));
|
||||
const args = [`--input=${input}`, `--output=${path}`, '--watch'];
|
||||
const launch = extra => {
|
||||
const child = spawnRecorder([...args, ...extra]);
|
||||
// Exercise the Windows IPC harness on every platform; it must not keep
|
||||
// a correctly rejected startup alive after the CLI sets its exit code.
|
||||
const child = spawnRecorder([...args, ...extra], process.env, true);
|
||||
child.log = ''; child.stdout.on('data', b => child.log += b); child.stderr.on('data', b => child.log += b);
|
||||
child.exited = once(child, 'exit');
|
||||
t.after(() => { if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL'); });
|
||||
@@ -209,14 +211,16 @@ test('the actual CLI resumes after process death at the same path without reclai
|
||||
first.kill('SIGKILL'); await first.exited;
|
||||
const previous = await readFile(path, 'utf8');
|
||||
const second = launch(['--resume']);
|
||||
await waitFor(async () => (await stat(part(path, 1))).size && decodePetJSONL(await readFile(path, 'utf8')).length >= 2);
|
||||
// The archive link can exist before replacement and handler installation.
|
||||
// Wait for CLI readiness as well as the new tape before requesting shutdown.
|
||||
await waitFor(async () => second.log.includes('Recording local live pet states.') && (await stat(part(path, 1))).size && decodePetJSONL(await readFile(path, 'utf8')).length >= 2);
|
||||
second.stopRecorder(); assert.equal((await second.exited)[0], 0, second.log);
|
||||
assert.equal(await readFile(part(path, 1), 'utf8'), previous);
|
||||
const buckets = decodePetJSONL(await readFile(path, 'utf8'));
|
||||
assert.equal(buckets[0].observed, 0); assert.equal(buckets[0].waiting, false); assert.equal(buckets[0].errors, 0);
|
||||
assert.ok(buckets[0].onsets.every(n => n === 0));
|
||||
const third = launch(['--resume']);
|
||||
await waitFor(async () => (await stat(part(path, 2))).size && decodePetJSONL(await readFile(path, 'utf8')).length >= 2);
|
||||
await waitFor(async () => third.log.includes('Recording local live pet states.') && (await stat(part(path, 2))).size && decodePetJSONL(await readFile(path, 'utf8')).length >= 2);
|
||||
third.stopRecorder(); assert.equal((await third.exited)[0], 0, third.log);
|
||||
assert.equal(decodePetJSONL(await readFile(part(path, 2), 'utf8')).length, buckets.length);
|
||||
});
|
||||
Reference in new issue
Block a user