fix(e2e): resolve mounted macOS desktop homes

This commit is contained in:
Vincent Koc
2026-06-20 16:51:20 +02:00
parent d0001f96f0
commit 106961b513
6 changed files with 81 additions and 4 deletions

View File

@@ -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";

View File

@@ -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"

View File

@@ -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);
}

View File

@@ -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"

View File

@@ -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<Platform>(["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");

View File

@@ -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<typeof spawnNodeEvalSync>;
let invalidWindowsUpdateTimeoutResult: ReturnType<typeof spawnNodeEvalSync>;
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");