Files
NetMesh/electron/bridges/systemManagerBridge.processes.test.cjs
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

446 lines
15 KiB
JavaScript

"use strict";
const test = require("node:test");
const assert = require("node:assert/strict");
const { spawnSync } = require("node:child_process");
const { EventEmitter } = require("node:events");
const fs = require("node:fs");
const path = require("node:path");
const { createSystemManagerBridge } = require("./systemManagerBridge.cjs");
function createFakeExecStream(stdout, options = {}) {
const stream = new EventEmitter();
stream.stderr = new EventEmitter();
process.nextTick(() => {
if (stdout) stream.emit("data", stdout);
if (options.stderr) stream.stderr.emit("data", options.stderr);
stream.emit("close", options.code ?? 0);
});
return stream;
}
function runProcessCommandWithBusyBoxPs(command, {
supportsWide = true,
supportsTop = true,
topHasCpuColumn = false,
topUsesPercentSymbols = true,
} = {}) {
const prefix = "exec sh -c '";
assert.ok(command.startsWith(prefix) && command.endsWith("'"));
const innerScript = command.slice(prefix.length, -1);
const processOutput = [
" PID USER VSZ STAT COMMAND",
" 1 root 1356 S /sbin/procd",
" 411 root 97.6m S /sbin/ubusd",
];
const printProcessOutput = processOutput
.map((line) => `printf '%s\\n' '${line}'`)
.join("\n");
const fakePs = [
"exec 3>&2",
"top() {",
" printf '__TOP_CALL__=%s\\n' \"$*\" >&3",
supportsTop ? " :" : " return 1",
" printf '%s\\n' 'Mem: 2059948K used, 14365980K free, 52256K shrd, 204K buff, 599840K cached'",
" printf '%s\\n' 'CPU: 0% usr 0% sys 0% nic 99% idle 0% io 0% irq 0% sirq'",
topHasCpuColumn
? " printf '%s\\n' ' PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND'"
: " printf '%s\\n' ' PID PPID USER STAT VSZ %VSZ %CPU COMMAND'",
topHasCpuColumn
? ` printf '%s\\n' ' 1 0 root S 1356 1${topUsesPercentSymbols ? "%" : ".0"} 0 2${topUsesPercentSymbols ? "%" : ".0"} /sbin/procd'`
: ` printf '%s\\n' ' 1 0 root S 1356 1${topUsesPercentSymbols ? "%" : ".0"} 2${topUsesPercentSymbols ? "%" : ".0"} /sbin/procd'`,
topHasCpuColumn
? ` printf '%s\\n' ' 411 1 root R 97.6m 10${topUsesPercentSymbols ? "%" : ".0"} 3 7${topUsesPercentSymbols ? "%" : ".0"} /sbin/ubusd'`
: ` printf '%s\\n' ' 411 1 root R 97.6m 10${topUsesPercentSymbols ? "%" : ".0"} 7${topUsesPercentSymbols ? "%" : ".0"} /sbin/ubusd'`,
"}",
"ps() {",
" printf '__PS_CALL__=%s\\n' \"$*\" >&3",
" if [ \"$1\" = '-eo' ]; then",
" printf '%s\\n' 'ps: unrecognized option: e' >&2",
" return 1",
" fi",
" if [ \"$#\" -eq 1 ] && [ \"$1\" = 'ww' ]; then",
supportsWide ? printProcessOutput : " return 1",
" return 0",
" fi",
" if [ \"$#\" -eq 0 ]; then",
printProcessOutput,
" return 0",
" fi",
" return 1",
"}",
innerScript,
].join("\n");
const result = spawnSync("sh", ["-c", fakePs], { encoding: "utf8" });
const calls = String(result.stderr || "")
.split("\n")
.filter((line) => line.startsWith("__PS_CALL__="))
.map((line) => line.slice("__PS_CALL__=".length));
const topCalls = String(result.stderr || "")
.split("\n")
.filter((line) => line.startsWith("__TOP_CALL__="))
.map((line) => line.slice("__TOP_CALL__=".length));
return {
stdout: String(result.stdout || ""),
stderr: String(result.stderr || ""),
code: result.status ?? 1,
calls,
topCalls,
};
}
test("listProcesses preserves BusyBox top CPU and memory percentages", async () => {
let seenTopCalls = [];
const conn = {
exec(command, callback) {
const execution = runProcessCommandWithBusyBoxPs(command);
seenTopCalls = execution.topCalls;
callback(null, createFakeExecStream(execution.stdout, execution));
},
};
const sessions = new Map([["openwrt", { conn, type: "ssh" }]]);
const bridge = createSystemManagerBridge({
getSessions: () => sessions,
process,
});
const result = await bridge.listProcesses(null, { sessionId: "openwrt" });
assert.equal(result.success, true);
assert.equal(result.processes[0].cpuPercent, 2);
assert.equal(result.processes[0].memPercent, 1);
assert.equal(result.processes[1].cpuPercent, 7);
assert.equal(result.processes[1].memPercent, 10);
assert.deepEqual(seenTopCalls, ["-b -n 1"]);
});
test("listProcesses accepts BusyBox top output with a CPU column", async () => {
const conn = {
exec(command, callback) {
const execution = runProcessCommandWithBusyBoxPs(command, { topHasCpuColumn: true });
callback(null, createFakeExecStream(execution.stdout, execution));
},
};
const sessions = new Map([["openwrt", { conn, type: "ssh" }]]);
const bridge = createSystemManagerBridge({
getSessions: () => sessions,
process,
});
const result = await bridge.listProcesses(null, { sessionId: "openwrt" });
assert.equal(result.success, true);
assert.equal(result.processes.length, 2);
assert.deepEqual(result.processes.map(({ pid, cpuPercent, memPercent, command }) => ({
pid,
cpuPercent,
memPercent,
command,
})), [
{ pid: 1, cpuPercent: 2, memPercent: 1, command: "/sbin/procd" },
{ pid: 411, cpuPercent: 7, memPercent: 10, command: "/sbin/ubusd" },
]);
});
test("listProcesses accepts BusyBox top percentages without percent symbols", async () => {
const conn = {
exec(command, callback) {
const execution = runProcessCommandWithBusyBoxPs(command, {
topHasCpuColumn: true,
topUsesPercentSymbols: false,
});
callback(null, createFakeExecStream(execution.stdout, execution));
},
};
const sessions = new Map([["openwrt", { conn, type: "ssh" }]]);
const bridge = createSystemManagerBridge({
getSessions: () => sessions,
process,
});
const result = await bridge.listProcesses(null, { sessionId: "openwrt" });
assert.equal(result.success, true);
assert.deepEqual(result.processes.map(({ pid, cpuPercent, memPercent, command }) => ({
pid,
cpuPercent,
memPercent,
command,
})), [
{ pid: 1, cpuPercent: 2, memPercent: 1, command: "/sbin/procd" },
{ pid: 411, cpuPercent: 7, memPercent: 10, command: "/sbin/ubusd" },
]);
});
test("listProcesses uses a ps format that works on CentOS 7 procps", async () => {
const compatiblePsFormat = "ps -eo pid= -o ppid= -o user= -o stat= -o pcpu= -o pmem= -o rss= -o vsz= -o etime= -o args=";
const badCentos7Output = [
",ppid=,user=,stat=,pcpu=,pmem=,rss=,vsz=,etime=,args=",
" 1",
].join("\n");
const compatibleOutput = [
" 1 0 root Ss 0.0 0.0 4060 191024 2-19:23:42 /usr/lib/systemd/systemd --switched-root --system --deserialize 21",
].join("\n");
let seenCommand = "";
const conn = {
exec(command, callback) {
seenCommand = command;
const stdout = command.includes(compatiblePsFormat)
? compatibleOutput
: badCentos7Output;
callback(null, createFakeExecStream(stdout));
},
};
const sessions = new Map([["s1", { conn, type: "ssh" }]]);
const bridge = createSystemManagerBridge({
getSessions: () => sessions,
process,
});
const result = await bridge.listProcesses(null, { sessionId: "s1" });
assert.equal(result.success, true);
assert.equal(result.processes.length, 1);
assert.equal(result.processes[0].pid, 1);
assert.equal(result.processes[0].command, "/usr/lib/systemd/systemd --switched-root --system --deserialize 21");
assert.doesNotMatch(seenCommand, /head\s+-n\s+2000/);
});
test("listProcesses returns processes from the default OpenWrt BusyBox top format", async () => {
let seenCommand = "";
let seenPsCalls = [];
const conn = {
exec(command, callback) {
seenCommand = command;
const execution = runProcessCommandWithBusyBoxPs(command);
seenPsCalls = execution.calls;
callback(null, createFakeExecStream(execution.stdout, execution));
},
};
const sessions = new Map([["openwrt", { conn, type: "ssh" }]]);
const bridge = createSystemManagerBridge({
getSessions: () => sessions,
process,
});
const result = await bridge.listProcesses(null, { sessionId: "openwrt" });
assert.equal(result.success, true);
assert.equal(result.processes.length, 2);
assert.deepEqual(result.processes[0], {
pid: 1,
ppid: 0,
user: "root",
stat: "S",
cpuPercent: 2,
memPercent: 1,
rssKb: 0,
vszKb: 1356,
elapsed: "",
command: "/sbin/procd",
});
assert.equal(result.processes[1].vszKb, 99942);
assert.match(seenCommand, /ps ww/);
assert.equal(seenPsCalls.length, 1);
assert.match(seenPsCalls[0], /^-eo pid=/);
});
test("listProcesses falls back to plain BusyBox ps when wide output is unavailable", async () => {
let seenPsCalls = [];
const conn = {
exec(command, callback) {
const execution = runProcessCommandWithBusyBoxPs(command, { supportsWide: false, supportsTop: false });
seenPsCalls = execution.calls;
callback(null, createFakeExecStream(execution.stdout, execution));
},
};
const sessions = new Map([["openwrt", { conn, type: "ssh" }]]);
const bridge = createSystemManagerBridge({
getSessions: () => sessions,
process,
});
const result = await bridge.listProcesses(null, { sessionId: "openwrt" });
assert.equal(result.success, true);
assert.equal(result.processes.length, 2);
assert.equal(result.processes[0].command, "/sbin/procd");
assert.equal(result.processes[1].vszKb, 99942);
assert.equal(seenPsCalls.length, 3);
assert.match(seenPsCalls[0], /^-eo pid=/);
assert.equal(seenPsCalls[1], "ww");
assert.equal(seenPsCalls[2], "");
});
test("process listing commands do not hard-cap the visible list at 2000 entries", () => {
const source = fs.readFileSync(path.join(__dirname, "systemManagerBridge.cjs"), "utf8");
assert.doesNotMatch(source, /head\s+-n\s+2000/);
assert.doesNotMatch(source, /Select-Object\s+-First\s+2000/);
});
test("listProcesses gives ET process listing enough output buffer for dense hosts", async () => {
let seenOptions = null;
const sessions = new Map([["s1", {
type: "et",
etStatsAuth: { knownHosts: [] },
}]]);
const bridge = createSystemManagerBridge({
getSessions: () => sessions,
process,
execOnEtSession: async (_session, _command, _timeoutMs, options) => {
seenOptions = options;
return { success: true, stdout: "" };
},
});
const result = await bridge.listProcesses(null, { sessionId: "s1" });
assert.equal(result.success, true);
assert.ok(seenOptions.maxBuffer > 10 * 1024 * 1024);
});
test("listProcesses gives Windows process listing enough output buffer for dense local hosts", () => {
const source = fs.readFileSync(path.join(__dirname, "systemManagerBridge.cjs"), "utf8");
assert.match(
source,
/execOnLocalMachine\(\s*"Get-CimInstance Win32_Process[\s\S]+10000,\s*\{\s*maxBuffer:\s*PROCESS_LIST_MAX_BUFFER\s*\}/,
);
});
test("probeCapabilities reports Docker when docker is installed even if plain docker access is denied", async () => {
const conn = {
exec(command, callback) {
assert.match(command, /command -v docker/);
assert.match(command, /command -v nvidia-smi/);
assert.match(command, /command -v npu-smi/);
assert.match(command, /command -v ss/);
assert.match(command, /command -v systemctl/);
assert.doesNotMatch(command, /docker info/);
assert.doesNotMatch(command, /docker\.sock/);
callback(null, createFakeExecStream(
"__NC_OS__=Linux\n__NC_DOCKER__=1\n__NC_NVIDIA_SMI__=1\n__NC_SS__=1\n__NC_SYSTEMCTL__=1\n",
));
},
};
const sessions = new Map([["s1", { conn, type: "ssh" }]]);
const bridge = createSystemManagerBridge({
getSessions: () => sessions,
process,
});
const result = await bridge.probeCapabilities(null, { sessionId: "s1" });
assert.equal(result.success, true);
assert.equal(result.capabilities.hasDocker, true);
assert.equal(result.capabilities.hasNvidiaSmi, true);
assert.equal(result.capabilities.hasNpuSmi, false);
assert.equal(result.capabilities.hasSs, true);
assert.equal(result.capabilities.hasSystemctl, true);
});
test("setupOsc7Tracking runs the setup command through the active session executor", async () => {
let seenCommand = "";
const conn = {
exec(command, callback) {
seenCommand = command;
callback(null, createFakeExecStream("__NETCATTY_OSC7_SETUP_SHELL__=bash\n"));
},
};
const sessions = new Map([["s1", { conn, type: "ssh" }]]);
const bridge = createSystemManagerBridge({
getSessions: () => sessions,
process,
});
const result = await bridge.setupOsc7Tracking(null, {
sessionId: "s1",
command: "printf setup-script",
});
assert.equal(result.success, true);
assert.equal(result.stdout, "__NETCATTY_OSC7_SETUP_SHELL__=bash\n");
assert.equal(seenCommand, "printf setup-script");
});
test("setupOsc7Tracking reports non-zero setup exits as failures", async () => {
const conn = {
exec(_command, callback) {
callback(null, createFakeExecStream("", { code: 2, stderr: "unsupported shell\n" }));
},
};
const sessions = new Map([["s1", { conn, type: "ssh" }]]);
const bridge = createSystemManagerBridge({
getSessions: () => sessions,
process,
});
const result = await bridge.setupOsc7Tracking(null, {
sessionId: "s1",
command: "printf setup-script",
});
assert.equal(result.success, false);
assert.equal(result.code, 2);
assert.match(result.error, /unsupported shell/);
});
test("signalProcess reports non-zero kill exits as failures", async () => {
const conn = {
exec(_command, callback) {
callback(null, createFakeExecStream("", { code: 1, stderr: "kill: (99999): No such process\n" }));
},
};
const sessions = new Map([["s1", { conn, type: "ssh" }]]);
const bridge = createSystemManagerBridge({
getSessions: () => sessions,
process,
});
const result = await bridge.signalProcess(null, {
sessionId: "s1",
pid: 99999,
signal: "TERM",
});
assert.equal(result.success, false);
assert.equal(result.code, 1);
assert.match(result.error, /No such process/);
});
test("signalProcess propagates pending when the SSH channel is unavailable", async () => {
const sessions = new Map([["mosh1", { type: "mosh" }]]);
const bridge = createSystemManagerBridge({
getSessions: () => sessions,
process,
});
const result = await bridge.signalProcess(null, {
sessionId: "mosh1",
pid: 42,
signal: "TERM",
});
assert.equal(result.success, false);
assert.equal(result.pending, true);
});
test("signalProcess rejects Windows-only STOP/CONT on local win32 sessions", async () => {
const sessions = new Map([["local", { type: "local" }]]);
const bridge = createSystemManagerBridge({
getSessions: () => sessions,
process: { ...process, platform: "win32" },
});
const result = await bridge.signalProcess(null, {
sessionId: "local",
pid: 42,
signal: "STOP",
});
assert.equal(result.success, false);
assert.match(result.error, /not supported on Windows/i);
});