test: harden parallels smoke harness

This commit is contained in:
Peter Steinberger
2026-03-24 05:42:27 +00:00
parent da10b6026a
commit 687ce31f88
5 changed files with 114 additions and 9 deletions

View File

@@ -23,10 +23,15 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- Preferred entrypoint: `pnpm test:parallels:npm-update`
- Flow: fresh snapshot -> install npm package baseline -> smoke -> install current main tgz on the same guest -> smoke again.
- Same-guest update verification should set the default model explicitly to `openai/gpt-5.4` before the agent turn and use a fresh explicit `--session-id` so old session model state does not leak into the check.
- Keep the aggregate npm-update Linux VM name aligned with the default Linux smoke VM (`Ubuntu 24.04.3 ARM64` on Peter's host today). Do not hardcode a different Linux guest in the wrapper unless the per-OS Linux smoke default changed too.
- The aggregate npm-update wrapper must resolve the Linux VM with the same Ubuntu fallback policy as `parallels-linux-smoke.sh` before both fresh and update lanes. On Peter's current host, missing `Ubuntu 24.04.3 ARM64` should fall back to `Ubuntu 25.10`.
- On Windows same-guest update checks, restart the gateway after the npm upgrade before `gateway status` / `agent`; in-place global npm updates can otherwise leave stale hashed `dist/*` module imports alive in the running service.
- For Windows same-guest update checks, prefer the done-file/log-drain PowerShell runner pattern over one long-lived `prlctl exec ... powershell -EncodedCommand ...` transport. The guest can finish successfully while the outer `prlctl exec` still hangs.
- Linux same-guest update verification should also export `HOME=/root`, pass `OPENAI_API_KEY` via `prlctl exec ... /usr/bin/env`, and use `openclaw agent --local`; the fresh Linux baseline does not rely on persisted gateway credentials.
## CLI invocation footgun
- The Parallels smoke shell scripts should tolerate a literal bare `--` arg so `pnpm test:parallels:* -- --json` and similar forwarded invocations work without needing to call `bash scripts/e2e/...` directly.
## macOS flow
- Preferred entrypoint: `pnpm test:parallels:macos`

View File

@@ -99,6 +99,9 @@ EOF
while [[ $# -gt 0 ]]; do
case "$1" in
--)
shift
;;
--vm)
VM_NAME="$2"
VM_NAME_EXPLICIT=1

View File

@@ -133,6 +133,9 @@ EOF
while [[ $# -gt 0 ]]; do
case "$1" in
--)
shift
;;
--vm)
VM_NAME="$2"
shift 2

View File

@@ -66,6 +66,9 @@ EOF
while [[ $# -gt 0 ]]; do
case "$1" in
--)
shift
;;
--package-spec)
PACKAGE_SPEC="$2"
shift 2
@@ -91,6 +94,37 @@ done
OPENAI_API_KEY_VALUE="${!OPENAI_API_KEY_ENV:-}"
[[ -n "$OPENAI_API_KEY_VALUE" ]] || die "$OPENAI_API_KEY_ENV is required"
resolve_linux_vm_name() {
local json requested
json="$(prlctl list --all --json)"
requested="$LINUX_VM"
PRL_VM_JSON="$json" REQUESTED_VM_NAME="$requested" python3 - <<'PY'
import difflib
import json
import os
import sys
payload = json.loads(os.environ["PRL_VM_JSON"])
requested = os.environ["REQUESTED_VM_NAME"].strip()
requested_lower = requested.lower()
names = [str(item.get("name", "")).strip() for item in payload if str(item.get("name", "")).strip()]
if requested in names:
print(requested)
raise SystemExit(0)
ubuntu_names = [name for name in names if "ubuntu" in name.lower()]
if not ubuntu_names:
sys.exit(f"default vm not found and no Ubuntu fallback available: {requested}")
best_name = max(
ubuntu_names,
key=lambda name: difflib.SequenceMatcher(None, requested_lower, name.lower()).ratio(),
)
print(best_name)
PY
}
resolve_latest_version() {
npm view openclaw version --userconfig "$(mktemp)"
}
@@ -183,6 +217,53 @@ PY
prlctl exec "$WINDOWS_VM" --current-user powershell.exe -NoProfile -ExecutionPolicy Bypass -EncodedCommand "$encoded"
}
run_windows_script_via_log() {
local script_body="$1"
local runner_name log_name done_name done_status
runner_name="openclaw-update-$RANDOM-$RANDOM.ps1"
log_name="openclaw-update-$RANDOM-$RANDOM.log"
done_name="openclaw-update-$RANDOM-$RANDOM.done"
guest_powershell "$(cat <<EOF
\$runner = Join-Path \$env:TEMP '$runner_name'
\$log = Join-Path \$env:TEMP '$log_name'
\$done = Join-Path \$env:TEMP '$done_name'
Remove-Item \$runner, \$log, \$done -Force -ErrorAction SilentlyContinue
@'
\$ErrorActionPreference = 'Stop'
\$PSNativeCommandUseErrorActionPreference = \$false
\$log = Join-Path \$env:TEMP '$log_name'
\$done = Join-Path \$env:TEMP '$done_name'
try {
$script_body
Set-Content -Path \$done -Value ([string]\$LASTEXITCODE)
} catch {
if (Test-Path \$log) {
Add-Content -Path \$log -Value (\$_ | Out-String)
} else {
(\$_ | Out-String) | Set-Content -Path \$log
}
Set-Content -Path \$done -Value '1'
}
'@ | Set-Content -Path \$runner
Start-Process powershell.exe -ArgumentList @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', \$runner) -WindowStyle Hidden | Out-Null
EOF
)"
while :; do
done_status="$(
guest_powershell "\$done = Join-Path \$env:TEMP '$done_name'; if (Test-Path \$done) { (Get-Content \$done -Raw).Trim() }"
)"
done_status="${done_status//$'\r'/}"
if [[ -n "$done_status" ]]; then
guest_powershell "\$log = Join-Path \$env:TEMP '$log_name'; if (Test-Path \$log) { Get-Content \$log }"
[[ "$done_status" == "0" ]]
return $?
fi
sleep 2
done
}
run_macos_update() {
local tgz_url="$1"
local head_short="$2"
@@ -212,24 +293,28 @@ EOF
run_windows_update() {
local tgz_url="$1"
local head_short="$2"
guest_powershell "$(cat <<EOF
run_windows_script_via_log "$(cat <<EOF
\$env:PATH = "\$env:LOCALAPPDATA\OpenClaw\deps\portable-git\cmd;\$env:LOCALAPPDATA\OpenClaw\deps\portable-git\mingw64\bin;\$env:LOCALAPPDATA\OpenClaw\deps\portable-git\usr\bin;\$env:PATH"
\$tgz = Join-Path \$env:TEMP 'openclaw-main-update.tgz'
curl.exe -fsSL '$tgz_url' -o \$tgz
npm.cmd install -g \$tgz --no-fund --no-audit
curl.exe -fsSL '$tgz_url' -o \$tgz >> \$log 2>&1
npm.cmd install -g \$tgz --no-fund --no-audit >> \$log 2>&1
\$openclaw = Join-Path \$env:APPDATA 'npm\openclaw.cmd'
\$version = & \$openclaw --version
\$version
\$version | Tee-Object -FilePath \$log -Append
if (\$version -notmatch '$head_short') {
throw 'version mismatch: expected substring $head_short'
}
& \$openclaw models set openai/gpt-5.4
& \$openclaw models set openai/gpt-5.4 >> \$log 2>&1
# Windows can keep the old hashed dist modules alive across in-place global npm upgrades.
# Restart the gateway/service before verifying status or the next agent turn.
& \$openclaw gateway restart
& \$openclaw gateway restart >> \$log 2>&1
Start-Sleep -Seconds 5
& \$openclaw gateway status --deep --require-rpc
& \$openclaw agent --agent main --session-id parallels-npm-update-windows-$head_short --message 'Reply with exact ASCII text OK only.' --json
& \$openclaw gateway status --deep --require-rpc >> \$log 2>&1
\$output = & \$openclaw agent --agent main --session-id parallels-npm-update-windows-$head_short --message 'Reply with exact ASCII text OK only.' --json 2>&1
if (\$null -ne \$output) {
\$output | ForEach-Object { \$_ } | Tee-Object -FilePath \$log -Append
}
exit \$LASTEXITCODE
EOF
)"
}
@@ -302,6 +387,12 @@ if [[ -z "$PACKAGE_SPEC" ]]; then
PACKAGE_SPEC="openclaw@$LATEST_VERSION"
fi
RESOLVED_LINUX_VM="$(resolve_linux_vm_name)"
if [[ "$RESOLVED_LINUX_VM" != "$LINUX_VM" ]]; then
warn "requested VM $LINUX_VM not found; using $RESOLVED_LINUX_VM"
LINUX_VM="$RESOLVED_LINUX_VM"
fi
say "Run fresh npm baseline: $PACKAGE_SPEC"
bash "$ROOT_DIR/scripts/e2e/parallels-macos-smoke.sh" \
--mode fresh \

View File

@@ -103,6 +103,9 @@ EOF
while [[ $# -gt 0 ]]; do
case "$1" in
--)
shift
;;
--vm)
VM_NAME="$2"
shift 2