Files
rea/tests/boundary/browser/runtimeFixtureCleanup.test.ts
22e5aa97af feat(artifacts): inspect classic iNES container regions (#2816)
* feat(artifacts): inspect classic iNES container regions

* fix(artifacts): preserve iNES source disappearance as integrity failure

* fix(artifacts): revalidate iNES source before parsing header

* fix(artifacts): validate admitted roots before classification

* fix(artifacts): normalize source loss during root hashing

* fix(artifacts): bind iNES extraction admission to inventory

* test(artifacts): clarify non-executing inspection wording

* fix: preserve binary plist detection with extended magic probe

* test(browser): wait for popup document loads before capture

* fix(artifacts): honor cancellation across source validation

* fix(artifacts): prefer artifact magic over implicit database suffix

* fix(artifacts): select extraction readers from scanned format

* fix(artifacts): complete prefix reads and share ZIP format admission

* fix(browser): budget scenario cleanup for content drain and shutdown

* test(artifacts): admit valid universal inputs before routing

* test(artifacts): verify iNES workflows on macOS

* test(ci): own iNES macOS coverage and portable fixture paths

* fix: preserve cleanup retries and avoid duplicate inventory parsing

* fix(contracts): share ZIP target formats across MCP lifecycle schemas

* fix(target): preserve typed cancellation during descriptor admission

* refactor(artifacts): use JSON-safe inventory producer output directly

* test(artifacts): share file read setup and satisfy CI checks

* fix(artifacts): classify changed admission as integrity failure

* ci: run Mach-O summary delivery coverage on macOS

* test(javascript): align parameter provenance with main contract

* test(browser): await discoverable fixture pages before runtime verification

* fix(artifacts): pass target metadata to format detection

* fix(artifacts): bind extraction to opened target

* fix: repair the static-check failures on the default branch (#2895)

* fix: repair the static-check failures on the default branch

The default branch fails its own required `Static checks` job three ways, so
every pull request reports that job as failed regardless of its own change.

- `npm run format:check` reports `src/domain/functionComparison.test.ts` and
  `src/ida/IdaOperationRunner.ts`.
- `npm run lint` reports a duplicate `import { join } from "node:path"` in
  `tests/boundary/mcp/ghidraInventoryMcp.test.ts` (lines 4 and 17).
- `npm run typecheck` reports `format: "devin"` missing from the client format
  union in `src/application/SupportedClients.ts`, and an unchecked index in
  `tests/boundary/cli/clientProfileLifecycle.test.ts`, whose pair array is
  inferred as `string[][]`.

Apply the pinned formatter, drop the duplicate import, list the `"devin"` format
that its own definition already uses, and mark the pair array `as const`.

Validation: `npm run check:fast` and `npm run format:check` pass.

* fix(setup): keep Devin on the JSON configuration dialect

---------

Co-authored-by: 吴佳浩 <[email protected]>
Co-authored-by: morluto <[email protected]>
(cherry picked from commit df3d96da80)

* fix(artifacts): bind session inventories to target identity

* test(browser): bound fixture readiness acknowledgements

---------

Co-authored-by: 64579525 <[email protected]>
Co-authored-by: 吴佳浩Alben <[email protected]>
Co-authored-by: 吴佳浩 <[email protected]>
2026-10-31 15:49:16 +08:00

222 lines
9.9 KiB
TypeScript

import { execFile } from "node:child_process";
import { readdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { promisify } from "node:util";
import { expect, it } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
it.skipIf(process.platform === "win32")(
"rejects an exited fixture before returning a lease and removes its profile",
async () => {
const root = await createTestTempDirectory("rea-runtime-startup-failure-");
const helper = new URL(
"../../../scripts/lib/browser-runtime-fixture-lifecycle.mjs",
import.meta.url,
);
const script = `
const { startRuntimeFixtureBrowser } = await import(${JSON.stringify(helper.href)});
// Node rejects Chrome's arguments immediately, exercising actual process exit.
try {
const fixture = await startRuntimeFixtureBrowser(process.execPath, 'http://127.0.0.1');
await fixture.close();
throw new Error('Expected startup failure');
} catch (error) {
if (error._tag !== 'BrowserStartupError') throw error;
console.log(JSON.stringify({
failure: error.failure,
exitCode: error.exitCode,
stderr: error.stderr,
}));
}
`;
const { stdout } = await promisify(execFile)(
process.execPath,
["--input-type=module", "-e", script],
{
env: { ...process.env, TMPDIR: root, TMP: root, TEMP: root },
timeout: 10_000,
},
);
expect(JSON.parse(stdout)).toEqual({
failure: "exited",
exitCode: expect.any(Number),
stderr: expect.stringContaining("bad option"),
});
expect(await readdir(root)).toEqual([]);
},
);
it.skipIf(process.platform === "win32")(
"retains browser ownership after denied cleanup and retries the whole process group",
async () => {
const root = await createTestTempDirectory("rea-runtime-owned-browser-");
const executable = await writeFixtureBrowser(root);
const helper = new URL(
"../../../scripts/lib/browser-runtime-fixture-lifecycle.mjs",
import.meta.url,
);
const script = `
import assert from 'node:assert/strict';
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
const { startRuntimeFixtureBrowser } = await import(${JSON.stringify(helper.href)});
const fixture = await startRuntimeFixtureBrowser(${JSON.stringify(executable)}, 'http://127.0.0.1');
const descendant = Number(readFileSync(join(fixture.profile, 'descendant'), 'utf8'));
fixture.process.emit('error', Object.assign(new Error('late EPERM fixture'), { code: 'EPERM' }));
const kill = process.kill;
let denied = false;
process.kill = function(pid, signal) {
if (pid < 0 && signal !== 0) {
denied = true;
throw Object.assign(new Error('denied fixture group cleanup'), { code: 'EPERM' });
}
return kill(pid, signal);
};
try {
await assert.rejects(fixture.close(), /incomplete/);
assert.equal(denied, true);
assert.equal(existsSync(fixture.profile), true);
assert.ok(fixture.process.listenerCount('error') > 0);
} finally {
process.kill = kill;
await fixture.close();
}
assert.equal(existsSync(fixture.profile), false);
assert.equal(fixture.process.listenerCount('error'), 0);
const { createSystemProcessOwnershipHost } = await import(${JSON.stringify(new URL("../../../dist/process/ProcessOwnershipObservation.js", import.meta.url).href)});
const { liveProcesses } = await import(${JSON.stringify(new URL("../../../dist/process/ProcessOwnershipProcessTree.js", import.meta.url).href)});
const live = liveProcesses(await createSystemProcessOwnershipHost().listProcesses());
assert.equal(live.some(item => item.pid === fixture.process.pid || item.pid === descendant), false);
// The process may finish while the separately owned socket still needs retry.
const { CdpConnection } = await import(${JSON.stringify(new URL("../../../dist/browser/CdpConnection.js", import.meta.url).href)});
const { WebSocketServer } = await import('ws');
const server = new WebSocketServer({ host: '127.0.0.1', port: 0 });
await new Promise(resolve => server.once('listening', resolve));
server.on('connection', socket => socket.on('message', data => {
const { id } = JSON.parse(data.toString());
socket.send(JSON.stringify({ id, result: {} }));
}));
const connect = CdpConnection.connect;
let connections = 0;
let closeAttempts = 0;
let socketOwner;
CdpConnection.connect = async function(...args) {
connections++;
args[0] = 'ws://127.0.0.1:' + server.address().port + '/devtools/browser/fixture';
const onConnected = args[4];
args[4] = (connection) => {
socketOwner = connection;
const close = connection.close.bind(connection);
connection.close = async () => {
closeAttempts++;
if (closeAttempts === 1) throw new Error('denied socket close');
await close();
};
onConnected?.(connection);
};
return connect.apply(this, args);
};
const socketFixture = await startRuntimeFixtureBrowser(${JSON.stringify(executable)}, 'http://127.0.0.1');
try {
await assert.rejects(socketFixture.close(), cause => hasFailure(cause, 'denied socket close'));
assert.ok(socketFixture.process.exitCode !== null || socketFixture.process.signalCode !== null);
assert.equal(existsSync(socketFixture.profile), true);
assert.ok(socketOwner, 'Acquire ownership before the WebSocket handshake');
assert.equal(server.clients.size, 1, 'Failed socket close must keep its actual open transport');
await socketFixture.close();
assert.equal(connections, 1, 'Retry must reuse the exact retained socket');
assert.equal(closeAttempts, 2);
assert.equal(existsSync(socketFixture.profile), false);
} finally {
CdpConnection.connect = connect;
await socketFixture.close();
await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve()));
}
// A late launcher exit must preserve the sibling/descendant cleanup lease
// even though startup never returned a browser handle.
process.kill = function(pid, signal) {
if (pid < 0 && signal !== 0)
throw Object.assign(new Error('denied startup cleanup'), { code: 'EPERM' });
return kill(pid, signal);
};
let startupFailure;
try {
await startRuntimeFixtureBrowser(${JSON.stringify(executable)}, 'http://127.0.0.1/startup-fail');
throw new Error('Expected startup failure');
} catch (cause) {
startupFailure = cause;
} finally {
process.kill = kill;
}
function hasFailure(cause, message) {
if (!cause) return false;
return cause?.message === message || hasFailure(cause?.cause, message) ||
(cause?.errors ?? []).some(error => hasFailure(error, message));
}
function findCleanupFailure(cause) {
if (cause.cleanupLeases?.length) return cause;
for (const error of cause.errors ?? []) {
const found = findCleanupFailure(error);
if (found) return found;
}
}
const retained = findCleanupFailure(startupFailure);
assert.ok(retained, 'Failed startup must retain exact cleanup authority');
const lease = retained.cleanupLeases[0];
const [failedProfile] = lease.cleanupResources;
const lateDescendant = Number(readFileSync(join(failedProfile, 'descendant'), 'utf8'));
assert.equal(existsSync(failedProfile), true);
await lease.release();
assert.equal(existsSync(failedProfile), false);
const afterRetry = liveProcesses(await createSystemProcessOwnershipHost().listProcesses());
assert.equal(afterRetry.some(item => item.pid === lateDescendant), false);
console.log(JSON.stringify({ retried: true, descendant_removed: true, startup_retry: true, socket_retry: true }));
`;
const { stdout, stderr } = await promisify(execFile)(
process.execPath,
["--input-type=module", "-e", script],
{ timeout: 15_000 },
);
expect(JSON.parse(stdout)).toEqual({
retried: true,
descendant_removed: true,
startup_retry: true,
socket_retry: true,
});
expect(stderr).toContain(
"Browser verifier process error: late EPERM fixture",
);
},
);
async function writeFixtureBrowser(root: string): Promise<string> {
const executable = join(root, "fixture-browser.cjs");
await writeFile(
executable,
`#!${process.execPath}
const { spawn } = require('node:child_process');
const { writeFileSync } = require('node:fs');
const { join } = require('node:path');
const { createServer } = require('node:http');
const profile = process.argv.find(value => value.startsWith('--user-data-dir=')).split('=').slice(1).join('=');
const child = spawn(process.execPath, ['-e', 'setInterval(() => {}, 1000)']);
writeFileSync(join(profile, 'descendant'), String(child.pid));
if (!process.argv.at(-1).endsWith('/startup-fail')) {
const origin = process.argv.at(-1);
const server = createServer((req, res) => {
const port = server.address().port;
res.end(JSON.stringify(req.url === '/json/version'
? { Browser: 'Fixture/1', 'Protocol-Version': '1.3', 'User-Agent': 'Fixture', 'V8-Version': '1', 'WebKit-Version': '1', webSocketDebuggerUrl: 'ws://127.0.0.1:' + port + '/devtools/browser/fixture' }
: [{ id: 'fixture', type: 'page', title: 'fixture', url: origin }]));
});
server.listen(0, '127.0.0.1', () => writeFileSync(join(profile, 'DevToolsActivePort'), String(server.address().port) + '\\n/devtools/browser/fixture'));
} else setTimeout(() => process.exit(3), 30);
console.error('owned fixture diagnostics');
setInterval(() => {}, 1000);
`,
{ mode: 0o755 },
);
return executable;
}