diff --git a/.github/workflows/platform-vitest-main.yaml b/.github/workflows/platform-vitest-main.yaml index 83e6329b89a..ca63d9323fc 100644 --- a/.github/workflows/platform-vitest-main.yaml +++ b/.github/workflows/platform-vitest-main.yaml @@ -90,14 +90,42 @@ jobs: node-version: "22" cache: npm + - name: Setup Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: "3.14" + cache: pip + cache-dependency-path: ci/platform-vitest-macos-requirements.lock + + - name: Install macOS test dependencies + run: | + set -euo pipefail + brew install bash coreutils gawk ripgrep + printf '%s\n' \ + "$(brew --prefix bash)/bin" \ + "$(brew --prefix coreutils)/libexec/gnubin" \ + "$(brew --prefix gawk)/libexec/gnubin" \ + >>"$GITHUB_PATH" + python -m pip install \ + --only-binary=:all: \ + --require-hashes \ + --requirement ci/platform-vitest-macos-requirements.lock + - name: Show environment run: | set -euo pipefail echo "Runner: $(uname -a)" echo "Arch: $(uname -m)" sw_vers + bash --version | head -n 1 node --version npm --version + python --version + python -c 'import setuptools, yaml; print(f"setuptools={setuptools.__version__} pyyaml={yaml.__version__}")' + rg --version | head -n 1 + timeout --version | head -n 1 + stat --version | head -n 1 + awk --version | head -n 1 - name: Install dependencies run: | @@ -194,7 +222,7 @@ jobs: 'Acquire::Retries "5";' \ >/etc/apt/apt.conf.d/99github-actions-network apt-get update - apt-get install -y bash ca-certificates curl git jq lsb-release make python3 python3-pip python3-venv rsync tar unzip xz-utils + apt-get install -y bash ca-certificates curl git jq lsb-release make python3 python3-pip python3-venv ripgrep rsync tar unzip xz-utils if ! id -u "$test_user" >/dev/null 2>&1; then useradd --create-home --shell /bin/bash "$test_user" fi diff --git a/agents/hermes/generate-config.ts b/agents/hermes/generate-config.ts index 25133ff6119..d4f957ee55d 100644 --- a/agents/hermes/generate-config.ts +++ b/agents/hermes/generate-config.ts @@ -13,8 +13,9 @@ // - Base environment entries used by Hermes inside OpenShell // - Agent defaults (terminal, memory, skills, display) +import { realpathSync } from "node:fs"; import { resolve } from "node:path"; -import { pathToFileURL } from "node:url"; +import { fileURLToPath } from "node:url"; import { generateHermesConfig } from "./config/generate.ts"; export function main(): void { @@ -22,7 +23,12 @@ export function main(): void { } function isMainModule(): boolean { - return process.argv[1] ? import.meta.url === pathToFileURL(resolve(process.argv[1])).href : false; + if (!process.argv[1]) return false; + try { + return realpathSync(resolve(process.argv[1])) === realpathSync(fileURLToPath(import.meta.url)); + } catch { + return false; + } } if (isMainModule()) main(); diff --git a/ci/platform-vitest-macos-requirements.lock b/ci/platform-vitest-macos-requirements.lock new file mode 100644 index 00000000000..2a76e3cbe32 --- /dev/null +++ b/ci/platform-vitest-macos-requirements.lock @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Test-only dependencies for the macOS platform Vitest lane. Keep these hashes +# aligned with agents/langchain-deepagents-code/requirements.lock. +pyyaml==6.0.3 \ + --hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 +setuptools==82.0.1 \ + --hash=sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb diff --git a/ci/source-shape-test-budget.json b/ci/source-shape-test-budget.json index c183033958b..fd8cf670b29 100644 --- a/ci/source-shape-test-budget.json +++ b/ci/source-shape-test-budget.json @@ -286,6 +286,16 @@ "test": "cross-checks the allowlist against every production archive install boundary", "category": "security" }, + { + "file": "test/platform-vitest-main-workflow.test.ts", + "test": "keeps the WSL suite unprivileged with explicit root-only contracts", + "category": "security" + }, + { + "file": "test/platform-vitest-main-workflow.test.ts", + "test": "provisions the pinned macOS test runtime before running the full suite", + "category": "compatibility" + }, { "file": "test/plugin-vitest-project.test.ts", "test": "defines one canonical plugin project for root and standalone runs", diff --git a/src/lib/onboard/docker-driver-gateway-compat-container.test.ts b/src/lib/onboard/docker-driver-gateway-compat-container.test.ts index f578f39c619..5fe7f19ef7d 100644 --- a/src/lib/onboard/docker-driver-gateway-compat-container.test.ts +++ b/src/lib/onboard/docker-driver-gateway-compat-container.test.ts @@ -270,7 +270,10 @@ describe("docker-driver-gateway compatibility container", () => { }); it("fails closed when the configured Unix socket does not answer as a Docker daemon", async () => { - const dir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-docker-daemon-probe-")); + // Darwin limits AF_UNIX socket paths to 104 bytes. Vitest's nested temp root + // can consume most of that budget before the fixture suffix is appended. + const socketTempRoot = process.platform === "darwin" ? "/tmp" : os.tmpdir(); + const dir = fs.mkdtempSync(path.join(socketTempRoot, "nemoclaw-docker-daemon-probe-")); const socketPath = path.join(dir, "docker.sock"); const server = createServer(); await new Promise((resolve, reject) => { diff --git a/src/lib/tunnel/gateway-stop-script.test.ts b/src/lib/tunnel/gateway-stop-script.test.ts index 93fd503079a..c7b2a331138 100644 --- a/src/lib/tunnel/gateway-stop-script.test.ts +++ b/src/lib/tunnel/gateway-stop-script.test.ts @@ -13,7 +13,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { GATEWAY_STOP_SCRIPT } from "./gateway-stop-script"; // Linux-only: execute the production shell script against real processes while @@ -94,6 +94,14 @@ ${script}`; return stat.replace(/^[^)]*\) /, "").split(" ")[19]; } + async function waitForArgv0(pid: number, expected: string): Promise { + await vi.waitFor( + () => + expect(readFileSync(`/proc/${pid}/cmdline`, "utf-8").split("\0")[0] ?? "").toBe(expected), + { timeout: 5_000, interval: 10 }, + ); + } + function identityFixture( pid: number, mode = 0o600, @@ -143,6 +151,7 @@ ${script}`; "finds and kills a gateway whose argv was rewritten to bare 'openclaw' (#4951)", async () => { const pid = spawnWithArgv0("openclaw"); + await waitForArgv0(pid, "openclaw"); expect(runStopScript(stopScriptWithGatewayIdentity(pid))).toBe(0); await new Promise((r) => setTimeout(r, 300)); expect(isAlive(pid)).toBe(false); diff --git a/test/e2e/support/e2e-fixture-context.test.ts b/test/e2e/support/e2e-fixture-context.test.ts index efc781aad8e..e09638fe6bb 100644 --- a/test/e2e/support/e2e-fixture-context.test.ts +++ b/test/e2e/support/e2e-fixture-context.test.ts @@ -77,7 +77,9 @@ describe("E2E fixture primitives", () => { const artifacts = createArtifactSink(targetId, tmp); await artifacts.ensureRoot(); - expect(artifacts.rootDir).toBe(path.resolve(artifactParent, targetId)); + expect(fs.realpathSync(artifacts.rootDir)).toBe( + fs.realpathSync(path.resolve(artifactParent, targetId)), + ); for (const file of allowlistedFiles) { await artifacts.writeJson(file, { targetId, file }); } diff --git a/test/gateway-supervisor-control.test.ts b/test/gateway-supervisor-control.test.ts index ba2928a5c7a..8a9e429fd96 100644 --- a/test/gateway-supervisor-control.test.ts +++ b/test/gateway-supervisor-control.test.ts @@ -173,9 +173,14 @@ describe("gateway supervisor tracked PID handling", () => { ); expect(result.status).toBe(0); - // macOS bash 3.2 reports SIGTERM job-control notifications to stderr - // (e.g. "Terminated: 15 sleep 30") despite set +m; filter them out. - expect(result.stderr.replace(/^(?:Terminated|Killed): \d+[^\n]*\n?/gm, "")).toBe(""); + // Bash can report SIGTERM job-control notifications to stderr despite + // set +m, with an additional "bash: line N: PID" prefix on macOS. + expect( + result.stderr.replace( + /^(?:bash: line \d+: \d+\s+)?(?:Terminated|Killed): \d+[^\n]*\n?/gm, + "", + ), + ).toBe(""); expect(result.stdout).toMatch(/^\d+$/); }); diff --git a/test/helpers/vitest-watch-triggers.ts b/test/helpers/vitest-watch-triggers.ts index cd356d9b5d9..2f81e424730 100644 --- a/test/helpers/vitest-watch-triggers.ts +++ b/test/helpers/vitest-watch-triggers.ts @@ -87,6 +87,11 @@ export const vitestWatchTriggerPatterns: VitestWatchTriggerPattern[] = [ pattern: /(?:^|\/)\.github\/workflows\/e2e\.yaml$/, testsToRun: runTests(...E2E_WORKFLOW_CONTRACTS), }, + { + pattern: + /(?:^|\/)(?:\.github\/workflows\/platform-vitest-main\.yaml|ci\/platform-vitest-macos-requirements\.lock)$/, + testsToRun: runTests("test/platform-vitest-main-workflow.test.ts"), + }, ]; export function resolveVitestWatchTests(file: string): string[] { diff --git a/test/langchain-deepagents-code-managed-mcp-hardening.test.ts b/test/langchain-deepagents-code-managed-mcp-hardening.test.ts index 03a52021123..b77653b943d 100644 --- a/test/langchain-deepagents-code-managed-mcp-hardening.test.ts +++ b/test/langchain-deepagents-code-managed-mcp-hardening.test.ts @@ -30,8 +30,10 @@ function runManagedHelper(source: string) { } describe("Deep Agents managed MCP runtime hardening", () => { - it("treats only the exact empty managed projection as an absent snapshot", () => { - const result = runManagedHelper(String.raw` + it.runIf(process.platform === "linux")( + "treats only the exact empty managed projection as an absent snapshot", + () => { + const result = runManagedHelper(String.raw` import importlib.util import sys @@ -66,12 +68,15 @@ for raw in invalid: print("strict-tombstone-ok") `); - expect(result.status, result.stderr).toBe(0); - expect(result.stdout.trim()).toBe("strict-tombstone-ok"); - }); + expect(result.status, result.stderr).toBe(0); + expect(result.stdout.trim()).toBe("strict-tombstone-ok"); + }, + ); - it("rejects a same-sized fully sealed descriptor not created by this process state", () => { - const result = runManagedHelper(String.raw` + it.runIf(process.platform === "linux")( + "rejects a same-sized fully sealed descriptor not created by this process state", + () => { + const result = runManagedHelper(String.raw` import fcntl import importlib.util import os @@ -110,12 +115,15 @@ finally: print("descriptor-provenance-ok") `); - expect(result.status, result.stderr).toBe(0); - expect(result.stdout.trim()).toBe("descriptor-provenance-ok"); - }); + expect(result.status, result.stderr).toBe(0); + expect(result.stdout.trim()).toBe("descriptor-provenance-ok"); + }, + ); - it("falls back on blocked memfd with repeatable digest-bound child reads", () => { - const result = runManagedHelper(String.raw` + it.runIf(process.platform === "linux")( + "falls back on blocked memfd with repeatable digest-bound child reads", + () => { + const result = runManagedHelper(String.raw` import errno import fcntl import importlib.util @@ -223,12 +231,15 @@ print(child.managed_mcp_config_bytes(sys.argv[2]).decode(), end="") print("anonymous-fallback-ok") `); - expect(result.status, result.stderr).toBe(0); - expect(result.stdout.trim()).toBe("anonymous-fallback-ok"); - }); + expect(result.status, result.stderr).toBe(0); + expect(result.stdout.trim()).toBe("anonymous-fallback-ok"); + }, + ); - it("fails closed without O_TMPFILE and does not mask unrelated memfd errors", () => { - const result = runManagedHelper(String.raw` + it.runIf(process.platform === "linux")( + "fails closed without O_TMPFILE and does not mask unrelated memfd errors", + () => { + const result = runManagedHelper(String.raw` import errno import importlib.util import os @@ -329,7 +340,8 @@ with tempfile.TemporaryDirectory() as tempdir: print("fallback-fail-closed-ok") `); - expect(result.status, result.stderr).toBe(0); - expect(result.stdout.trim()).toBe("fallback-fail-closed-ok"); - }); + expect(result.status, result.stderr).toBe(0); + expect(result.stdout.trim()).toBe("fallback-fail-closed-ok"); + }, + ); }); diff --git a/test/local-credential-helper-suite.ts b/test/local-credential-helper-suite.ts index 3bab1f15987..184b0d0bcf9 100644 --- a/test/local-credential-helper-suite.ts +++ b/test/local-credential-helper-suite.ts @@ -997,7 +997,7 @@ export function registerLocalCredentialHelperTests(group: LocalCredentialHelperT USERPROFILE: accountHome, } : { HOME: accountHome, PWD: commandCwd }; - expect(observed.cwd).toBe(commandCwd); + expect(fs.realpathSync(observed.cwd)).toBe(fs.realpathSync(commandCwd)); expect( Object.fromEntries(Object.entries(observed.environment).filter(([, value]) => value)), ).toEqual(expectedEnvironment); diff --git a/test/onboard-model-router.test.ts b/test/onboard-model-router.test.ts index d6fccf00bed..decbac63033 100644 --- a/test/onboard-model-router.test.ts +++ b/test/onboard-model-router.test.ts @@ -221,8 +221,8 @@ describe("onboard Model Router setup", () => { }).trim(); assert.match(sourceHead, /^[0-9a-f]{40}$/i); assert.equal( - runCapture(["git", "-C", routerDir, "rev-parse", "--show-toplevel"]).trim(), - routerDir, + fs.realpathSync(runCapture(["git", "-C", routerDir, "rev-parse", "--show-toplevel"]).trim()), + fs.realpathSync(routerDir), ); fs.mkdirSync(path.dirname(managedCommand), { recursive: true }); fs.writeFileSync(managedCommand, "#!/usr/bin/env sh\nexit 0\n", { mode: 0o755 }); @@ -349,7 +349,7 @@ describe("onboard Model Router setup", () => { "--output", litellmConfigPath, ]); - assert.equal(proxyConfig.cwd, blueprintDir); + assert.equal(fs.realpathSync(proxyConfig.cwd), fs.realpathSync(blueprintDir)); assert.deepEqual(proxy.args, [ "proxy", "--litellm-config", @@ -361,7 +361,7 @@ describe("onboard Model Router setup", () => { "--port", String(port), ]); - assert.equal(proxy.cwd, blueprintDir); + assert.equal(fs.realpathSync(proxy.cwd), fs.realpathSync(blueprintDir)); assert.deepEqual(proxy.env, { ROUTER_API_KEY: "router-secret", OPENAI_API_KEY: "router-secret", diff --git a/test/platform-vitest-main-workflow.test.ts b/test/platform-vitest-main-workflow.test.ts new file mode 100644 index 00000000000..d3c3118ab57 --- /dev/null +++ b/test/platform-vitest-main-workflow.test.ts @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, expect, it } from "vitest"; + +import { + readRepoText, + readYaml, + type Workflow, + type WorkflowJob, + type WorkflowStep, +} from "./helpers/e2e-workflow-contract"; + +const WORKFLOW_PATH = ".github/workflows/platform-vitest-main.yaml"; +const MACOS_REQUIREMENTS_PATH = "ci/platform-vitest-macos-requirements.lock"; +const workflow = readYaml(WORKFLOW_PATH); + +function job(name: string): WorkflowJob { + const candidate = workflow.jobs[name]; + expect(candidate, `missing ${name} job`).toBeDefined(); + return candidate; +} + +function step(jobName: string, name: string): WorkflowStep { + const candidate = job(jobName).steps?.find((entry) => entry.name === name); + expect(candidate, `missing ${jobName} step ${name}`).toBeDefined(); + return candidate!; +} + +describe("platform Vitest main workflow", () => { + // source-shape-contract: compatibility -- macOS must use the same modern shell/tool semantics as the Linux sandbox fixtures + it("provisions the pinned macOS test runtime before running the full suite", () => { + const stepNames = job("macos-vitest").steps?.map((entry) => entry.name) ?? []; + const setupPython = step("macos-vitest", "Setup Python"); + const install = step("macos-vitest", "Install macOS test dependencies"); + const run = install.run ?? ""; + + expect(job("macos-vitest")["timeout-minutes"]).toBe(60); + expect(stepNames.indexOf("Setup Python")).toBeLessThan( + stepNames.indexOf("Install macOS test dependencies"), + ); + expect(stepNames.indexOf("Install macOS test dependencies")).toBeLessThan( + stepNames.indexOf("Run full Vitest suite on macOS"), + ); + expect(setupPython.uses).toBe("actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1"); + expect(setupPython.with).toMatchObject({ + "python-version": "3.14", + cache: "pip", + "cache-dependency-path": MACOS_REQUIREMENTS_PATH, + }); + for (const dependency of ["bash", "coreutils", "gawk", "ripgrep"]) { + expect(run).toMatch(new RegExp(`brew install[^\\n]*\\b${dependency}\\b`, "u")); + } + expect(run).toContain("$(brew --prefix bash)/bin"); + expect(run).toContain("$(brew --prefix coreutils)/libexec/gnubin"); + expect(run).toContain("$(brew --prefix gawk)/libexec/gnubin"); + expect(run).toContain("--only-binary=:all:"); + expect(run).toContain("--require-hashes"); + expect(run).toContain(`--requirement ${MACOS_REQUIREMENTS_PATH}`); + + const requirements = readRepoText(MACOS_REQUIREMENTS_PATH); + expect(requirements).toContain("pyyaml==6.0.3"); + expect(requirements).toContain( + "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", + ); + expect(requirements).toContain("setuptools==82.0.1"); + expect(requirements).toContain( + "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", + ); + }); + + // source-shape-contract: security -- ordinary tests stay non-root while the five UID-0 contracts remain isolated + it("keeps the WSL suite unprivileged with explicit root-only contracts", () => { + const stepNames = job("wsl-vitest").steps?.map((entry) => entry.name) ?? []; + const install = step("wsl-vitest", "Install Ubuntu dependencies").run ?? ""; + const fullSuite = step("wsl-vitest", "Run full Vitest suite in WSL").run ?? ""; + const rootSuite = step("wsl-vitest", "Run root-required Vitest contracts in WSL").run ?? ""; + + expect(job("wsl-vitest")["timeout-minutes"]).toBe(60); + expect(stepNames.indexOf("Install Ubuntu dependencies")).toBeLessThan( + stepNames.indexOf("Run full Vitest suite in WSL"), + ); + expect(install).toMatch(/apt-get install[^\n]*\bpython3-venv\b/u); + expect(install).toMatch(/apt-get install[^\n]*\bripgrep\b/u); + expect(install).not.toMatch(/\bsudo\b|sudoers|NOPASSWD/u); + expect(fullSuite).toContain("--user $env:WSL_TEST_USER"); + expect(fullSuite).toContain("NEMOCLAW_EXEC_TIMEOUT=60000"); + expect(fullSuite).toContain("NEMOCLAW_TEST_TIMEOUT=60000"); + expect(fullSuite).not.toMatch(/\bsudo\b|sudoers|NOPASSWD/u); + expect(rootSuite).toContain("--user root"); + expect([...rootSuite.matchAll(/-t '([^']+)'/gu)].map((match) => match[1])).toEqual([ + "keeps the locked Hermes entry sticky-protected|lets a sandbox-group peer create state", + "requires both fixed files to match|reclaims a root-owned collapsed config|leaves a root-owned recovery baseline untouched", + ]); + }); +}); diff --git a/test/pr-review-advisor.test.ts b/test/pr-review-advisor.test.ts index 305cb19fd18..ff6fe1a35bf 100644 --- a/test/pr-review-advisor.test.ts +++ b/test/pr-review-advisor.test.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import fs from "node:fs"; +import { tmpdir } from "node:os"; import path from "node:path"; import Ajv2020 from "ajv/dist/2020.js"; import { afterEach, describe, expect, it, vi } from "vitest"; @@ -702,7 +703,7 @@ diff --git a/test/plain-logic.test.ts b/test/plain-logic.test.ts it("skips symlinked changed test files in static test inventory", () => { const tmp = fs.mkdtempSync(path.join(ROOT, ".tmp-pr-advisor-symlink-")); - const outside = fs.mkdtempSync(path.join(ROOT, "..", ".tmp-pr-advisor-outside-")); + const outside = fs.mkdtempSync(path.join(tmpdir(), "nemoclaw-pr-advisor-outside-")); const outsideFile = path.join(outside, "secret.test.ts"); const linkPath = path.join(tmp, "linked.test.ts"); fs.writeFileSync(outsideFile, 'describe("secret outside test", () => {});\n'); diff --git a/test/pr-workflow-contract.test.ts b/test/pr-workflow-contract.test.ts index 0a3052779fc..26b2ea875d0 100644 --- a/test/pr-workflow-contract.test.ts +++ b/test/pr-workflow-contract.test.ts @@ -13,6 +13,7 @@ import { type WorkflowJob, type WorkflowStep, } from "./helpers/e2e-workflow-contract"; +import { execTimeout } from "./helpers/timeouts"; type CiWorkflow = { "run-name"?: string; @@ -1105,7 +1106,7 @@ describe("pull request and main workflow contracts", () => { const result = spawnSync("bash", ["-c", resolver ?? ""], { cwd: process.cwd(), encoding: "utf8", - timeout: 10_000, + timeout: execTimeout(), env: { ...process.env, DOCKER_LOG: dockerLog, diff --git a/test/repro-5978-policy-denial-hint.test.ts b/test/repro-5978-policy-denial-hint.test.ts index 7a54b3bbef0..cc0ed0fc484 100644 --- a/test/repro-5978-policy-denial-hint.test.ts +++ b/test/repro-5978-policy-denial-hint.test.ts @@ -96,15 +96,18 @@ function runInPty(snippet: string, env: NodeJS.ProcessEnv): { stdout: string; st const file = path.join(fs.mkdtempSync(path.join(os.tmpdir(), "nc-5978-")), "snippet.sh"); fs.writeFileSync(file, snippet); try { - const result = spawnSync( - "script", - ["-qec", `bash --noprofile --norc -i ${file}`, "/dev/null"], - { - encoding: "utf-8", - timeout: 10_000, - env: { ...process.env, ...env, SHLVL: "9" }, - }, - ); + const scriptArgs = + process.platform === "darwin" + ? ["-q", "/dev/null", "bash", "--noprofile", "--norc", "-i", file] + : ["-qec", `bash --noprofile --norc -i ${file}`, "/dev/null"]; + const result = spawnSync("script", scriptArgs, { + encoding: "utf-8", + timeout: 10_000, + env: { ...process.env, ...env, SHLVL: "9" }, + // Darwin's script(1) rejects Node's default socket-backed stdin. The + // snippet is read from file, so a null stdin preserves the PTY contract. + stdio: ["ignore", "pipe", "pipe"], + }); return { stdout: result.stdout ?? "", status: result.status ?? -1 }; } finally { fs.rmSync(path.dirname(file), { recursive: true, force: true }); diff --git a/test/sandbox-init.test.ts b/test/sandbox-init.test.ts index 1973171feef..b18f46aaa87 100644 --- a/test/sandbox-init.test.ts +++ b/test/sandbox-init.test.ts @@ -697,31 +697,34 @@ EOF } }); - it("bypasses shadowed ulimit functions for nproc and nofile enforcement and verification", () => { - const nprocLimit = process.platform === "darwin" ? 4000 : 4096; - const { stdout } = runWithLib( - [ - `NEMOCLAW_SANDBOX_NPROC_LIMIT=${nprocLimit}`, - "ulimit() {", - ' case "$1:$#" in', - " -Su:2 | -Hu:2 | -Sn:2 | -Hn:2) return 0 ;;", - " -Su:1 | -Hu:1 | -Sn:1 | -Hn:1) printf '%s\\n' 999999; return 0 ;;", - " esac", - " return 0", - "}", - "harden_resource_limits --quiet", - "verify_resource_limits", - 'printf "shadow=%s\\n" "$(type -t ulimit)"', - 'printf "nproc=%s\\n" "$(builtin ulimit -u)"', - 'printf "nofile=%s\\n" "$(builtin ulimit -n)"', - ].join("\n"), - ); - expect(stdout).toContain("shadow=function"); - expect(stdout).toContain(`nproc=${nprocLimit}`); - const nofile = Number(stdout.match(/nofile=(\d+)/)?.[1] ?? "NaN"); - expect(nofile).toBeGreaterThan(0); - expect(nofile).toBeLessThanOrEqual(65536); - }); + it.runIf(process.platform === "linux")( + "bypasses shadowed ulimit functions for nproc and nofile enforcement and verification", + () => { + const nprocLimit = 4096; + const { stdout } = runWithLib( + [ + `NEMOCLAW_SANDBOX_NPROC_LIMIT=${nprocLimit}`, + "ulimit() {", + ' case "$1:$#" in', + " -Su:2 | -Hu:2 | -Sn:2 | -Hn:2) return 0 ;;", + " -Su:1 | -Hu:1 | -Sn:1 | -Hn:1) printf '%s\\n' 999999; return 0 ;;", + " esac", + " return 0", + "}", + "harden_resource_limits --quiet", + "verify_resource_limits", + 'printf "shadow=%s\\n" "$(type -t ulimit)"', + 'printf "nproc=%s\\n" "$(builtin ulimit -u)"', + 'printf "nofile=%s\\n" "$(builtin ulimit -n)"', + ].join("\n"), + ); + expect(stdout).toContain("shadow=function"); + expect(stdout).toContain(`nproc=${nprocLimit}`); + const nofile = Number(stdout.match(/nofile=(\d+)/)?.[1] ?? "NaN"); + expect(nofile).toBeGreaterThan(0); + expect(nofile).toBeLessThanOrEqual(65536); + }, + ); it("is best-effort: exits 0 and warns when ulimit fails", () => { const { stdout } = runWithLib( diff --git a/test/vitest-watch-triggers.test.ts b/test/vitest-watch-triggers.test.ts index 86ebe80392c..d00c8d62b31 100644 --- a/test/vitest-watch-triggers.test.ts +++ b/test/vitest-watch-triggers.test.ts @@ -55,6 +55,8 @@ const OPAQUE_INPUTS = [ "test/e2e/manifests/openclaw-nvidia.yaml", "test/e2e/docs/parity-inventory.generated.json", ".github/workflows/e2e.yaml", + ".github/workflows/platform-vitest-main.yaml", + "ci/platform-vitest-macos-requirements.lock", ] as const; function triggeredBy(relativePath: string): string[] { @@ -99,6 +101,12 @@ describe("Vitest opaque-input watch triggers", () => { "test/e2e/support/e2e-migration-policy.test.ts", ]); expect(triggeredBy(".github/workflows/e2e.yaml")).toEqual(E2E_WORKFLOW_CONTRACTS); + expect(triggeredBy(".github/workflows/platform-vitest-main.yaml")).toEqual([ + "test/platform-vitest-main-workflow.test.ts", + ]); + expect(triggeredBy("ci/platform-vitest-macos-requirements.lock")).toEqual([ + "test/platform-vitest-main-workflow.test.ts", + ]); }); it("returns only concrete test files that exist (#6692)", () => {