From 71d2eba0a6e2221b04d665150d8713650caa14cc Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 6 Apr 2026 03:48:36 +0100 Subject: [PATCH] test: add windows dev-update smoke lanes --- .../skills/openclaw-parallels-smoke/SKILL.md | 6 + scripts/e2e/parallels-windows-smoke.sh | 777 +++++++++++++++++- 2 files changed, 742 insertions(+), 41 deletions(-) diff --git a/.agents/skills/openclaw-parallels-smoke/SKILL.md b/.agents/skills/openclaw-parallels-smoke/SKILL.md index 4e510f38120..05f4ed36491 100644 --- a/.agents/skills/openclaw-parallels-smoke/SKILL.md +++ b/.agents/skills/openclaw-parallels-smoke/SKILL.md @@ -67,14 +67,20 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo - Preferred entrypoint: `pnpm test:parallels:windows` - Use the snapshot closest to `pre-openclaw-native-e2e-2026-03-12`. +- Default upgrade coverage on Windows should now include: fresh snapshot -> site installer pinned to the requested stable tag -> `openclaw update --channel dev` on the guest. Keep the older host-tgz upgrade path only when the caller explicitly passes `--target-package-spec`. +- Optional exact npm-tag baseline on Windows: `bash scripts/e2e/parallels-windows-smoke.sh --mode upgrade --target-package-spec openclaw@ --json`. That lane installs the published npm tarball as baseline, then runs `openclaw update --channel dev`. +- Optional forward-fix Windows validation: `bash scripts/e2e/parallels-windows-smoke.sh --mode upgrade --upgrade-from-packed-main --json`. That lane installs the packed current-main npm tgz as baseline, then runs `openclaw update --channel dev`. - Always use `prlctl exec --current-user`; plain `prlctl exec` lands in `NT AUTHORITY\\SYSTEM`. - Prefer explicit `npm.cmd` and `openclaw.cmd`. - Use PowerShell only as the transport with `-ExecutionPolicy Bypass`, then call the `.cmd` shims from inside it. +- Current Windows Node installs expose `corepack` as a `.cmd` shim. If a release-to-dev lane sees `corepack` on PATH but `openclaw update --channel dev` still behaves as if corepack is missing, treat that as an exec-shim regression first. +- If an exact published-tag Windows lane fails during preflight with `npm run build` and `'pnpm' is not recognized`, remember that the guest is still executing the old published updater. Validate the fix with `--upgrade-from-packed-main`, then wait for the next tagged npm release before expecting the historical tag lane to pass. - Multi-word `openclaw agent --message ...` checks should call `& $openclaw ...` inside PowerShell, not `Start-Process ... -ArgumentList` against `openclaw.cmd`, or Commander can see split argv and throw `too many arguments for 'agent'`. - Windows installer/tgz phases now retry once after guest-ready recheck; keep new Windows smoke steps idempotent so a transport-flake retry is safe. - If a Windows retry sees the VM become `suspended` or `stopped`, resume/start it before the next `prlctl exec`; otherwise the second attempt just repeats the same `rc=255`. - Windows global `npm install -g` phases can stay quiet for a minute or more even when healthy; inspect the phase log before calling it hung, and only treat it as a regression once the retry wrapper or timeout trips. - Fresh Windows tgz install phases should also use the background PowerShell runner plus done-file/log-drain pattern; do not rely on one long-lived `prlctl exec ... powershell ... npm install -g` transport for package installs. +- Windows release-to-dev helpers should log `where pnpm` before and after the update and require `where pnpm` to succeed post-update. That proves the updater installed or enabled `pnpm` itself instead of depending on a smoke-only bootstrap. - Fresh Windows ref-mode onboard should use the same background PowerShell runner plus done-file/log-drain pattern as the npm-update helper, including startup materialization checks, host-side timeouts on short poll `prlctl exec` calls, and retry-on-poll-failure behavior for transient transport flakes. - Fresh Windows daemon-health reachability should use a hello-only gateway probe and a longer per-probe timeout than the default local attach path; full health RPCs are too eager during initial startup on current main. - Fresh Windows ref-mode agent verification should set `OPENAI_API_KEY` in the PowerShell environment before invoking `openclaw.cmd agent`, for the same pairing-required fallback reason as macOS. diff --git a/scripts/e2e/parallels-windows-smoke.sh b/scripts/e2e/parallels-windows-smoke.sh index 5a8ee50eeaa..bcad3a68a3f 100644 --- a/scripts/e2e/parallels-windows-smoke.sh +++ b/scripts/e2e/parallels-windows-smoke.sh @@ -16,6 +16,7 @@ HOST_IP="" LATEST_VERSION="" INSTALL_VERSION="" TARGET_PACKAGE_SPEC="" +UPGRADE_FROM_PACKED_MAIN=0 JSON_OUTPUT=0 KEEP_SERVER=0 CHECK_LATEST_REF=1 @@ -28,8 +29,11 @@ MAIN_TGZ_DIR="$(mktemp -d)" MAIN_TGZ_PATH="" MINGIT_ZIP_PATH="" MINGIT_ZIP_NAME="" +WINDOWS_LATEST_INSTALL_SCRIPT_PATH="" +WINDOWS_BASELINE_INSTALL_SCRIPT_PATH="" WINDOWS_INSTALL_SCRIPT_PATH="" WINDOWS_ONBOARD_SCRIPT_PATH="" +WINDOWS_DEV_UPDATE_SCRIPT_PATH="" SERVER_PID="" RUN_DIR="$(mktemp -d /tmp/openclaw-parallels-windows.XXXXXX)" BUILD_LOCK_DIR="${TMPDIR:-/tmp}/openclaw-parallels-build.lock" @@ -58,13 +62,41 @@ say() { } artifact_label() { + if [[ "$TARGET_PACKAGE_SPEC" == "" && "$MODE" == "upgrade" && "$UPGRADE_FROM_PACKED_MAIN" -eq 0 ]]; then + printf 'Windows smoke artifacts' + return + fi if [[ -n "$TARGET_PACKAGE_SPEC" ]]; then - printf 'target package tgz' + printf 'baseline package tgz' + return + fi + if [[ "$UPGRADE_FROM_PACKED_MAIN" -eq 1 ]]; then + printf 'packed main tgz' return fi printf 'current main tgz' } +upgrade_uses_host_tgz() { + [[ "$UPGRADE_FROM_PACKED_MAIN" -eq 1 || -n "$TARGET_PACKAGE_SPEC" ]] +} + +needs_host_tgz() { + [[ "$MODE" == "fresh" || "$MODE" == "both" ]] || upgrade_uses_host_tgz +} + +upgrade_summary_label() { + if [[ -n "$TARGET_PACKAGE_SPEC" ]]; then + printf 'target-package->dev' + return + fi + if [[ "$UPGRADE_FROM_PACKED_MAIN" -eq 1 ]]; then + printf 'packed-main->dev' + return + fi + printf 'latest->dev' +} + extract_package_build_commit_from_tgz() { tar -xOf "$1" package/dist/build-info.json | python3 -c 'import json, sys; print(json.load(sys.stdin).get("commit", ""))' } @@ -106,9 +138,15 @@ Options: --host-ip Override Parallels host IP. --latest-version Override npm latest version lookup. --install-version Pin site-installer version/dist-tag for the baseline lane. + --upgrade-from-packed-main + Upgrade lane: install the packed current-main npm tgz as baseline, + then run openclaw update --channel dev. --target-package-spec - Install this npm package tarball instead of packing current main. + Upgrade lane: install this npm package tarball as the baseline, + then run openclaw update --channel dev. + Fresh lane: install this npm package tarball instead of packing current main. Example: openclaw@2026.3.13-beta.1 + Default upgrade lane without this flag: latest/site installer -> dev channel update. --skip-latest-ref-check Skip latest-release ref-mode precheck. --keep-server Leave temp host HTTP server running. --json Print machine-readable JSON summary. @@ -162,6 +200,10 @@ while [[ $# -gt 0 ]]; do INSTALL_VERSION="$2" shift 2 ;; + --upgrade-from-packed-main) + UPGRADE_FROM_PACKED_MAIN=1 + shift + ;; --target-package-spec) TARGET_PACKAGE_SPEC="$2" shift 2 @@ -653,6 +695,14 @@ resolve_latest_version() { npm view openclaw version --userconfig "$(mktemp)" } +baseline_install_version() { + if [[ -n "$INSTALL_VERSION" ]]; then + printf '%s\n' "$INSTALL_VERSION" + return + fi + printf '%s\n' "$LATEST_VERSION" +} + resolve_mingit_download() { python3 - <<'PY' import json @@ -778,19 +828,24 @@ EOF )" } +ensure_mingit_zip() { + local mingit_name mingit_url + mapfile -t mingit_meta < <(resolve_mingit_download) + mingit_name="${mingit_meta[0]}" + mingit_url="${mingit_meta[1]}" + MINGIT_ZIP_NAME="$mingit_name" + MINGIT_ZIP_PATH="$MAIN_TGZ_DIR/$mingit_name" + if [[ ! -f "$MINGIT_ZIP_PATH" ]]; then + say "Download $MINGIT_ZIP_NAME" + curl -fsSL "$mingit_url" -o "$MINGIT_ZIP_PATH" + fi +} + pack_main_tgz() { - local mingit_name mingit_url short_head pkg packed_commit + local short_head pkg packed_commit + ensure_mingit_zip if [[ -n "$TARGET_PACKAGE_SPEC" ]]; then say "Pack target package tgz: $TARGET_PACKAGE_SPEC" - mapfile -t mingit_meta < <(resolve_mingit_download) - mingit_name="${mingit_meta[0]}" - mingit_url="${mingit_meta[1]}" - MINGIT_ZIP_NAME="$mingit_name" - MINGIT_ZIP_PATH="$MAIN_TGZ_DIR/$mingit_name" - if [[ ! -f "$MINGIT_ZIP_PATH" ]]; then - say "Download $MINGIT_ZIP_NAME" - curl -fsSL "$mingit_url" -o "$MINGIT_ZIP_PATH" - fi pkg="$( npm pack "$TARGET_PACKAGE_SPEC" --ignore-scripts --json --pack-destination "$MAIN_TGZ_DIR" \ | python3 -c 'import json, sys; data = json.load(sys.stdin); print(data[-1]["filename"])' @@ -803,15 +858,6 @@ pack_main_tgz() { fi say "Pack current main tgz" ensure_current_build - mapfile -t mingit_meta < <(resolve_mingit_download) - mingit_name="${mingit_meta[0]}" - mingit_url="${mingit_meta[1]}" - MINGIT_ZIP_NAME="$mingit_name" - MINGIT_ZIP_PATH="$MAIN_TGZ_DIR/$mingit_name" - if [[ ! -f "$MINGIT_ZIP_PATH" ]]; then - say "Download $MINGIT_ZIP_NAME" - curl -fsSL "$mingit_url" -o "$MINGIT_ZIP_PATH" - fi short_head="$(git rev-parse --short HEAD)" pkg="$( npm pack --ignore-scripts --json --pack-destination "$MAIN_TGZ_DIR" \ @@ -838,7 +884,11 @@ verify_target_version() { start_server() { local host_ip="$1" local artifact probe_url attempt - artifact="$(basename "$MAIN_TGZ_PATH")" + if [[ -n "$MAIN_TGZ_PATH" ]]; then + artifact="$(basename "$MAIN_TGZ_PATH")" + else + artifact="$MINGIT_ZIP_NAME" + fi attempt=0 while :; do attempt=$((attempt + 1)) @@ -864,22 +914,372 @@ start_server() { done } +write_latest_install_runner_script() { + local install_url_q="$1" + local version_flag_q="$2" + WINDOWS_LATEST_INSTALL_SCRIPT_PATH="$MAIN_TGZ_DIR/openclaw-install-latest.ps1" + cat >"$WINDOWS_LATEST_INSTALL_SCRIPT_PATH" < \$Stage" | Tee-Object -FilePath \$LogPath -Append | Out-Null +} + +try { + \$script = Invoke-RestMethod -Uri '$install_url_q' + Write-ProgressLog 'install.start' + & ([scriptblock]::Create(\$script)) ${version_flag_q}-NoOnboard *>&1 | Tee-Object -FilePath \$LogPath -Append | Out-Null + if (\$LASTEXITCODE -ne 0) { + throw "installer failed with exit code \$LASTEXITCODE" + } + Write-ProgressLog 'install.version' + & (Join-Path \$env:APPDATA 'npm\openclaw.cmd') --version *>&1 | Tee-Object -FilePath \$LogPath -Append | Out-Null + if (\$LASTEXITCODE -ne 0) { + throw "openclaw --version failed with exit code \$LASTEXITCODE" + } + Set-Content -Path \$DonePath -Value ([string]0) + exit 0 +} catch { + if (Test-Path \$LogPath) { + Add-Content -Path \$LogPath -Value (\$_ | Out-String) + } else { + (\$_ | Out-String) | Set-Content -Path \$LogPath + } + Set-Content -Path \$DonePath -Value '1' + exit 1 +} +EOF +} + +write_baseline_npm_install_runner_script() { + WINDOWS_BASELINE_INSTALL_SCRIPT_PATH="$MAIN_TGZ_DIR/openclaw-install-baseline-npm.ps1" + cat >"$WINDOWS_BASELINE_INSTALL_SCRIPT_PATH" <<'EOF' +param( + [Parameter(Mandatory = $true)][string]$Version, + [Parameter(Mandatory = $true)][string]$LogPath, + [Parameter(Mandatory = $true)][string]$DonePath +) + +$ErrorActionPreference = 'Stop' +$PSNativeCommandUseErrorActionPreference = $false + +function Write-ProgressLog { + param([Parameter(Mandatory = $true)][string]$Stage) + + "==> $Stage" | Tee-Object -FilePath $LogPath -Append | Out-Null +} + +function Invoke-Logged { + param( + [Parameter(Mandatory = $true)][string]$Label, + [Parameter(Mandatory = $true)][scriptblock]$Command + ) + + $output = $null + $previousErrorActionPreference = $ErrorActionPreference + $previousNativeErrorPreference = $PSNativeCommandUseErrorActionPreference + try { + $ErrorActionPreference = 'Continue' + $PSNativeCommandUseErrorActionPreference = $false + $output = & $Command *>&1 + $exitCode = $LASTEXITCODE + } finally { + $ErrorActionPreference = $previousErrorActionPreference + $PSNativeCommandUseErrorActionPreference = $previousNativeErrorPreference + } + + if ($null -ne $output) { + $output | Tee-Object -FilePath $LogPath -Append | Out-Null + } + + if ($exitCode -ne 0) { + throw "$Label failed with exit code $exitCode" + } +} + +try { + $portableGit = Join-Path (Join-Path (Join-Path $env:LOCALAPPDATA 'OpenClaw\deps') 'portable-git') '' + $env:PATH = "$portableGit\cmd;$portableGit\mingw64\bin;$portableGit\usr\bin;$env:PATH" + $openclaw = Join-Path $env:APPDATA 'npm\openclaw.cmd' + + Write-ProgressLog 'install.start' + Invoke-Logged 'npm install baseline release' { + & npm.cmd install -g "openclaw@$Version" --no-fund --no-audit --loglevel=error + } + + Write-ProgressLog 'install.version' + Invoke-Logged 'openclaw --version' { & $openclaw --version } + + Set-Content -Path $DonePath -Value ([string]0) + exit 0 +} catch { + if (Test-Path $LogPath) { + Add-Content -Path $LogPath -Value ($_ | Out-String) + } else { + ($_ | Out-String) | Set-Content -Path $LogPath + } + Set-Content -Path $DonePath -Value '1' + exit 1 +} +EOF +} + +install_baseline_npm_release() { + local host_ip="$1" + local version="$2" + local script_url + local runner_name log_name done_name done_status launcher_state guest_log + local log_state_path + local start_seconds poll_deadline startup_checked poll_rc state_rc log_rc + + write_baseline_npm_install_runner_script + script_url="http://$host_ip:$HOST_PORT/$(basename "$WINDOWS_BASELINE_INSTALL_SCRIPT_PATH")" + runner_name="openclaw-install-baseline-$RANDOM-$RANDOM.ps1" + log_name="openclaw-install-baseline-$RANDOM-$RANDOM.log" + done_name="openclaw-install-baseline-$RANDOM-$RANDOM.done" + log_state_path="$(mktemp "${TMPDIR:-/tmp}/openclaw-install-baseline-log-state.XXXXXX")" + : >"$log_state_path" + start_seconds="$SECONDS" + poll_deadline=$((SECONDS + TIMEOUT_INSTALL_S + 60)) + startup_checked=0 + + guest_powershell_poll 20 "$(cat <