test(e2e): cover root-managed VPS upgrades

This commit is contained in:
Vincent Koc
2026-05-13 21:54:40 +08:00
parent 25dd30d656
commit d08f68dee7
8 changed files with 79 additions and 7 deletions

View File

@@ -626,7 +626,7 @@ jobs:
artifact_name: ${{ needs.prepare_release_package.outputs.artifact_name }}
package_sha256: ${{ (needs.resolve_target.outputs.package_acceptance_package_spec == '' && needs.resolve_target.outputs.release_package_spec == '') && needs.prepare_release_package.outputs.package_sha256 || '' }}
suite_profile: custom
docker_lanes: doctor-switch update-channel-switch skill-install update-corrupt-plugin upgrade-survivor published-upgrade-survivor update-restart-auth plugins-offline plugin-update
docker_lanes: doctor-switch update-channel-switch skill-install update-corrupt-plugin upgrade-survivor published-upgrade-survivor root-managed-vps-upgrade update-restart-auth plugins-offline plugin-update
published_upgrade_survivor_baselines: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'last-stable-4 2026.4.23 2026.5.2 2026.4.15' || '' }}
published_upgrade_survivor_scenarios: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'reported-issues' || '' }}
telegram_mode: mock-openai

View File

@@ -386,10 +386,10 @@ jobs:
docker_lanes="npm-onboard-channel-agent gateway-network config-reload"
;;
package)
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch skill-install update-corrupt-plugin upgrade-survivor published-upgrade-survivor update-restart-auth plugins-offline plugin-update"
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch skill-install update-corrupt-plugin upgrade-survivor published-upgrade-survivor root-managed-vps-upgrade update-restart-auth plugins-offline plugin-update"
;;
product)
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch skill-install update-corrupt-plugin upgrade-survivor published-upgrade-survivor update-restart-auth plugins plugin-update mcp-channels cron-mcp-cleanup openai-web-search-minimal openwebui"
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch skill-install update-corrupt-plugin upgrade-survivor published-upgrade-survivor root-managed-vps-upgrade update-restart-auth plugins plugin-update mcp-channels cron-mcp-cleanup openai-web-search-minimal openwebui"
include_openwebui=true
;;
full)

View File

@@ -1633,6 +1633,7 @@
"test:docker:published-upgrade-survivor": "env OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE=1 OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC=${OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC:-openclaw@latest} OPENCLAW_UPGRADE_SURVIVOR_DOCKER_RUN_TIMEOUT=${OPENCLAW_UPGRADE_SURVIVOR_DOCKER_RUN_TIMEOUT:-1500s} bash scripts/e2e/upgrade-survivor-docker.sh",
"test:docker:qr": "bash scripts/e2e/qr-import-docker.sh",
"test:docker:rerun": "node scripts/docker-e2e-rerun.mjs",
"test:docker:root-managed-vps-upgrade": "env OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE=1 OPENCLAW_UPGRADE_SURVIVOR_ROOT_MANAGED_VPS=1 OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC=${OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC:-openclaw@2026.5.7} OPENCLAW_UPGRADE_SURVIVOR_DOCKER_RUN_TIMEOUT=${OPENCLAW_UPGRADE_SURVIVOR_DOCKER_RUN_TIMEOUT:-1500s} bash scripts/e2e/upgrade-survivor-docker.sh",
"test:docker:session-runtime-context": "bash scripts/e2e/session-runtime-context-docker.sh",
"test:docker:skill-install": "bash scripts/e2e/skill-install-docker.sh",
"test:docker:timings": "node scripts/docker-e2e-timings.mjs",

View File

