Skip to content

Commit b08f51c

Browse files
committed
test(core): filesystem native-parity guard (fs_probe vs native Linux)
Build fs_probe for wasm and native, run the wasm one inside an AgentOs VM via openShell, and assert its stdout matches native-Linux glibc line-for-line (stat mode bits, chmod round-trip, access rwx, truncate size, append/read integrity, and a heap-growth write loop). Guards the filesystem native-parity fixes in secure-exec.
1 parent 36e6630 commit b08f51c

1 file changed

Lines changed: 173 additions & 0 deletions

File tree

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import { spawnSync } from "node:child_process";
2+
import {
3+
copyFileSync,
4+
existsSync,
5+
mkdirSync,
6+
mkdtempSync,
7+
rmSync,
8+
writeFileSync,
9+
} from "node:fs";
10+
import { tmpdir } from "node:os";
11+
import { dirname, join, resolve } from "node:path";
12+
import { fileURLToPath } from "node:url";
13+
import { afterEach, describe, expect, test } from "vitest";
14+
import type { AgentOs } from "../src/index.js";
15+
16+
const __dirname = dirname(fileURLToPath(import.meta.url));
17+
const REPO_ROOT = resolve(__dirname, "../../..");
18+
const SECURE_EXEC_C_ROOT = resolve(
19+
REPO_ROOT,
20+
"../secure-exec/registry/native/c",
21+
);
22+
const WASM_PROBE_BINARY = resolve(SECURE_EXEC_C_ROOT, "build/fs_probe");
23+
const NATIVE_PROBE_BINARY = resolve(
24+
SECURE_EXEC_C_ROOT,
25+
"build/native/fs_probe",
26+
);
27+
const PATCHED_LIBC = resolve(
28+
SECURE_EXEC_C_ROOT,
29+
"sysroot/lib/wasm32-wasi/libc.a",
30+
);
31+
const SIDECAR_BINARY = resolve(REPO_ROOT, "target/debug/agentos-sidecar");
32+
33+
function runChecked(
34+
command: string,
35+
args: string[],
36+
options: { cwd: string; label: string },
37+
): void {
38+
const result = spawnSync(command, args, {
39+
cwd: options.cwd,
40+
encoding: "utf8",
41+
env: { ...process.env, AGENTOS_WASM_SNAPSHOT_RUNNER: "off" },
42+
});
43+
if (result.status !== 0) {
44+
throw new Error(
45+
[
46+
`${options.label} failed`,
47+
`cwd=${options.cwd}`,
48+
`command=${command} ${args.join(" ")}`,
49+
`status=${result.status}`,
50+
result.stdout,
51+
result.stderr,
52+
]
53+
.filter(Boolean)
54+
.join("\n"),
55+
);
56+
}
57+
}
58+
59+
function ensureFsProbeBuilt(): void {
60+
if (!existsSync(PATCHED_LIBC)) {
61+
runChecked("make", ["sysroot"], {
62+
cwd: SECURE_EXEC_C_ROOT,
63+
label: "failed to build patched wasi-libc sysroot",
64+
});
65+
}
66+
runChecked("make", ["build/native/fs_probe", "build/fs_probe"], {
67+
cwd: SECURE_EXEC_C_ROOT,
68+
label: "failed to build fs_probe parity fixtures",
69+
});
70+
}
71+
72+
function ensureWorkspaceSidecarBuilt(): void {
73+
runChecked("node", ["scripts/secure-exec-dep.mjs", "prepare-build"], {
74+
cwd: REPO_ROOT,
75+
label: "failed to prepare secure-exec workspace dependency",
76+
});
77+
runChecked("cargo", ["build", "-q", "-p", "agentos-sidecar"], {
78+
cwd: REPO_ROOT,
79+
label: "failed to build workspace agentos-sidecar",
80+
});
81+
process.env.AGENTOS_SIDECAR_BIN = SIDECAR_BINARY;
82+
process.env.AGENTOS_WASM_SNAPSHOT_RUNNER = "off";
83+
}
84+
85+
function materializeFsProbePackage(): string {
86+
const pkgDir = mkdtempSync(join(tmpdir(), "agentos-fs-probe-pkg-"));
87+
mkdirSync(join(pkgDir, "bin"));
88+
copyFileSync(WASM_PROBE_BINARY, join(pkgDir, "bin", "fs_probe"));
89+
writeFileSync(
90+
join(pkgDir, "package.json"),
91+
JSON.stringify({ name: "fs-probe-fixture", version: "0.0.0" }),
92+
);
93+
writeFileSync(
94+
join(pkgDir, "agentos-package.json"),
95+
JSON.stringify({ name: "fs-probe-fixture" }),
96+
);
97+
return pkgDir;
98+
}
99+
100+
function normalizeProbeOutput(output: string): string {
101+
return output
102+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
103+
.replace(/\r\n/g, "\n")
104+
.replace(/\r/g, "\n")
105+
.trimEnd();
106+
}
107+
108+
function runNativeProbe(): string {
109+
const scratchDir = mkdtempSync(join(tmpdir(), "fs-probe-native-"));
110+
try {
111+
const result = spawnSync(NATIVE_PROBE_BINARY, [scratchDir], {
112+
encoding: "utf8",
113+
});
114+
expect(result.status, result.stderr || result.stdout).toBe(0);
115+
return normalizeProbeOutput(result.stdout);
116+
} finally {
117+
rmSync(scratchDir, { force: true, recursive: true });
118+
}
119+
}
120+
121+
describe("filesystem native parity", () => {
122+
let vm: AgentOs | undefined;
123+
let shellId: string | undefined;
124+
let unsubscribeShellData: (() => void) | undefined;
125+
126+
afterEach(async () => {
127+
if (unsubscribeShellData) {
128+
unsubscribeShellData();
129+
unsubscribeShellData = undefined;
130+
}
131+
if (vm && shellId) {
132+
try {
133+
vm.closeShell(shellId);
134+
} catch {
135+
// The probe may already have exited.
136+
}
137+
}
138+
shellId = undefined;
139+
if (vm) {
140+
await vm.dispose();
141+
vm = undefined;
142+
}
143+
});
144+
145+
test("fs_probe output matches native Linux through openShell", async () => {
146+
ensureWorkspaceSidecarBuilt();
147+
ensureFsProbeBuilt();
148+
const expected = runNativeProbe();
149+
const { AgentOs } = await import("../src/index.js");
150+
151+
vm = await AgentOs.create({
152+
defaultSoftware: false,
153+
software: [materializeFsProbePackage()],
154+
});
155+
156+
let rawOutput = "";
157+
({ shellId } = vm.openShell({
158+
command: "fs_probe",
159+
args: ["/tmp/fs-probe-vm"],
160+
cols: 120,
161+
rows: 40,
162+
}));
163+
unsubscribeShellData = vm.onShellData(shellId, (data) => {
164+
rawOutput +=
165+
typeof data === "string" ? data : Buffer.from(data).toString("utf8");
166+
});
167+
168+
const status = await vm.waitShell(shellId);
169+
const actual = normalizeProbeOutput(rawOutput);
170+
expect(status, actual).toBe(0);
171+
expect(actual).toBe(expected);
172+
}, 120_000);
173+
});

0 commit comments

Comments
 (0)