From 106961b51351e3f6cbb10839cb357b130e1a477b Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 20 Jun 2026 16:51:20 +0200 Subject: [PATCH] fix(e2e): resolve mounted macOS desktop homes --- scripts/e2e/parallels/common.ts | 1 + scripts/e2e/parallels/macos-smoke.ts | 7 ++- scripts/e2e/parallels/macos-users.ts | 13 ++++++ scripts/e2e/parallels/npm-update-smoke.ts | 7 ++- .../parallels-npm-update-smoke.test.ts | 46 +++++++++++++++++++ test/scripts/parallels-smoke-model.test.ts | 11 +++++ 6 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 scripts/e2e/parallels/macos-users.ts diff --git a/scripts/e2e/parallels/common.ts b/scripts/e2e/parallels/common.ts index 7e452d4040cb..be1e3a86cda0 100644 --- a/scripts/e2e/parallels/common.ts +++ b/scripts/e2e/parallels/common.ts @@ -4,6 +4,7 @@ export * from "./env-limits.ts"; export * from "./host-command.ts"; export * from "./host-server.ts"; export * from "./lane-runner.ts"; +export * from "./macos-users.ts"; export * from "./package-artifact.ts"; export * from "./parallels-vm.ts"; export * from "./plugin-isolation.ts"; diff --git a/scripts/e2e/parallels/macos-smoke.ts b/scripts/e2e/parallels/macos-smoke.ts index ea67d75a301f..6f21456ddfb1 100755 --- a/scripts/e2e/parallels/macos-smoke.ts +++ b/scripts/e2e/parallels/macos-smoke.ts @@ -10,8 +10,10 @@ import { currentRunningSnapshotInfo, extractLastOpenClawVersionFromLog, makeTempDir, + isLikelyMacosDesktopHome, packageBuildCommitFromTgz, packageVersionFromTgz, + parseMacosDsclUserHomeLine, packOpenClaw, parseMode, parseProvider, @@ -690,10 +692,11 @@ exec node "$entry" ${argv}`, }, ).stdout.replaceAll("\r", ""); for (const line of users.split("\n")) { - const [user, home] = line.trim().split(/\s+/); + const parsed = parseMacosDsclUserHomeLine(line); + const user = parsed?.user; if ( user && - home?.startsWith("/Users/") && + isLikelyMacosDesktopHome(parsed?.home) && !user.startsWith("_") && user !== "Shared" && user !== ".localized" diff --git a/scripts/e2e/parallels/macos-users.ts b/scripts/e2e/parallels/macos-users.ts new file mode 100644 index 000000000000..67b052f0fcc4 --- /dev/null +++ b/scripts/e2e/parallels/macos-users.ts @@ -0,0 +1,13 @@ +// macOS user helpers support Parallels guest fallback discovery. +export function parseMacosDsclUserHomeLine(line: string): { user: string; home: string } | null { + const match = /^(\S+)\s+(.+?)\s*$/u.exec(line.replaceAll("\r", "")); + if (!match) { + return null; + } + return { user: match[1], home: match[2] }; +} + +export function isLikelyMacosDesktopHome(home: string | undefined): boolean { + const normalized = home?.trim(); + return Boolean(normalized) && /(?:^|\/)Users\/[^/]+$/u.test(normalized); +} diff --git a/scripts/e2e/parallels/npm-update-smoke.ts b/scripts/e2e/parallels/npm-update-smoke.ts index 93acfb2f1938..fa274156bf5c 100755 --- a/scripts/e2e/parallels/npm-update-smoke.ts +++ b/scripts/e2e/parallels/npm-update-smoke.ts @@ -10,10 +10,12 @@ import { die, ensureValue, extractLastOpenClawVersionFromLog, + isLikelyMacosDesktopHome, makeTempDir, packOpenClaw, packageBuildCommitFromTgz, packageVersionFromTgz, + parseMacosDsclUserHomeLine, parsePlatformList, parseProvider, readPositiveIntEnv, @@ -1096,10 +1098,11 @@ export class NpmUpdateSmoke { { check: false, quiet: true, timeoutMs: 30_000 }, ).stdout.replaceAll("\r", ""); for (const line of users.split("\n")) { - const [user, home] = line.trim().split(/\s+/); + const parsed = parseMacosDsclUserHomeLine(line); + const user = parsed?.user; if ( user && - home?.startsWith("/Users/") && + isLikelyMacosDesktopHome(parsed?.home) && !user.startsWith("_") && user !== "Shared" && user !== ".localized" diff --git a/test/scripts/parallels-npm-update-smoke.test.ts b/test/scripts/parallels-npm-update-smoke.test.ts index 0ae5bd7d8686..c0a00248555b 100644 --- a/test/scripts/parallels-npm-update-smoke.test.ts +++ b/test/scripts/parallels-npm-update-smoke.test.ts @@ -754,6 +754,52 @@ exit 1 expect(script).toContain('"/usr/sbin/chown", sudoUser, scriptPath'); }); + it("selects macOS desktop users with homes on spaced mounted volumes", () => { + const root = makeTempDir(); + const prlctlPath = path.join(root, "prlctl"); + writeFileSync( + prlctlPath, + `#!/usr/bin/env bash +set -euo pipefail +args=" $* " +if [[ "$args" == *" /usr/bin/stat -f %Su /dev/console"* ]]; then + printf '%s\\n' 'loginwindow' + exit 0 +fi +if [[ "$args" == *" /usr/bin/dscl . -list /Users NFSHomeDirectory"* ]]; then + printf '%s\\n' '_daemon /var/root' + printf '%s\\n' 'clawuser /Volumes/Macintosh HD/Users/clawuser' + exit 0 +fi +exit 7 +`, + ); + chmodSync(prlctlPath, 0o755); + + withEnv( + { + OPENAI_API_KEY: "test-key", + PATH: `${root}${path.delimiter}${process.env.PATH ?? ""}`, + }, + () => { + const smoke = new NpmUpdateSmoke({ + ...TEST_AUTH, + json: false, + packageSpec: "openclaw@latest", + platforms: new Set(["macos"]), + provider: "openai", + updateTarget: "local-main", + }); + const resolveMacosDesktopUser = Reflect.get( + smoke, + "resolveMacosDesktopUser", + ) as () => string; + + expect(resolveMacosDesktopUser.call(smoke)).toBe("clawuser"); + }, + ); + }); + it("keeps spaces in macOS sudo fallback desktop homes", () => { const root = makeTempDir(); const prlctlPath = path.join(root, "prlctl"); diff --git a/test/scripts/parallels-smoke-model.test.ts b/test/scripts/parallels-smoke-model.test.ts index 604fb7fde562..699002f9ad3c 100644 --- a/test/scripts/parallels-smoke-model.test.ts +++ b/test/scripts/parallels-smoke-model.test.ts @@ -20,7 +20,9 @@ import { pathToFileURL } from "node:url"; import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { extractLastOpenClawVersionFromLog, + isLikelyMacosDesktopHome, modelProviderConfigBatchJson, + parseMacosDsclUserHomeLine, readPositiveIntEnv, resolveLatestVersion, resolveParallelsModelTimeoutSeconds, @@ -221,6 +223,15 @@ describe("Parallels smoke model selection", () => { let invalidWindowsAgentTimeoutResult: ReturnType; let invalidWindowsUpdateTimeoutResult: ReturnType; + it("parses macOS dscl user homes with spaces on mounted volumes", () => { + expect(parseMacosDsclUserHomeLine("clawuser /Volumes/Macintosh HD/Users/clawuser")).toEqual({ + user: "clawuser", + home: "/Volumes/Macintosh HD/Users/clawuser", + }); + expect(isLikelyMacosDesktopHome("/Volumes/Macintosh HD/Users/clawuser")).toBe(true); + expect(isLikelyMacosDesktopHome("/var/empty")).toBe(false); + }); + it("extracts the last OpenClaw version from a bounded log tail", async () => { const tempDir = mkdtempSync(join(tmpdir(), "openclaw-parallels-log-tail-")); const logPath = join(tempDir, "phase.log");