@@ -38,6 +38,7 @@ CANDIDATE_KIND="${OPENCLAW_UPGRADE_SURVIVOR_CANDIDATE_KIND:-tarball}"
CANDIDATE_SPEC="${OPENCLAW_UPGRADE_SURVIVOR_CANDIDATE_SPEC:-${OPENCLAW_CURRENT_PACKAGE_TGZ:-}}"
SCENARIO="${OPENCLAW_UPGRADE_SURVIVOR_SCENARIO:-base}"
UPDATE_RESTART_MODE="${OPENCLAW_UPGRADE_SURVIVOR_UPDATE_RESTART_MODE:-manual}"
ROOT_MANAGED_VPS="${OPENCLAW_UPGRADE_SURVIVOR_ROOT_MANAGED_VPS:-0}"
CURRENT_PHASE="setup"
FAILURE_PHASE=""
FAILURE_MESSAGE=""
@@ -681,7 +682,16 @@ install_baseline() {
seed_state() {
openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_FUNCTION_B64:?missing OPENCLAW_TEST_STATE_FUNCTION_B64}"
openclaw_test_state_create "$ARTIFACT_ROOT/state-home" minimal
if [ "$ROOT_MANAGED_VPS" = "1" ]; then
if [ "$(id -u)" -ne 0 ]; then
echo "root-managed VPS survivor mode must run as uid 0" >&2
return 1
fi
rm -rf /root/.openclaw /root/workspace
openclaw_test_state_create /root minimal
else
openclaw_test_state_create "$ARTIFACT_ROOT/state-home" minimal
fi
export OPENCLAW_UPGRADE_SURVIVOR_BASELINE_VERSION="$baseline_version"
node scripts/e2e/lib/upgrade-survivor/assertions.mjs seed
}
@@ -1037,12 +1047,21 @@ update_candidate() {
local update_start=""
local update_end=""
local update_args=(update --tag "$CANDIDATE_SPEC" --yes --json)
local update_env=(
env
-u OPENCLAW_GATEWAY_TOKEN
-u OPENCLAW_GATEWAY_PASSWORD
-u OPENCLAW_ALLOW_ROOT
)
if [ "$UPDATE_RESTART_MODE" = "manual" ]; then
update_args+=(--no-restart)
else
update_start="$(node -e "process.stdout.write(String(Date.now()))")"
fi
if ! env -u OPENCLAW_GATEWAY_TOKEN -u OPENCLAW_GATEWAY_PASSWORD OPENCLAW_ALLOW_ROOT=1 openclaw "${update_args[@]}" >"$UPDATE_JSON" 2>"$UPDATE_ERR"; then
if [ "$ROOT_MANAGED_VPS" != "1" ]; then
update_env+=(OPENCLAW_ALLOW_ROOT=1)
fi
if ! "${update_env[@]}" openclaw "${update_args[@]}" >"$UPDATE_JSON" 2>"$UPDATE_ERR"; then
echo "openclaw update failed" >&2
cat "$UPDATE_ERR" >&2 || true
cat "$UPDATE_JSON" >&2 || true
@@ -1063,6 +1082,20 @@ update_candidate() {
installed_version="$(read_installed_version)"
}
assert_root_managed_vps_cli_usable() {
if [ "$ROOT_MANAGED_VPS" != "1" ]; then
return 0
fi
local root_cli_env=(
env
-u OPENCLAW_GATEWAY_TOKEN
-u OPENCLAW_GATEWAY_PASSWORD
-u OPENCLAW_ALLOW_ROOT
)
"${root_cli_env[@]}" openclaw config file >"$ARTIFACT_ROOT/root-vps-config-file.out" 2>"$ARTIFACT_ROOT/root-vps-config-file.err"
"${root_cli_env[@]}" openclaw plugins >"$ARTIFACT_ROOT/root-vps-plugins.out" 2>"$ARTIFACT_ROOT/root-vps-plugins.err"
}
run_doctor() {
if ! openclaw doctor --fix --non-interactive >"$DOCTOR_LOG" 2>&1; then
echo "openclaw doctor failed" >&2
@@ -1183,6 +1216,7 @@ phase seed-legacy-runtime-deps-symlink seed_legacy_runtime_deps_symlink
phase resolve-candidate resolve_candidate_version
phase prepare-update-restart-probe prepare_update_restart_probe
phase update-candidate update_candidate
phase root-managed-vps-cli-usable assert_root_managed_vps_cli_usable
phase assert-legacy-plugin-dependency-debris-before-doctor assert_legacy_plugin_dependency_debris_before_doctor
phase configure-configured-plugin-install-fixture-registry configure_configured_plugin_install_fixture_registry
phase doctor run_doctor

View File

@@ -17,6 +17,16 @@ UPDATE_RESTART_MODE="${OPENCLAW_UPGRADE_SURVIVOR_UPDATE_RESTART_MODE:-manual}"
LANE_ARTIFACT_SUFFIX="${OPENCLAW_DOCKER_ALL_LANE_NAME:-default}"
LANE_ARTIFACT_SUFFIX="${LANE_ARTIFACT_SUFFIX//[^A-Za-z0-9_.-]/_}"
ARTIFACT_DIR="${OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_DIR:-$ROOT_DIR/.artifacts/upgrade-survivor/$LANE_ARTIFACT_SUFFIX}"
ROOT_MANAGED_VPS="${OPENCLAW_UPGRADE_SURVIVOR_ROOT_MANAGED_VPS:-0}"
DOCKER_RUN_USER_ARGS=()
if [ "$ROOT_MANAGED_VPS" = "1" ]; then
if [ "${OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE:-0}" != "1" ]; then
echo "OPENCLAW_UPGRADE_SURVIVOR_ROOT_MANAGED_VPS=1 requires OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE=1" >&2
exit 1
fi
DOCKER_RUN_USER_ARGS+=(--user root -e HOME=/root -e USER=root)
fi
normalize_npm_candidate() {
local raw="$1"
@@ -89,11 +99,13 @@ if [ "${OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE:-0}" = "1" ]; then
-e OPENCLAW_UPGRADE_SURVIVOR_SCENARIO="$SCENARIO" \
-e OPENCLAW_UPGRADE_SURVIVOR_UPDATE_RESTART_MODE="$UPDATE_RESTART_MODE" \
-e OPENCLAW_UPGRADE_SURVIVOR_LEGACY_RUNTIME_DEPS_SYMLINK="${OPENCLAW_UPGRADE_SURVIVOR_LEGACY_RUNTIME_DEPS_SYMLINK:-}" \
-e OPENCLAW_UPGRADE_SURVIVOR_ROOT_MANAGED_VPS="$ROOT_MANAGED_VPS" \
-e OPENCLAW_UPGRADE_SURVIVOR_SUMMARY_JSON=/tmp/openclaw-upgrade-survivor-artifacts/summary.json \
-e OPENCLAW_UPGRADE_SURVIVOR_START_BUDGET_SECONDS="${OPENCLAW_UPGRADE_SURVIVOR_START_BUDGET_SECONDS:-90}" \
-e OPENCLAW_UPGRADE_SURVIVOR_STATUS_BUDGET_SECONDS="${OPENCLAW_UPGRADE_SURVIVOR_STATUS_BUDGET_SECONDS:-30}" \
-v "$ARTIFACT_DIR:/tmp/openclaw-upgrade-survivor-artifacts" \
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
"${DOCKER_RUN_USER_ARGS[@]}" \
"$IMAGE_NAME" \
timeout "$DOCKER_RUN_TIMEOUT" bash scripts/e2e/lib/upgrade-survivor/run.sh
exit 0
@@ -112,12 +124,14 @@ docker_e2e_run_with_harness \
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
-e OPENCLAW_TEST_STATE_SCRIPT_B64="$OPENCLAW_TEST_STATE_SCRIPT_B64" \
-e OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_ROOT=/tmp/openclaw-upgrade-survivor-artifacts \
-e OPENCLAW_UPGRADE_SURVIVOR_ROOT_MANAGED_VPS="$ROOT_MANAGED_VPS" \
-e OPENCLAW_UPGRADE_SURVIVOR_SCENARIO="$SCENARIO" \
-e OPENCLAW_UPGRADE_SURVIVOR_UPDATE_RESTART_MODE="$UPDATE_RESTART_MODE" \
-e OPENCLAW_UPGRADE_SURVIVOR_START_BUDGET_SECONDS="${OPENCLAW_UPGRADE_SURVIVOR_START_BUDGET_SECONDS:-90}" \
-e OPENCLAW_UPGRADE_SURVIVOR_STATUS_BUDGET_SECONDS="${OPENCLAW_UPGRADE_SURVIVOR_STATUS_BUDGET_SECONDS:-30}" \
-v "$ARTIFACT_DIR:/tmp/openclaw-upgrade-survivor-artifacts" \
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
"${DOCKER_RUN_USER_ARGS[@]}" \
"$IMAGE_NAME" \
timeout "$DOCKER_RUN_TIMEOUT" bash -lc 'set -euo pipefail
source scripts/lib/openclaw-e2e-instance.sh

View File

@@ -11,6 +11,8 @@ const RELEASE_OPENWEBUI_COMMAND =
"OPENCLAW_OPENWEBUI_MODEL=openai/gpt-5.4-mini OPENWEBUI_SMOKE_MODE=models OPENCLAW_OPENWEBUI_PROVIDER_TIMEOUT_SECONDS=300 OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:openwebui";
export const BUNDLED_PLUGIN_INSTALL_UNINSTALL_SHARDS = 24;
const upgradeSurvivorCommand = "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:upgrade-survivor";
const rootManagedVpsUpgradeCommand =
"OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:root-managed-vps-upgrade";
const updateRestartAuthCommand =
"OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:update-restart-auth";
@@ -345,6 +347,11 @@ export const mainLanes = [
weight: 3,
},
),
npmLane("root-managed-vps-upgrade", rootManagedVpsUpgradeCommand, {
stateScenario: "upgrade-survivor",
timeoutMs: 25 * 60 * 1000,
weight: 3,
}),
npmLane("update-restart-auth", updateRestartAuthCommand, {
stateScenario: "upgrade-survivor",
timeoutMs: 25 * 60 * 1000,
@@ -686,6 +693,11 @@ const releasePathPackageUpdateCoreLanes = [
weight: 3,
},
),
npmLane("root-managed-vps-upgrade", rootManagedVpsUpgradeCommand, {
stateScenario: "upgrade-survivor",
timeoutMs: 25 * 60 * 1000,
weight: 3,
}),
npmLane("update-restart-auth", updateRestartAuthCommand, {
stateScenario: "upgrade-survivor",
timeoutMs: 25 * 60 * 1000,

View File

@@ -330,6 +330,16 @@ describe("scripts/lib/docker-e2e-plan", () => {
timeoutMs: 1_500_000,
weight: 3,
},
{
command: "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:root-managed-vps-upgrade",
imageKind: "bare",
live: false,
name: "root-managed-vps-upgrade",
resources: ["docker", "npm"],
stateScenario: "upgrade-survivor",
timeoutMs: 1_500_000,
weight: 3,
},
{
command: "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:update-restart-auth",
imageKind: "bare",
@@ -483,6 +493,7 @@ describe("scripts/lib/docker-e2e-plan", () => {
"skill-install",
"upgrade-survivor",
"published-upgrade-survivor",
"root-managed-vps-upgrade",
"update-restart-auth",
]);
expect(pluginsRuntime.lanes.map((lane) => lane.name)).toEqual([

View File

@@ -115,7 +115,7 @@ describe("package acceptance workflow", () => {
expect(workflow).toContain("update-channel-switch skill-install update-corrupt-plugin");
expect(workflow).toContain("update-corrupt-plugin upgrade-survivor");
expect(workflow).toContain("published-upgrade-survivor");
expect(workflow).toContain("published-upgrade-survivor update-restart-auth");
expect(workflow).toContain("published-upgrade-survivor root-managed-vps-upgrade update-restart-auth");
expect(workflow).toContain("plugins-offline plugin-update");
expect(workflow).toContain("include_release_path_suites=true");
expect(workflow).not.toContain("telegram_mode requires source=npm");
@@ -596,7 +596,7 @@ describe("package artifact reuse", () => {
);
expect(workflow).toContain("suite_profile: custom");
expect(workflow).toContain(
"docker_lanes: doctor-switch update-channel-switch skill-install update-corrupt-plugin upgrade-survivor published-upgrade-survivor update-restart-auth plugins-offline plugin-update",
"docker_lanes: doctor-switch update-channel-switch skill-install update-corrupt-plugin upgrade-survivor published-upgrade-survivor root-managed-vps-upgrade update-restart-auth plugins-offline plugin-update",
);
expect(workflow).toContain(
"published_upgrade_survivor_baselines: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'last-stable-4 2026.4.23 2026.5.2 2026.4.15' || '' }}",