File size: 1,856 Bytes
d197cf3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import { randomUUID } from "node:crypto";
import { existsSync } from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js";
import { prepareHostedStopExecutor } from "./hosted-stop-executor.js";

// Requires an isolated systemd VM with permission to create transient scopes.
describe.runIf(
  process.platform === "linux" && process.getuid?.() === 0 && existsSync("/run/systemd/system"),
)("hosted stop executor native systemd scope", () => {
  const dirs = useAutoCleanupTempDirTracker(afterEach);
  it("retains literal shell arguments across scope placement and exec", async () => {
    const output = path.join(dirs.make("hosted-stop-scope-"), "argv");
    const unit = `openclaw-stop-test-${randomUUID()}.scope`;
    const args = ["$@", "$$", "$HOME", "two words", "'\"; echo bad"];
    const executor = await prepareHostedStopExecutor({
      command: [
        process.execPath,
        "-e",
        "require('node:fs').writeFileSync(process.argv[1], JSON.stringify(process.argv.slice(2)))",
        output,
        ...args,
      ],
      scopeArgs: [
        "--system",
        "--scope",
        "--quiet",
        "--collect",
        "--no-ask-password",
        `--unit=${unit}`,
      ],
      env: process.env,
      signal: new AbortController().signal,
      assertCurrent: () => {},
      verifyPlacement: async (pid) => {
        expect(await fs.readFile(`/proc/${pid}/cgroup`, "utf8")).toContain(`/${unit}\n`);
      },
    });
    try {
      await expect(executor.execute(() => {})).resolves.toMatchObject({ disposition: "accepted" });
      expect(JSON.parse(await fs.readFile(output, "utf8"))).toEqual(args);
    } finally {
      await executor.dispose();
    }
  });
});