mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-23 15:37:44 +00:00
* test(memory-core): deslop z0615 tests * test(memory-core): deslop z0615 tests * test(memory-core): deslop z0615 tests * test(gateway): deslop z0618 tests * test(gateway): deslop z0618 tests * test(gateway): deslop z0618 tests * test(cua-computer): deslop z0620 tests * test(cua-computer): deslop z0620 tests * test(channels): deslop z0617 tests * test(channels): deslop z0617 tests * test(channels): deslop z0617 tests * test(line): deslop z0621 tests * test(line): deslop z0621 tests * test(agents): deslop z0622 tests * test(agents): deslop z0622 tests * test(cron): deslop z0623 tests * test(cron): deslop z0623 tests * test(cron): deslop z0623 tests * test(talk): deslop z0625 tests * test(talk): deslop z0625 tests * test(zalo): deslop z0627 tests * test(infra): deslop z0619 tests * test(infra): further deslop z0619 tests * test(state): deslop z0624 tests * test(state): deslop z0624 tests * test(state): deslop z0624 tests * test(scripts): deslop z0626 tests * test(scripts): deslop z0626 tests * test(scripts): deslop z0626 tests * test(mcp): deslop z0628 tests * test: repair d357 fixture types and retain cleanup contracts * test(channels): use synchronous ingress context fixtures
115 lines
4.5 KiB
TypeScript
115 lines
4.5 KiB
TypeScript
import { EventEmitter } from "node:events";
|
|
import fs from "node:fs";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { observePostCoreCommand } from "../../scripts/e2e/lib/plugin-update/process-observer.mjs";
|
|
|
|
const argv = "node\0entry.js\0update\0--json\0";
|
|
const workerArgv =
|
|
"node\0/opt/openclaw/dist/infra/update-migrated-finalize.worker.js\0--post-core\0";
|
|
const marker = "OPENCLAW_UPDATE_POST_CORE=1\0";
|
|
const procError = (code: string) => Object.assign(new Error(`proc read: ${code}`), { code });
|
|
|
|
describe("plugin update command observation", () => {
|
|
let files: Map<string, string | Error>;
|
|
let child: EventEmitter & { pid: number };
|
|
const setProc = (pid: number, values: Record<string, string | Error>) => {
|
|
for (const [file, value] of Object.entries(values)) {
|
|
files.set(`/proc/${pid}/${file}`, value);
|
|
}
|
|
};
|
|
const finish = (code = 0) => child.emit("exit", code, null);
|
|
const observed = (pid: string, cmdline = argv, postCore: boolean | null = true) => ({
|
|
pid,
|
|
argv: cmdline.split("\0").filter(Boolean),
|
|
postCore,
|
|
});
|
|
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
child = Object.assign(new EventEmitter(), { pid: 10 });
|
|
files = new Map();
|
|
setProc(10, { "task/10/children": "11 12" });
|
|
for (const pid of [11, 12]) {
|
|
setProc(pid, { cmdline: argv, environ: marker, [`task/${pid}/children`]: "" });
|
|
}
|
|
const readFile = fs.readFileSync;
|
|
vi.spyOn(fs, "readFileSync").mockImplementation((file, options) => {
|
|
if (!String(file).startsWith("/proc/")) {
|
|
return readFile(file, options);
|
|
}
|
|
const value = files.get(String(file)) ?? procError("ENOENT");
|
|
if (value instanceof Error) {
|
|
throw value;
|
|
}
|
|
return value;
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
const timers = vi.getTimerCount();
|
|
vi.useRealTimers();
|
|
vi.restoreAllMocks();
|
|
expect(timers).toBe(0);
|
|
});
|
|
|
|
it("ESRCH leaves evidence unknown and keeps scanning readable siblings and grandchildren", async () => {
|
|
setProc(11, { environ: procError("ESRCH"), "task/11/children": "13" });
|
|
setProc(13, { cmdline: argv, environ: marker });
|
|
const outcome = observePostCoreCommand(child, "update");
|
|
await vi.advanceTimersByTimeAsync(20);
|
|
finish();
|
|
const result = await outcome;
|
|
expect(result.code).toBe(0);
|
|
expect(result.children).toEqual([observed("11", argv, null), observed("13"), observed("12")]);
|
|
});
|
|
|
|
it("does not manufacture positive handoff evidence when cmdline is inaccessible", async () => {
|
|
files.set("/proc/10/task/10/children", "11");
|
|
setProc(11, { environ: "OPENCLAW_UPDATE_POST_CORE=0\0", cmdline: procError("EACCES") });
|
|
const outcome = observePostCoreCommand(child, "update");
|
|
await vi.advanceTimersByTimeAsync(20);
|
|
finish();
|
|
expect((await outcome).children.some((entry) => entry.postCore)).toBe(false);
|
|
});
|
|
|
|
it("retains positive evidence and worker argv after process.title changes and the process exits", async () => {
|
|
for (const pid of [11, 12]) {
|
|
setProc(pid, { cmdline: workerArgv });
|
|
}
|
|
const outcome = observePostCoreCommand(child, "update");
|
|
await vi.advanceTimersByTimeAsync(20);
|
|
setProc(11, { cmdline: "openclaw-update\0", environ: procError("EACCES") });
|
|
files.set("/proc/12/cmdline", procError("ESRCH"));
|
|
await vi.advanceTimersByTimeAsync(20);
|
|
files.set("/proc/10/task/10/children", "");
|
|
await vi.advanceTimersByTimeAsync(20);
|
|
finish(7);
|
|
const result = await outcome;
|
|
expect(result.code).toBe(7);
|
|
expect(result.children).toEqual(["11", "12"].map((pid) => observed(pid, workerArgv)));
|
|
});
|
|
|
|
it("joins unexpected observer failure with command exit instead of throwing from the timer", async () => {
|
|
const failure = procError("EIO");
|
|
files.set("/proc/11/environ", failure);
|
|
const outcome = observePostCoreCommand(child, "update-denied");
|
|
const rejected = expect(outcome).rejects.toMatchObject({
|
|
message: "update-denied observation failed after command exit 7 (null)",
|
|
cause: failure,
|
|
});
|
|
await vi.advanceTimersByTimeAsync(20);
|
|
expect(child.listenerCount("exit")).toBe(1);
|
|
finish(7);
|
|
await rejected;
|
|
expect(child.listenerCount("exit")).toBe(0);
|
|
});
|
|
|
|
it("cleans up observation when command spawning fails", async () => {
|
|
const failure = procError("ENOENT");
|
|
const outcome = observePostCoreCommand(child, "update");
|
|
const rejected = expect(outcome).rejects.toBe(failure);
|
|
child.emit("error", failure);
|
|
await rejected;
|
|
expect(child.listenerCount("exit")).toBe(0);
|
|
});
|
|
});
|