// node-login-shell's execute over real child processes (novox/hq ADR 0204 ยง4, to-be 41 WP3): bounded // in time with its whole process group ended, bounded in output and saying so, run in the account's // home, answering the exit status. `sh` stands in for zsh where the test needs no zsh of its own, so // the bounds are proven wherever the tests run; one test runs zsh itself when it is installed. import { test } from "node:test"; import assert from "node:assert/strict"; import { execFileSync } from "node:child_process"; import { mkdtempSync, readFileSync, realpathSync, writeFileSync, mkdirSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { DEFAULT_TIMEOUT_SECONDS, MAX_TIMEOUT_SECONDS, commandEnv, execute, homeOf, timeoutOf, zshFile } from "../shell.ts"; const dir = realpathSync(mkdtempSync(join(tmpdir(), "zsh-execute-"))); const base = { shell: "sh", cwd: dir, env: { PATH: process.env.PATH, HOME: dir }, timeoutSeconds: 5 }; function alive(pid: number): boolean { try { process.kill(pid, 0); return true; } catch { return false; } } async function gone(pid: number, ms = 2000): Promise { const until = Date.now() + ms; while (Date.now() < until) { if (!alive(pid)) return true; await new Promise((r) => setTimeout(r, 50)); } return !alive(pid); } test("the exit status and both streams are answered", async () => { const r = await execute("echo out; echo err >&2; exit 3", base); assert.equal(r.status, 3); assert.equal(r.stdout, "out\n"); assert.equal(r.stderr, "err\n"); assert.equal(r.timed_out, false); assert.deepEqual(r.truncated, { stdout: false, stderr: false }); }); test("it runs in the directory it is given", async () => { const r = await execute("pwd", base); assert.equal(r.stdout.trim(), dir); assert.equal(r.cwd, dir); }); test("on timeout the whole process group is ended, a backgrounded grandchild included", async () => { const pidfile = join(dir, "grandchild.pid"); const started = Date.now(); const r = await execute(`sh -c 'sleep 100 & echo $! > ${pidfile}; wait'`, { ...base, timeoutSeconds: 0.5 }); assert.equal(r.timed_out, true); assert.ok(Date.now() - started < 5000, "answered soon after the timeout, not when the sleep ended"); assert.equal(r.status, null); assert.equal(r.signal, "SIGKILL"); const pid = Number(readFileSync(pidfile, "utf8").trim()); assert.ok(pid > 0); assert.ok(await gone(pid), `the grandchild ${pid} is gone`); }); test("output beyond the bound is cut and the answer says so", async () => { const r = await execute("head -c 300000 /dev/zero | tr '\\0' a; head -c 10 /dev/zero | tr '\\0' b >&2", { ...base, capBytes: 1024 }); assert.equal(r.stdout.length, 1024); assert.equal(r.truncated.stdout, true); assert.equal(r.stderr, "bbbbbbbbbb"); assert.equal(r.truncated.stderr, false); assert.equal(r.status, 0); }); test("a job left in the background does not hold the answer", async () => { const pidfile = join(dir, "background.pid"); const started = Date.now(); const r = await execute(`sleep 30 & echo $! > ${pidfile}; echo done`, base); assert.ok(Date.now() - started < 3000); assert.equal(r.stdout, "done\n"); assert.equal(r.status, 0); process.kill(Number(readFileSync(pidfile, "utf8").trim()), "SIGKILL"); }); test("zsh runs the command as a login shell, when zsh is here", async (t) => { try { execFileSync("zsh", ["-c", "true"]); } catch { t.skip("zsh is not installed here"); return; } const home = join(dir, "home"); mkdirSync(home, { recursive: true }); writeFileSync(join(home, ".zshenv"), "export FROM_ZSHENV=yes\n"); const r = await execute("print -r -- $FROM_ZSHENV; [[ -o login ]] && print login", { cwd: home, env: { PATH: process.env.PATH, HOME: home, ZDOTDIR: home }, timeoutSeconds: 5 }); assert.equal(r.stdout, "yes\nlogin\n"); assert.equal(r.status, 0); }); test("the timeout is held below the runtime's call limit", () => { assert.equal(timeoutOf(undefined), DEFAULT_TIMEOUT_SECONDS); assert.equal(timeoutOf(5), 5); assert.equal(timeoutOf(600), MAX_TIMEOUT_SECONDS); assert.equal(timeoutOf(-1), DEFAULT_TIMEOUT_SECONDS); assert.equal(timeoutOf("x"), DEFAULT_TIMEOUT_SECONDS); assert.ok(MAX_TIMEOUT_SECONDS < 30 && DEFAULT_TIMEOUT_SECONDS <= MAX_TIMEOUT_SECONDS); }); test("the command's environment drops the mesh's words and gains the account's session words when they exist", () => { const env = { PATH: "/usr/bin", HOME: "/home/op", MESH_OPERATOR_ACCOUNT: "op", MESH_OPERATOR_HOME: "/home/op", MESH_ANYTHING: "x" }; const there = commandEnv(env, 1000, (p) => p === "/run/user/1000"); assert.deepEqual(there, { PATH: "/usr/bin", HOME: "/home/op", XDG_RUNTIME_DIR: "/run/user/1000", DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/1000/bus" }); const absent = commandEnv(env, 1000, () => false); assert.deepEqual(absent, { PATH: "/usr/bin", HOME: "/home/op" }); assert.equal(homeOf(env), "/home/op"); assert.equal(homeOf({ HOME: "/h" }), "/h"); }); test("zsh_config counts the mesh's block in a file", async () => { const path = join(dir, ".zshrc"); writeFileSync(path, "# BEGIN mesh zsh.rc\na\nb\nc\n# END mesh zsh.rc\nmine\n"); const r = await zshFile(path); assert.equal(r.lines, 6); assert.equal(r.mesh_block_lines, 3); assert.deepEqual(await zshFile(join(dir, "absent")), { path: join(dir, "absent"), exists: false }); });