import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { PROVIDER_SANDBOX_ISOLATION_ARGUMENTS, PROVIDER_SANDBOX_UNAVAILABLE, assertProviderSandboxUsable, probeProviderSandbox, providerSandboxDecision, providerSandboxProbeArguments, } from "../../scripts/lib/provider-sandbox-probe.ts"; const roots: string[] = []; afterEach(async () => { await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true }))); }); async function stubSandboxBinary(body: string): Promise { const root = await mkdtemp(path.join(tmpdir(), "sandbox-probe-")); roots.push(root); const binary = path.join(root, "bwrap"); await writeFile(binary, `#!/bin/sh\n${body}\n`); await chmod(binary, 0o755); return binary; } describe("provider sandbox probe", () => { it("probes execution rather than the existence of the binary", async () => { const refuses = await stubSandboxBinary( 'echo "bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted" >&2\nexit 1', ); const probe = probeProviderSandbox(refuses); expect(probe.usable).toBe(false); expect(probe.usable ? "" : probe.reason).toContain( "bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted", ); expect(probeProviderSandbox(await stubSandboxBinary("exit 0"))).toEqual({ usable: true }); }); it("reports a missing sandbox binary as its own reason", async () => { const missing = path.join(await mkdtemp(path.join(tmpdir(), "sandbox-absent-")), "bwrap"); roots.push(path.dirname(missing)); const probe = probeProviderSandbox(missing); expect(probe.usable).toBe(false); expect(probe.usable ? "" : probe.reason).toMatch(/is not installed/u); }); it("probes with the same isolation options the real provider run applies", async () => { const source = await readFile( new URL("../../scripts/run-and-validate-provider.ts", import.meta.url), "utf8", ); // The run builds its bubblewrap vector from the shared constant and // declares no isolation option of its own, so an option cannot be added to // the run without the probe having to clear it as well. expect(source).toContain("...PROVIDER_SANDBOX_ISOLATION_ARGUMENTS"); expect(source.match(/"(?:--unshare-[a-z]+|--as-pid-1|--new-session|--die-with-parent)"/gu)) .toBeNull(); expect(PROVIDER_SANDBOX_ISOLATION_ARGUMENTS).toEqual( expect.arrayContaining([ "--unshare-pid", "--unshare-ipc", "--unshare-uts", "--unshare-net", "--as-pid-1", ]), ); const probeArguments = providerSandboxProbeArguments(); expect(probeArguments.slice(0, PROVIDER_SANDBOX_ISOLATION_ARGUMENTS.length)).toEqual([ ...PROVIDER_SANDBOX_ISOLATION_ARGUMENTS, ]); expect(probeArguments.at(-1)).toBe("/bin/true"); }); it("skips locally and fails in CI on the same unusable sandbox", () => { const probe = { usable: false, reason: "bwrap: setting up uid map: Permission denied" } as const; expect(providerSandboxDecision(probe, {})).toEqual({ outcome: "skip", reason: `${PROVIDER_SANDBOX_UNAVAILABLE}: bwrap: setting up uid map: Permission denied`, }); expect(providerSandboxDecision(probe, { CI: "true" })).toEqual({ outcome: "fail", reason: `${PROVIDER_SANDBOX_UNAVAILABLE}: bwrap: setting up uid map: Permission denied`, }); expect(providerSandboxDecision({ usable: true }, { CI: "true" })).toEqual({ outcome: "run" }); }); it("asserts only the CI failure outcome", () => { const probe = { usable: false, reason: "denied" } as const; expect(() => assertProviderSandboxUsable(providerSandboxDecision(probe, { CI: "true" }))) .toThrow(new RegExp(PROVIDER_SANDBOX_UNAVAILABLE, "u")); expect(() => assertProviderSandboxUsable(providerSandboxDecision(probe, {}))).not.toThrow(); expect(() => assertProviderSandboxUsable(providerSandboxDecision({ usable: true }, { CI: "true" }))) .not.toThrow(); }); });