149 lines
4.6 KiB
JavaScript
Executable File
149 lines
4.6 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Regression check for daemon liveness when <session>.pid is missing.
|
|
*
|
|
* What it verifies:
|
|
* 1) A daemon session is reachable.
|
|
* 2) Deleting <session>.pid does not break the next command.
|
|
* 3) The session socket is not recreated (inode unchanged on Unix),
|
|
* meaning we reused the live daemon instead of tearing it down.
|
|
*
|
|
* Usage:
|
|
* node scripts/check-daemon-pid-recovery.js
|
|
* node scripts/check-daemon-pid-recovery.js --session default
|
|
* node scripts/check-daemon-pid-recovery.js --binary ./bin/agent-browser-darwin-arm64
|
|
*/
|
|
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path, { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { spawnSync } from 'node:child_process';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const rootDir = join(__dirname, '..');
|
|
|
|
const args = process.argv.slice(2);
|
|
const getArgValue = (name, fallback) => {
|
|
const index = args.indexOf(name);
|
|
if (index === -1 || index + 1 >= args.length) return fallback;
|
|
return args[index + 1];
|
|
};
|
|
|
|
function resolveSocketDir() {
|
|
if (process.env.AGENT_BROWSER_SOCKET_DIR && process.env.AGENT_BROWSER_SOCKET_DIR.length > 0) {
|
|
return process.env.AGENT_BROWSER_SOCKET_DIR;
|
|
}
|
|
if (process.env.XDG_RUNTIME_DIR && process.env.XDG_RUNTIME_DIR.length > 0) {
|
|
return path.join(process.env.XDG_RUNTIME_DIR, 'agent-browser');
|
|
}
|
|
return path.join(os.homedir(), '.agent-browser');
|
|
}
|
|
|
|
function resolveDefaultBinary() {
|
|
const osKey = os.platform() === 'win32' ? 'win32' : os.platform() === 'darwin' ? 'darwin' : 'linux';
|
|
const archKey = os.arch() === 'arm64' ? 'arm64' : 'x64';
|
|
const ext = os.platform() === 'win32' ? '.exe' : '';
|
|
|
|
const candidates = [
|
|
join(rootDir, 'bin', `agent-browser-${osKey}-${archKey}${ext}`),
|
|
join(rootDir, 'cli', 'target', 'release', `agent-browser${ext}`),
|
|
join(rootDir, 'bin', `agent-browser-local${ext}`),
|
|
];
|
|
|
|
for (const candidate of candidates) {
|
|
if (fs.existsSync(candidate)) return candidate;
|
|
}
|
|
|
|
return candidates[0];
|
|
}
|
|
|
|
function runCommand(binary, commandArgs, allowFailure = false) {
|
|
const result = spawnSync(binary, commandArgs, {
|
|
encoding: 'utf8',
|
|
env: process.env,
|
|
});
|
|
if (result.status !== 0 && !allowFailure) {
|
|
const stderr = (result.stderr || '').trim();
|
|
const stdout = (result.stdout || '').trim();
|
|
throw new Error(
|
|
`Command failed: ${binary} ${commandArgs.join(' ')}\n${stderr || stdout || `exit code ${result.status}`}`
|
|
);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function main() {
|
|
const session = getArgValue('--session', 'default');
|
|
const binary = getArgValue('--binary', resolveDefaultBinary());
|
|
const socketDir = resolveSocketDir();
|
|
const isWindows = os.platform() === 'win32';
|
|
|
|
const pidPath = join(socketDir, `${session}.pid`);
|
|
const socketPath = isWindows ? null : join(socketDir, `${session}.sock`);
|
|
const portPath = isWindows ? join(socketDir, `${session}.port`) : null;
|
|
|
|
if (!fs.existsSync(binary)) {
|
|
throw new Error(`Binary not found: ${binary}`);
|
|
}
|
|
|
|
// Ensure daemon/session is live before we simulate pid loss.
|
|
runCommand(binary, ['--session', session, 'get', 'url']);
|
|
|
|
let socketInodeBefore = null;
|
|
if (!isWindows) {
|
|
if (!socketPath || !fs.existsSync(socketPath)) {
|
|
throw new Error(`Socket file not found: ${socketPath}`);
|
|
}
|
|
socketInodeBefore = fs.statSync(socketPath).ino;
|
|
}
|
|
|
|
const pidExistedBefore = fs.existsSync(pidPath);
|
|
if (pidExistedBefore) {
|
|
fs.unlinkSync(pidPath);
|
|
}
|
|
|
|
// This is the critical step: should still work even though pid file is gone.
|
|
const second = runCommand(binary, ['--session', session, 'get', 'title']);
|
|
const secondOutput = (second.stdout || '').trim();
|
|
|
|
let socketInodeAfter = null;
|
|
let socketUnchanged = true;
|
|
if (!isWindows) {
|
|
if (!socketPath || !fs.existsSync(socketPath)) {
|
|
throw new Error(`Socket file missing after pid removal: ${socketPath}`);
|
|
}
|
|
socketInodeAfter = fs.statSync(socketPath).ino;
|
|
socketUnchanged = socketInodeBefore === socketInodeAfter;
|
|
} else if (portPath && !fs.existsSync(portPath)) {
|
|
throw new Error(`Port file missing after pid removal: ${portPath}`);
|
|
}
|
|
|
|
const pidExistsAfter = fs.existsSync(pidPath);
|
|
const passed = socketUnchanged;
|
|
|
|
const report = {
|
|
passed,
|
|
session,
|
|
binary,
|
|
socketDir,
|
|
pidPath,
|
|
pidExistedBefore,
|
|
pidExistsAfter,
|
|
socketPath,
|
|
socketInodeBefore,
|
|
socketInodeAfter,
|
|
socketUnchanged,
|
|
secondCommandOutput: secondOutput,
|
|
timestamp: new Date().toISOString(),
|
|
};
|
|
|
|
console.log(JSON.stringify(report, null, 2));
|
|
if (!passed) {
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main();
|