mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:40:43 +00:00
test: strengthen published upgrade survivor lane (#75361)
* test: integrate upgrade survivor baseline controls * test: gate published upgrade survivor path * test: preserve upgrade survivor fixture contract * test: keep upgrade survivor temp state off overlay
This commit is contained in:
@@ -34,6 +34,25 @@ function getConfig() {
|
||||
return readJson(requireEnv("OPENCLAW_CONFIG_PATH"));
|
||||
}
|
||||
|
||||
function getCoverage() {
|
||||
const file = process.env.OPENCLAW_UPGRADE_SURVIVOR_CONFIG_COVERAGE_JSON;
|
||||
if (!file || !fs.existsSync(file)) {
|
||||
return null;
|
||||
}
|
||||
return readJson(file);
|
||||
}
|
||||
|
||||
function acceptsIntent(coverage, id) {
|
||||
if (!coverage) {
|
||||
return true;
|
||||
}
|
||||
return Array.isArray(coverage.acceptedIntents) && coverage.acceptedIntents.includes(id);
|
||||
}
|
||||
|
||||
function hasCoverage(coverage) {
|
||||
return !!coverage;
|
||||
}
|
||||
|
||||
function seedState() {
|
||||
const stateDir = requireEnv("OPENCLAW_STATE_DIR");
|
||||
const workspace = requireEnv("OPENCLAW_TEST_WORKSPACE_DIR");
|
||||
@@ -84,60 +103,95 @@ function seedState() {
|
||||
|
||||
function assertConfigSurvived() {
|
||||
const config = getConfig();
|
||||
assert(config.update?.channel === "stable", "update.channel was not preserved");
|
||||
assert(config.gateway?.auth?.mode === "token", "gateway auth mode was not preserved");
|
||||
const coverage = getCoverage();
|
||||
|
||||
const agents = config.agents?.list ?? [];
|
||||
assert(Array.isArray(agents), "agents.list missing after update/doctor");
|
||||
assert(
|
||||
agents.some((agent) => agent?.id === "main"),
|
||||
"main agent missing",
|
||||
);
|
||||
assert(
|
||||
agents.some((agent) => agent?.id === "ops"),
|
||||
"ops agent missing",
|
||||
);
|
||||
assert(
|
||||
agents.find((agent) => agent?.id === "main")?.contextTokens === 64000,
|
||||
"main agent contextTokens changed",
|
||||
);
|
||||
assert(
|
||||
agents.find((agent) => agent?.id === "ops")?.fastModeDefault === true,
|
||||
"ops fastModeDefault changed",
|
||||
);
|
||||
if (acceptsIntent(coverage, "update")) {
|
||||
assert(config.update?.channel === "stable", "update.channel was not preserved");
|
||||
}
|
||||
if (acceptsIntent(coverage, "gateway")) {
|
||||
assert(config.gateway?.auth?.mode === "token", "gateway auth mode was not preserved");
|
||||
}
|
||||
|
||||
const discord = config.channels?.discord;
|
||||
assert(discord?.enabled === true, "discord enabled flag changed");
|
||||
const discordAllowFrom = discord.allowFrom ?? discord.dm?.allowFrom;
|
||||
const discordDmPolicy = discord.dmPolicy ?? discord.dm?.policy;
|
||||
assert(discordDmPolicy === "allowlist", "discord DM policy changed");
|
||||
assert(
|
||||
Array.isArray(discordAllowFrom) && discordAllowFrom.includes("111111111111111111"),
|
||||
"discord allowFrom changed",
|
||||
);
|
||||
assert(
|
||||
discord.guilds?.["222222222222222222"]?.channels?.["333333333333333333"]?.requireMention ===
|
||||
true,
|
||||
"discord guild channel mention policy changed",
|
||||
);
|
||||
assert(discord.threadBindings?.idleHours === 72, "discord thread binding ttl changed");
|
||||
if (acceptsIntent(coverage, "models")) {
|
||||
assert(config.models?.providers?.openai, "OpenAI model provider missing");
|
||||
}
|
||||
|
||||
assert(config.channels?.telegram?.enabled === true, "telegram enabled flag changed");
|
||||
assert(
|
||||
config.channels?.telegram?.groups?.["-1001234567890"]?.requireMention === true,
|
||||
"telegram group policy changed",
|
||||
);
|
||||
assert(config.channels?.whatsapp?.enabled === true, "whatsapp enabled flag changed");
|
||||
assert(
|
||||
config.channels?.whatsapp?.groups?.["120363000000000000@g.us"]?.systemPrompt ===
|
||||
"Use the existing WhatsApp group prompt.",
|
||||
"whatsapp group policy changed",
|
||||
);
|
||||
if (acceptsIntent(coverage, "agents")) {
|
||||
const agents = config.agents?.list ?? [];
|
||||
assert(Array.isArray(agents), "agents.list missing after update/doctor");
|
||||
assert(
|
||||
agents.some((agent) => agent?.id === "main"),
|
||||
"main agent missing",
|
||||
);
|
||||
assert(
|
||||
agents.some((agent) => agent?.id === "ops"),
|
||||
"ops agent missing",
|
||||
);
|
||||
if (hasCoverage(coverage)) {
|
||||
assert(config.agents?.defaults?.contextTokens === 64000, "default contextTokens changed");
|
||||
} else {
|
||||
assert(
|
||||
agents.find((agent) => agent?.id === "main")?.contextTokens === 64000,
|
||||
"main agent contextTokens changed",
|
||||
);
|
||||
}
|
||||
assert(
|
||||
agents.find((agent) => agent?.id === "ops")?.fastModeDefault === true,
|
||||
"ops fastModeDefault changed",
|
||||
);
|
||||
}
|
||||
|
||||
const pluginAllow = config.plugins?.allow ?? [];
|
||||
assert(pluginAllow.includes("discord"), "discord plugin allow entry missing");
|
||||
assert(pluginAllow.includes("telegram"), "telegram plugin allow entry missing");
|
||||
assert(pluginAllow.includes("whatsapp"), "whatsapp plugin allow entry missing");
|
||||
if (acceptsIntent(coverage, "skills")) {
|
||||
assert(config.skills?.allowBundled?.includes("memory"), "memory skill allowlist changed");
|
||||
}
|
||||
|
||||
if (acceptsIntent(coverage, "plugins")) {
|
||||
const pluginAllow = config.plugins?.allow ?? [];
|
||||
assert(pluginAllow.includes("discord"), "discord plugin allow entry missing");
|
||||
assert(pluginAllow.includes("telegram"), "telegram plugin allow entry missing");
|
||||
assert(pluginAllow.includes("whatsapp"), "whatsapp plugin allow entry missing");
|
||||
}
|
||||
|
||||
if (acceptsIntent(coverage, "discord-channel")) {
|
||||
const discord = config.channels?.discord;
|
||||
assert(discord?.enabled === true, "discord enabled flag changed");
|
||||
const discordAllowFrom = discord.allowFrom ?? discord.dm?.allowFrom;
|
||||
const discordDmPolicy = discord.dmPolicy ?? discord.dm?.policy;
|
||||
assert(discordDmPolicy === "allowlist", "discord DM policy changed");
|
||||
assert(
|
||||
Array.isArray(discordAllowFrom) && discordAllowFrom.includes("111111111111111111"),
|
||||
"discord allowFrom changed",
|
||||
);
|
||||
assert(
|
||||
discord.guilds?.["222222222222222222"]?.channels?.["333333333333333333"]?.requireMention ===
|
||||
true,
|
||||
"discord guild channel mention policy changed",
|
||||
);
|
||||
assert(discord.threadBindings?.idleHours === 72, "discord thread binding ttl changed");
|
||||
}
|
||||
|
||||
if (acceptsIntent(coverage, "telegram-channel")) {
|
||||
const telegram = config.channels?.telegram;
|
||||
assert(telegram?.enabled === true, "telegram enabled flag changed");
|
||||
assert(
|
||||
telegram.groups?.["-1001234567890"]?.requireMention === true,
|
||||
"telegram group policy changed",
|
||||
);
|
||||
}
|
||||
|
||||
if (acceptsIntent(coverage, "whatsapp-channel")) {
|
||||
const whatsapp = config.channels?.whatsapp;
|
||||
assert(whatsapp?.enabled === true, "whatsapp enabled flag changed");
|
||||
const whatsappGroup = whatsapp.groups?.["120363000000000000@g.us"];
|
||||
if (hasCoverage(coverage)) {
|
||||
assert(whatsappGroup?.requireMention === true, "whatsapp group policy changed");
|
||||
} else {
|
||||
assert(
|
||||
whatsappGroup?.systemPrompt === "Use the existing WhatsApp group prompt.",
|
||||
"whatsapp group policy changed",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertStateSurvived() {
|
||||
|
||||
139
scripts/e2e/lib/upgrade-survivor/config-recipe.mjs
Normal file
139
scripts/e2e/lib/upgrade-survivor/config-recipe.mjs
Normal file
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const command = args.shift();
|
||||
|
||||
function option(name, fallback) {
|
||||
const index = args.indexOf(name);
|
||||
if (index === -1) {
|
||||
return fallback;
|
||||
}
|
||||
const value = args[index + 1];
|
||||
if (!value) {
|
||||
throw new Error(`missing value for ${name}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function tail(value, max = 2400) {
|
||||
const text = String(value || "");
|
||||
return text.length <= max ? text : text.slice(-max);
|
||||
}
|
||||
|
||||
function writeJson(file, value) {
|
||||
fs.mkdirSync(path.dirname(file), { recursive: true });
|
||||
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
||||
}
|
||||
|
||||
const configSectionDir = new URL("./config-recipe/", import.meta.url);
|
||||
|
||||
function readConfigSection(fileName) {
|
||||
const fileUrl = new URL(fileName, configSectionDir);
|
||||
return JSON.stringify(JSON.parse(fs.readFileSync(fileUrl, "utf8")));
|
||||
}
|
||||
|
||||
function configSetJsonFile(id, intent, configPath, fileName) {
|
||||
return {
|
||||
id,
|
||||
intent,
|
||||
argv: ["config", "set", configPath, readConfigSection(fileName), "--strict-json"],
|
||||
};
|
||||
}
|
||||
|
||||
const representativeConfigSteps = [
|
||||
configSetJsonFile("models-openai", "models", "models.providers.openai", "models-openai.json"),
|
||||
configSetJsonFile("agents", "agents", "agents", "agents.json"),
|
||||
configSetJsonFile("skills", "skills", "skills", "skills.json"),
|
||||
configSetJsonFile("plugins", "plugins", "plugins", "plugins.json"),
|
||||
configSetJsonFile(
|
||||
"channels-discord",
|
||||
"discord-channel",
|
||||
"channels.discord",
|
||||
"channels-discord.json",
|
||||
),
|
||||
configSetJsonFile(
|
||||
"channels-telegram",
|
||||
"telegram-channel",
|
||||
"channels.telegram",
|
||||
"channels-telegram.json",
|
||||
),
|
||||
configSetJsonFile(
|
||||
"channels-whatsapp",
|
||||
"whatsapp-channel",
|
||||
"channels.whatsapp",
|
||||
"channels-whatsapp.json",
|
||||
),
|
||||
];
|
||||
|
||||
const recipe = [
|
||||
{
|
||||
id: "update-channel",
|
||||
intent: "update",
|
||||
argv: ["config", "set", "update.channel", "stable"],
|
||||
},
|
||||
configSetJsonFile("gateway", "gateway", "gateway", "gateway.json"),
|
||||
...representativeConfigSteps,
|
||||
{
|
||||
id: "validate",
|
||||
intent: "validate",
|
||||
argv: ["config", "validate"],
|
||||
},
|
||||
];
|
||||
|
||||
function runOpenClaw(step) {
|
||||
const result = spawnSync("openclaw", step.argv, {
|
||||
encoding: "utf8",
|
||||
env: process.env,
|
||||
});
|
||||
return {
|
||||
id: step.id,
|
||||
intent: step.intent,
|
||||
command: ["openclaw", ...step.argv].join(" "),
|
||||
status: result.status,
|
||||
signal: result.signal,
|
||||
ok: result.status === 0,
|
||||
stdout: tail(result.stdout),
|
||||
stderr: tail(result.stderr),
|
||||
};
|
||||
}
|
||||
|
||||
function applyRecipe() {
|
||||
const summaryPath = option("--summary");
|
||||
const baselineVersion = option("--baseline-version", null);
|
||||
const summary = {
|
||||
source: "baseline-cli-command-recipe",
|
||||
recipe: "upgrade-survivor-v1",
|
||||
baselineVersion,
|
||||
acceptedIntents: [
|
||||
"update",
|
||||
"gateway",
|
||||
"models",
|
||||
"agents",
|
||||
"skills",
|
||||
"plugins",
|
||||
"discord-channel",
|
||||
"telegram-channel",
|
||||
"whatsapp-channel",
|
||||
],
|
||||
skippedIntents: [],
|
||||
steps: [],
|
||||
};
|
||||
|
||||
for (const step of recipe) {
|
||||
const outcome = runOpenClaw(step);
|
||||
summary.steps.push(outcome);
|
||||
writeJson(summaryPath, summary);
|
||||
if (!outcome.ok) {
|
||||
throw new Error(`baseline config recipe failed at ${step.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (command === "apply") {
|
||||
applyRecipe();
|
||||
} else {
|
||||
throw new Error(`unknown upgrade-survivor config-recipe command: ${command ?? "<missing>"}`);
|
||||
}
|
||||
31
scripts/e2e/lib/upgrade-survivor/config-recipe/agents.json
Normal file
31
scripts/e2e/lib/upgrade-survivor/config-recipe/agents.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"defaults": {
|
||||
"model": {
|
||||
"primary": "openai/gpt-4.1-mini"
|
||||
},
|
||||
"contextTokens": 64000,
|
||||
"skills": ["memory"]
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"id": "main",
|
||||
"default": true,
|
||||
"name": "Main",
|
||||
"workspace": "~/workspace",
|
||||
"model": {
|
||||
"primary": "openai/gpt-4.1-mini"
|
||||
},
|
||||
"thinkingDefault": "low",
|
||||
"skills": ["memory"]
|
||||
},
|
||||
{
|
||||
"id": "ops",
|
||||
"name": "Ops",
|
||||
"workspace": "~/workspace/ops",
|
||||
"model": {
|
||||
"primary": "openai/gpt-4.1-mini"
|
||||
},
|
||||
"fastModeDefault": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"token": {
|
||||
"source": "env",
|
||||
"provider": "default",
|
||||
"id": "DISCORD_BOT_TOKEN"
|
||||
},
|
||||
"dm": {
|
||||
"policy": "allowlist",
|
||||
"allowFrom": ["111111111111111111"]
|
||||
},
|
||||
"groupPolicy": "allowlist",
|
||||
"guilds": {
|
||||
"222222222222222222": {
|
||||
"slug": "survivor-guild",
|
||||
"channels": {
|
||||
"333333333333333333": {
|
||||
"enabled": true,
|
||||
"requireMention": true,
|
||||
"tools": {
|
||||
"allow": ["message_send"],
|
||||
"deny": ["exec"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"threadBindings": {
|
||||
"enabled": true,
|
||||
"idleHours": 72
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"botToken": {
|
||||
"source": "env",
|
||||
"provider": "default",
|
||||
"id": "TELEGRAM_BOT_TOKEN"
|
||||
},
|
||||
"dmPolicy": "allowlist",
|
||||
"allowFrom": ["123456789"],
|
||||
"defaultTo": "123456789",
|
||||
"groupPolicy": "allowlist",
|
||||
"groupAllowFrom": ["123456789"],
|
||||
"groups": {
|
||||
"-1001234567890": {
|
||||
"requireMention": true,
|
||||
"tools": {
|
||||
"allow": ["message_send"],
|
||||
"deny": ["exec"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"dmPolicy": "allowlist",
|
||||
"allowFrom": ["+15555550123"],
|
||||
"defaultTo": "+15555550123",
|
||||
"groupPolicy": "allowlist",
|
||||
"groupAllowFrom": ["+15555550123"],
|
||||
"groups": {
|
||||
"120363000000000000@g.us": {
|
||||
"requireMention": true,
|
||||
"tools": {
|
||||
"allow": ["message_send"],
|
||||
"deny": ["exec"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"accounts": {
|
||||
"default": {
|
||||
"enabled": true,
|
||||
"name": "Default WhatsApp"
|
||||
}
|
||||
}
|
||||
}
|
||||
13
scripts/e2e/lib/upgrade-survivor/config-recipe/gateway.json
Normal file
13
scripts/e2e/lib/upgrade-survivor/config-recipe/gateway.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"mode": "local",
|
||||
"port": 18789,
|
||||
"bind": "loopback",
|
||||
"auth": {
|
||||
"mode": "token",
|
||||
"token": {
|
||||
"source": "env",
|
||||
"provider": "default",
|
||||
"id": "GATEWAY_AUTH_TOKEN_REF"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"api": "openai-responses",
|
||||
"apiKey": {
|
||||
"source": "env",
|
||||
"provider": "default",
|
||||
"id": "OPENAI_API_KEY"
|
||||
},
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"models": []
|
||||
}
|
||||
15
scripts/e2e/lib/upgrade-survivor/config-recipe/plugins.json
Normal file
15
scripts/e2e/lib/upgrade-survivor/config-recipe/plugins.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"allow": ["discord", "memory", "telegram", "whatsapp"],
|
||||
"entries": {
|
||||
"discord": {
|
||||
"enabled": true
|
||||
},
|
||||
"telegram": {
|
||||
"enabled": true
|
||||
},
|
||||
"whatsapp": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"allowBundled": ["memory", "openclaw-testing"],
|
||||
"limits": {
|
||||
"maxSkillsInPrompt": 8,
|
||||
"maxSkillsPromptChars": 30000
|
||||
}
|
||||
}
|
||||
405
scripts/e2e/lib/upgrade-survivor/run.sh
Normal file
405
scripts/e2e/lib/upgrade-survivor/run.sh
Normal file
@@ -0,0 +1,405 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
source scripts/lib/openclaw-e2e-instance.sh
|
||||
|
||||
export npm_config_loglevel=error
|
||||
export npm_config_fund=false
|
||||
export npm_config_audit=false
|
||||
export CI=true
|
||||
export OPENCLAW_NO_ONBOARD=1
|
||||
export OPENCLAW_NO_PROMPT=1
|
||||
export OPENCLAW_SKIP_PROVIDERS=1
|
||||
export OPENCLAW_SKIP_CHANNELS=1
|
||||
export OPENCLAW_DISABLE_BONJOUR=1
|
||||
export GATEWAY_AUTH_TOKEN_REF="upgrade-survivor-token"
|
||||
export OPENAI_API_KEY="sk-openclaw-upgrade-survivor"
|
||||
export DISCORD_BOT_TOKEN="upgrade-survivor-discord-token"
|
||||
export TELEGRAM_BOT_TOKEN="123456:upgrade-survivor-telegram-token"
|
||||
|
||||
ARTIFACT_ROOT="$(dirname "${OPENCLAW_UPGRADE_SURVIVOR_SUMMARY_JSON:-/tmp/openclaw-upgrade-survivor-artifacts/summary.json}")"
|
||||
mkdir -p "$ARTIFACT_ROOT"
|
||||
export TMPDIR="$ARTIFACT_ROOT/tmp"
|
||||
mkdir -p "$TMPDIR"
|
||||
export npm_config_prefix="$ARTIFACT_ROOT/npm-prefix"
|
||||
export NPM_CONFIG_PREFIX="$npm_config_prefix"
|
||||
export npm_config_cache="$ARTIFACT_ROOT/npm-cache"
|
||||
export npm_config_tmp="$TMPDIR"
|
||||
mkdir -p "$npm_config_prefix" "$npm_config_cache"
|
||||
export PATH="$npm_config_prefix/bin:$PATH"
|
||||
|
||||
SUMMARY_JSON="${OPENCLAW_UPGRADE_SURVIVOR_SUMMARY_JSON:-$ARTIFACT_ROOT/summary.json}"
|
||||
PHASE_LOG="$ARTIFACT_ROOT/phases.jsonl"
|
||||
: >"$PHASE_LOG"
|
||||
BASELINE_RAW="${OPENCLAW_UPGRADE_SURVIVOR_BASELINE:?missing OPENCLAW_UPGRADE_SURVIVOR_BASELINE}"
|
||||
CANDIDATE_KIND="${OPENCLAW_UPGRADE_SURVIVOR_CANDIDATE_KIND:-tarball}"
|
||||
CANDIDATE_SPEC="${OPENCLAW_UPGRADE_SURVIVOR_CANDIDATE_SPEC:-${OPENCLAW_CURRENT_PACKAGE_TGZ:-}}"
|
||||
CURRENT_PHASE="setup"
|
||||
FAILURE_PHASE=""
|
||||
FAILURE_MESSAGE=""
|
||||
gateway_pid=""
|
||||
baseline_spec=""
|
||||
baseline_version=""
|
||||
baseline_version_expected="0"
|
||||
candidate_version=""
|
||||
installed_version=""
|
||||
start_seconds=""
|
||||
status_seconds=""
|
||||
|
||||
BASELINE_INSTALL_LOG="$ARTIFACT_ROOT/baseline-install.log"
|
||||
UPDATE_JSON="$ARTIFACT_ROOT/update.json"
|
||||
UPDATE_ERR="$ARTIFACT_ROOT/update.err"
|
||||
DOCTOR_LOG="$ARTIFACT_ROOT/doctor.log"
|
||||
GATEWAY_LOG="$ARTIFACT_ROOT/gateway.log"
|
||||
STATUS_JSON="$ARTIFACT_ROOT/status.json"
|
||||
STATUS_ERR="$ARTIFACT_ROOT/status.err"
|
||||
BASELINE_CONFIG_VALIDATE_LOG="$ARTIFACT_ROOT/baseline-config-validate.log"
|
||||
CONFIG_COVERAGE_JSON="$ARTIFACT_ROOT/config-recipe.json"
|
||||
export OPENCLAW_UPGRADE_SURVIVOR_CONFIG_COVERAGE_JSON="$CONFIG_COVERAGE_JSON"
|
||||
|
||||
normalize_baseline() {
|
||||
local raw="${BASELINE_RAW//[[:space:]]/}"
|
||||
if [ -z "$raw" ]; then
|
||||
echo "OPENCLAW_UPGRADE_SURVIVOR_BASELINE cannot be empty" >&2
|
||||
return 1
|
||||
fi
|
||||
case "$raw" in
|
||||
openclaw@*)
|
||||
baseline_spec="$raw"
|
||||
baseline_version="${raw#openclaw@}"
|
||||
;;
|
||||
*@*)
|
||||
echo "OPENCLAW_UPGRADE_SURVIVOR_BASELINE must be openclaw@<version> or a bare version" >&2
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
baseline_version="$raw"
|
||||
baseline_spec="openclaw@$raw"
|
||||
;;
|
||||
esac
|
||||
case "$baseline_version" in
|
||||
latest | beta)
|
||||
baseline_version=""
|
||||
baseline_version_expected="0"
|
||||
;;
|
||||
dev | main | "")
|
||||
echo "OPENCLAW_UPGRADE_SURVIVOR_BASELINE must be openclaw@latest, openclaw@beta, openclaw@<version>, or a bare version" >&2
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
baseline_version_expected="1"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
json_event() {
|
||||
local phase="$1"
|
||||
local status="$2"
|
||||
PHASE_EVENT_PHASE="$phase" PHASE_EVENT_STATUS="$status" node <<'NODE' >>"$PHASE_LOG"
|
||||
const event = {
|
||||
phase: process.env.PHASE_EVENT_PHASE,
|
||||
status: process.env.PHASE_EVENT_STATUS,
|
||||
at: new Date().toISOString(),
|
||||
};
|
||||
process.stdout.write(`${JSON.stringify(event)}\n`);
|
||||
NODE
|
||||
}
|
||||
|
||||
write_summary() {
|
||||
local status="$1"
|
||||
local message="${2:-}"
|
||||
mkdir -p "$(dirname "$SUMMARY_JSON")"
|
||||
SUMMARY_STATUS="$status" \
|
||||
SUMMARY_MESSAGE="$message" \
|
||||
SUMMARY_PHASE_LOG="$PHASE_LOG" \
|
||||
SUMMARY_JSON="$SUMMARY_JSON" \
|
||||
SUMMARY_BASELINE_SPEC="$baseline_spec" \
|
||||
SUMMARY_BASELINE_VERSION="$baseline_version" \
|
||||
SUMMARY_CANDIDATE_VERSION="$candidate_version" \
|
||||
SUMMARY_INSTALLED_VERSION="$installed_version" \
|
||||
SUMMARY_START_SECONDS="$start_seconds" \
|
||||
SUMMARY_STATUS_SECONDS="$status_seconds" \
|
||||
SUMMARY_FAILURE_PHASE="$FAILURE_PHASE" \
|
||||
SUMMARY_CONFIG_COVERAGE="$CONFIG_COVERAGE_JSON" \
|
||||
node <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
const phaseLog = process.env.SUMMARY_PHASE_LOG;
|
||||
const phases = fs.existsSync(phaseLog)
|
||||
? fs.readFileSync(phaseLog, "utf8").trim().split("\n").filter(Boolean).map((line) => JSON.parse(line))
|
||||
: [];
|
||||
const numberOrNull = (value) => {
|
||||
if (!value) return null;
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
};
|
||||
const readJsonOrNull = (file) => {
|
||||
if (!file || !fs.existsSync(file)) return null;
|
||||
return JSON.parse(fs.readFileSync(file, "utf8"));
|
||||
};
|
||||
const summary = {
|
||||
status: process.env.SUMMARY_STATUS,
|
||||
baseline: {
|
||||
spec: process.env.SUMMARY_BASELINE_SPEC || null,
|
||||
version: process.env.SUMMARY_BASELINE_VERSION || null,
|
||||
},
|
||||
candidate: {
|
||||
kind: process.env.OPENCLAW_UPGRADE_SURVIVOR_CANDIDATE_KIND || null,
|
||||
spec: process.env.OPENCLAW_UPGRADE_SURVIVOR_CANDIDATE_SPEC || process.env.OPENCLAW_CURRENT_PACKAGE_TGZ || null,
|
||||
version: process.env.SUMMARY_CANDIDATE_VERSION || null,
|
||||
},
|
||||
installedVersion: process.env.SUMMARY_INSTALLED_VERSION || null,
|
||||
timings: {
|
||||
startupSeconds: numberOrNull(process.env.SUMMARY_START_SECONDS),
|
||||
statusSeconds: numberOrNull(process.env.SUMMARY_STATUS_SECONDS),
|
||||
},
|
||||
config: readJsonOrNull(process.env.SUMMARY_CONFIG_COVERAGE),
|
||||
failure: process.env.SUMMARY_STATUS === "passed"
|
||||
? null
|
||||
: {
|
||||
phase: process.env.SUMMARY_FAILURE_PHASE || null,
|
||||
message: process.env.SUMMARY_MESSAGE || null,
|
||||
},
|
||||
phases,
|
||||
};
|
||||
fs.writeFileSync(process.env.SUMMARY_JSON, `${JSON.stringify(summary, null, 2)}\n`);
|
||||
NODE
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
openclaw_e2e_terminate_gateways "${gateway_pid:-}"
|
||||
}
|
||||
|
||||
on_error() {
|
||||
local status="$1"
|
||||
FAILURE_PHASE="${CURRENT_PHASE:-unknown}"
|
||||
FAILURE_MESSAGE="phase ${FAILURE_PHASE} failed with status ${status}"
|
||||
json_event "$FAILURE_PHASE" failed || true
|
||||
return "$status"
|
||||
}
|
||||
|
||||
on_exit() {
|
||||
local status="$1"
|
||||
set +e
|
||||
cleanup
|
||||
if [ "$status" -eq 0 ]; then
|
||||
write_summary passed ""
|
||||
else
|
||||
[ -n "$FAILURE_PHASE" ] || FAILURE_PHASE="${CURRENT_PHASE:-unknown}"
|
||||
[ -n "$FAILURE_MESSAGE" ] || FAILURE_MESSAGE="upgrade survivor failed with status $status"
|
||||
write_summary failed "$FAILURE_MESSAGE"
|
||||
fi
|
||||
echo "Upgrade survivor summary: $SUMMARY_JSON"
|
||||
cat "$SUMMARY_JSON" 2>/dev/null || true
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
trap 'on_error $?' ERR
|
||||
trap 'on_exit $?' EXIT
|
||||
|
||||
phase() {
|
||||
local name="$1"
|
||||
shift
|
||||
CURRENT_PHASE="$name"
|
||||
echo "==> upgrade-survivor:$name"
|
||||
json_event "$name" started
|
||||
"$@"
|
||||
json_event "$name" passed
|
||||
CURRENT_PHASE=""
|
||||
}
|
||||
|
||||
package_root() {
|
||||
printf '%s/lib/node_modules/openclaw\n' "$npm_config_prefix"
|
||||
}
|
||||
|
||||
read_installed_version() {
|
||||
node -p 'JSON.parse(require("node:fs").readFileSync(process.argv[1] + "/package.json", "utf8")).version' "$(package_root)"
|
||||
}
|
||||
|
||||
storage_preflight() {
|
||||
echo "Storage preflight:"
|
||||
df -h "$ARTIFACT_ROOT" "$TMPDIR" /tmp || true
|
||||
}
|
||||
|
||||
reset_run_state() {
|
||||
rm -rf "$npm_config_prefix" "$TMPDIR" "$ARTIFACT_ROOT/state-home"
|
||||
mkdir -p "$npm_config_prefix" "$npm_config_cache" "$TMPDIR"
|
||||
}
|
||||
|
||||
install_baseline() {
|
||||
normalize_baseline
|
||||
echo "Installing baseline package: $baseline_spec"
|
||||
if ! npm install -g --prefix "$npm_config_prefix" "$baseline_spec" --no-fund --no-audit >"$BASELINE_INSTALL_LOG" 2>&1; then
|
||||
echo "baseline npm install failed" >&2
|
||||
cat "$BASELINE_INSTALL_LOG" >&2 || true
|
||||
return 1
|
||||
fi
|
||||
if ! command -v openclaw >/dev/null; then
|
||||
echo "baseline install did not expose openclaw on PATH" >&2
|
||||
echo "PATH=$PATH" >&2
|
||||
find "$npm_config_prefix" -maxdepth 3 -type f -o -type l >&2 || true
|
||||
return 1
|
||||
fi
|
||||
installed_version="$(read_installed_version)"
|
||||
if [ "$baseline_version_expected" = "1" ] && [ "$installed_version" != "$baseline_version" ]; then
|
||||
echo "baseline package version mismatch: expected $baseline_version, got $installed_version" >&2
|
||||
cat "$(package_root)/package.json" >&2 || true
|
||||
return 1
|
||||
fi
|
||||
baseline_version="$installed_version"
|
||||
local version_output
|
||||
if ! version_output="$(openclaw --version 2>&1)"; then
|
||||
echo "baseline openclaw --version failed" >&2
|
||||
echo "$version_output" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ "$version_output" != *"$baseline_version"* ]]; then
|
||||
echo "baseline openclaw --version mismatch: expected output to include $baseline_version" >&2
|
||||
echo "$version_output" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
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
|
||||
node scripts/e2e/lib/upgrade-survivor/assertions.mjs seed
|
||||
}
|
||||
|
||||
apply_baseline_config_recipe() {
|
||||
node scripts/e2e/lib/upgrade-survivor/config-recipe.mjs apply \
|
||||
--summary "$CONFIG_COVERAGE_JSON" \
|
||||
--baseline-version "$baseline_version"
|
||||
}
|
||||
|
||||
validate_baseline_config() {
|
||||
if ! openclaw config validate >"$BASELINE_CONFIG_VALIDATE_LOG" 2>&1; then
|
||||
echo "generated baseline config failed baseline validation" >&2
|
||||
cat "$BASELINE_CONFIG_VALIDATE_LOG" >&2 || true
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_baseline_state() {
|
||||
node scripts/e2e/lib/upgrade-survivor/assertions.mjs assert-config
|
||||
node scripts/e2e/lib/upgrade-survivor/assertions.mjs assert-state
|
||||
}
|
||||
|
||||
resolve_candidate_version() {
|
||||
if [ -z "$CANDIDATE_SPEC" ]; then
|
||||
echo "missing OPENCLAW_UPGRADE_SURVIVOR_CANDIDATE_SPEC" >&2
|
||||
return 1
|
||||
fi
|
||||
case "$CANDIDATE_KIND" in
|
||||
tarball)
|
||||
candidate_version="$(
|
||||
node -e '
|
||||
const { execFileSync } = require("node:child_process");
|
||||
const packageJson = execFileSync("tar", ["-xOf", process.argv[1], "package/package.json"], {
|
||||
encoding: "utf8",
|
||||
});
|
||||
process.stdout.write(JSON.parse(packageJson).version);
|
||||
' "$CANDIDATE_SPEC"
|
||||
)"
|
||||
;;
|
||||
npm)
|
||||
candidate_version="$(npm view "$CANDIDATE_SPEC" version --silent)"
|
||||
;;
|
||||
*)
|
||||
echo "unknown candidate kind: $CANDIDATE_KIND" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
if [ -z "$candidate_version" ]; then
|
||||
echo "could not resolve candidate version from $CANDIDATE_KIND:$CANDIDATE_SPEC" >&2
|
||||
return 1
|
||||
fi
|
||||
OPENCLAW_PACKAGE_ACCEPTANCE_LEGACY_COMPAT="$(
|
||||
node scripts/e2e/lib/package-compat.mjs "$candidate_version"
|
||||
)"
|
||||
export OPENCLAW_PACKAGE_ACCEPTANCE_LEGACY_COMPAT
|
||||
}
|
||||
|
||||
update_candidate() {
|
||||
echo "Updating baseline $baseline_spec to candidate $CANDIDATE_KIND:$CANDIDATE_SPEC ($candidate_version)"
|
||||
if ! openclaw update --tag "$CANDIDATE_SPEC" --yes --json --no-restart >"$UPDATE_JSON" 2>"$UPDATE_ERR"; then
|
||||
echo "openclaw update failed" >&2
|
||||
cat "$UPDATE_ERR" >&2 || true
|
||||
cat "$UPDATE_JSON" >&2 || true
|
||||
return 1
|
||||
fi
|
||||
installed_version="$(read_installed_version)"
|
||||
}
|
||||
|
||||
run_doctor() {
|
||||
if ! openclaw doctor --fix --non-interactive >"$DOCTOR_LOG" 2>&1; then
|
||||
echo "openclaw doctor failed" >&2
|
||||
cat "$DOCTOR_LOG" >&2 || true
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_survival() {
|
||||
node scripts/e2e/lib/upgrade-survivor/assertions.mjs assert-config
|
||||
node scripts/e2e/lib/upgrade-survivor/assertions.mjs assert-state
|
||||
installed_version="$(read_installed_version)"
|
||||
if [ "$installed_version" != "$candidate_version" ]; then
|
||||
echo "candidate package version mismatch: expected $candidate_version, got $installed_version" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
start_gateway() {
|
||||
local port=18789
|
||||
local budget="${OPENCLAW_UPGRADE_SURVIVOR_START_BUDGET_SECONDS:-90}"
|
||||
local start_epoch
|
||||
local ready_epoch
|
||||
start_epoch="$(node -e "process.stdout.write(String(Date.now()))")"
|
||||
openclaw gateway --port "$port" --bind loopback --allow-unconfigured >"$GATEWAY_LOG" 2>&1 &
|
||||
gateway_pid="$!"
|
||||
openclaw_e2e_wait_gateway_ready "$gateway_pid" "$GATEWAY_LOG" 360
|
||||
ready_epoch="$(node -e "process.stdout.write(String(Date.now()))")"
|
||||
start_seconds=$(((ready_epoch - start_epoch + 999) / 1000))
|
||||
if [ "$start_seconds" -gt "$budget" ]; then
|
||||
echo "gateway startup exceeded survivor budget: ${start_seconds}s > ${budget}s" >&2
|
||||
cat "$GATEWAY_LOG" >&2 || true
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_gateway_status() {
|
||||
local port=18789
|
||||
local budget="${OPENCLAW_UPGRADE_SURVIVOR_STATUS_BUDGET_SECONDS:-30}"
|
||||
local status_start
|
||||
local status_end
|
||||
status_start="$(node -e "process.stdout.write(String(Date.now()))")"
|
||||
if ! openclaw gateway status --url "ws://127.0.0.1:$port" --token "$GATEWAY_AUTH_TOKEN_REF" --require-rpc --timeout 30000 --json >"$STATUS_JSON" 2>"$STATUS_ERR"; then
|
||||
echo "gateway status failed" >&2
|
||||
cat "$STATUS_ERR" >&2 || true
|
||||
cat "$GATEWAY_LOG" >&2 || true
|
||||
return 1
|
||||
fi
|
||||
status_end="$(node -e "process.stdout.write(String(Date.now()))")"
|
||||
status_seconds=$(((status_end - status_start + 999) / 1000))
|
||||
if [ "$status_seconds" -gt "$budget" ]; then
|
||||
echo "gateway status exceeded survivor budget: ${status_seconds}s > ${budget}s" >&2
|
||||
cat "$STATUS_JSON" >&2 || true
|
||||
return 1
|
||||
fi
|
||||
node scripts/e2e/lib/upgrade-survivor/assertions.mjs assert-status-json "$STATUS_JSON"
|
||||
}
|
||||
|
||||
phase storage-preflight storage_preflight
|
||||
phase reset-run-state reset_run_state
|
||||
phase install-baseline install_baseline
|
||||
phase seed-state seed_state
|
||||
phase apply-baseline-config-recipe apply_baseline_config_recipe
|
||||
phase validate-baseline-config validate_baseline_config
|
||||
phase assert-baseline assert_baseline_state
|
||||
phase resolve-candidate resolve_candidate_version
|
||||
phase update-candidate update_candidate
|
||||
phase doctor run_doctor
|
||||
phase assert-survival assert_survival
|
||||
phase gateway-start start_gateway
|
||||
phase gateway-status check_gateway_status
|
||||
|
||||
echo "Upgrade survivor Docker E2E passed baseline=${baseline_spec} candidate=${candidate_version} startup=${start_seconds}s status=${status_seconds}s."
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# Installs the packed OpenClaw tarball over a dirty old-user state fixture, runs
|
||||
# the package update/doctor paths, then proves the Gateway still boots.
|
||||
# Installs the packed OpenClaw tarball over dirty old-user state. When
|
||||
# OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC is set, installs that published
|
||||
# baseline first and upgrades it to the selected candidate.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
@@ -9,12 +10,91 @@ source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-upgrade-survivor-e2e" OPENCLAW_UPGRADE_SURVIVOR_E2E_IMAGE)"
|
||||
SKIP_BUILD="${OPENCLAW_UPGRADE_SURVIVOR_E2E_SKIP_BUILD:-0}"
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz upgrade-survivor "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}")"
|
||||
DOCKER_RUN_TIMEOUT="${OPENCLAW_UPGRADE_SURVIVOR_DOCKER_RUN_TIMEOUT:-900s}"
|
||||
BASELINE_SPEC="${OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC:-}"
|
||||
ARTIFACT_DIR="${OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_DIR:-$ROOT_DIR/.artifacts/upgrade-survivor}"
|
||||
|
||||
normalize_npm_candidate() {
|
||||
local raw="$1"
|
||||
case "$raw" in
|
||||
latest | beta)
|
||||
printf 'openclaw@%s\n' "$raw"
|
||||
;;
|
||||
openclaw@*)
|
||||
printf '%s\n' "$raw"
|
||||
;;
|
||||
*@*)
|
||||
echo "OPENCLAW_UPGRADE_SURVIVOR_CANDIDATE must be current, latest, beta, openclaw@<version>, a bare version, or a .tgz path." >&2
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
printf 'openclaw@%s\n' "$raw"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [ "${OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE:-0}" = "1" ]; then
|
||||
if [ -z "${BASELINE_SPEC// }" ]; then
|
||||
echo "OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC is required for published upgrade survivor" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$ARTIFACT_DIR"
|
||||
|
||||
DOCKER_E2E_PACKAGE_ARGS=()
|
||||
CANDIDATE_RAW="${OPENCLAW_UPGRADE_SURVIVOR_CANDIDATE:-current}"
|
||||
CANDIDATE_KIND="npm"
|
||||
CANDIDATE_SPEC=""
|
||||
|
||||
if [ -n "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}" ]; then
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz upgrade-survivor "$OPENCLAW_CURRENT_PACKAGE_TGZ")"
|
||||
docker_e2e_package_mount_args "$PACKAGE_TGZ"
|
||||
CANDIDATE_KIND="tarball"
|
||||
CANDIDATE_SPEC="/tmp/openclaw-current.tgz"
|
||||
elif [ "$CANDIDATE_RAW" = "current" ]; then
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz upgrade-survivor)"
|
||||
docker_e2e_package_mount_args "$PACKAGE_TGZ"
|
||||
CANDIDATE_KIND="tarball"
|
||||
CANDIDATE_SPEC="/tmp/openclaw-current.tgz"
|
||||
elif [[ "$CANDIDATE_RAW" == *.tgz ]]; then
|
||||
if [ ! -f "$CANDIDATE_RAW" ]; then
|
||||
echo "OpenClaw candidate tarball does not exist: $CANDIDATE_RAW" >&2
|
||||
exit 1
|
||||
fi
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz upgrade-survivor "$CANDIDATE_RAW")"
|
||||
docker_e2e_package_mount_args "$PACKAGE_TGZ"
|
||||
CANDIDATE_KIND="tarball"
|
||||
CANDIDATE_SPEC="/tmp/openclaw-current.tgz"
|
||||
else
|
||||
CANDIDATE_KIND="npm"
|
||||
CANDIDATE_SPEC="$(normalize_npm_candidate "$CANDIDATE_RAW")"
|
||||
fi
|
||||
|
||||
OPENCLAW_TEST_STATE_FUNCTION_B64="$(docker_e2e_test_state_function_b64)"
|
||||
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" upgrade-survivor "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "bare" "$SKIP_BUILD"
|
||||
|
||||
echo "Running published upgrade survivor Docker E2E..."
|
||||
docker_e2e_run_with_harness \
|
||||
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
||||
-e OPENCLAW_TEST_STATE_FUNCTION_B64="$OPENCLAW_TEST_STATE_FUNCTION_B64" \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_BASELINE="$BASELINE_SPEC" \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_CANDIDATE_KIND="$CANDIDATE_KIND" \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_CANDIDATE_SPEC="$CANDIDATE_SPEC" \
|
||||
-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[@]}" \
|
||||
"$IMAGE_NAME" \
|
||||
timeout "$DOCKER_RUN_TIMEOUT" bash scripts/e2e/lib/upgrade-survivor/run.sh
|
||||
exit 0
|
||||
fi
|
||||
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz upgrade-survivor "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}")"
|
||||
docker_e2e_package_mount_args "$PACKAGE_TGZ"
|
||||
OPENCLAW_TEST_STATE_SCRIPT_B64="$(docker_e2e_test_state_shell_b64 upgrade-survivor upgrade-survivor)"
|
||||
mkdir -p "$ARTIFACT_DIR"
|
||||
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" upgrade-survivor "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "bare" "$SKIP_BUILD"
|
||||
|
||||
@@ -22,9 +102,10 @@ echo "Running upgrade survivor Docker E2E..."
|
||||
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_START_BUDGET_SECONDS="${OPENCLAW_UPGRADE_SURVIVOR_START_BUDGET_SECONDS:-90}" \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_STATUS_BUDGET_SECONDS="${OPENCLAW_UPGRADE_SURVIVOR_STATUS_BUDGET_SECONDS:-30}" \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC="$BASELINE_SPEC" \
|
||||
-v "$ARTIFACT_DIR:/tmp/openclaw-upgrade-survivor-artifacts" \
|
||||
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
|
||||
"$IMAGE_NAME" \
|
||||
timeout "$DOCKER_RUN_TIMEOUT" bash -lc 'set -euo pipefail
|
||||
@@ -33,9 +114,16 @@ source scripts/lib/openclaw-e2e-instance.sh
|
||||
export npm_config_loglevel=error
|
||||
export npm_config_fund=false
|
||||
export npm_config_audit=false
|
||||
export npm_config_prefix=/tmp/npm-prefix
|
||||
export NPM_CONFIG_PREFIX=/tmp/npm-prefix
|
||||
export PATH="/tmp/npm-prefix/bin:$PATH"
|
||||
export OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_ROOT="${OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_ROOT:-/tmp/openclaw-upgrade-survivor-artifacts}"
|
||||
mkdir -p "$OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_ROOT"
|
||||
export TMPDIR="$OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_ROOT/tmp"
|
||||
export OPENCLAW_TEST_STATE_TMPDIR="$OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_ROOT/state-tmp"
|
||||
export npm_config_prefix="$OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_ROOT/npm-prefix"
|
||||
export NPM_CONFIG_PREFIX="$npm_config_prefix"
|
||||
export npm_config_cache="$OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_ROOT/npm-cache"
|
||||
export npm_config_tmp="$TMPDIR"
|
||||
mkdir -p "$TMPDIR" "$OPENCLAW_TEST_STATE_TMPDIR" "$npm_config_prefix" "$npm_config_cache"
|
||||
export PATH="$npm_config_prefix/bin:$PATH"
|
||||
export CI=true
|
||||
export OPENCLAW_NO_ONBOARD=1
|
||||
export OPENCLAW_NO_PROMPT=1
|
||||
@@ -56,18 +144,9 @@ trap cleanup EXIT
|
||||
openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}"
|
||||
node scripts/e2e/lib/upgrade-survivor/assertions.mjs seed
|
||||
|
||||
if [ -n "${OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC:-}" ]; then
|
||||
echo "Installing published upgrade survivor baseline: ${OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC}"
|
||||
if ! npm install -g --prefix /tmp/npm-prefix "$OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC" --no-fund --no-audit >/tmp/openclaw-upgrade-survivor-install.log 2>&1; then
|
||||
echo "npm install failed for upgrade survivor baseline" >&2
|
||||
cat /tmp/openclaw-upgrade-survivor-install.log >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
openclaw_e2e_install_package /tmp/openclaw-upgrade-survivor-install.log "upgrade survivor package" /tmp/npm-prefix
|
||||
fi
|
||||
openclaw_e2e_install_package "$OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_ROOT/install.log" "upgrade survivor package" "$npm_config_prefix"
|
||||
command -v openclaw >/dev/null
|
||||
package_version="$(node -p "JSON.parse(require(\"node:fs\").readFileSync(\"/tmp/npm-prefix/lib/node_modules/openclaw/package.json\", \"utf8\")).version")"
|
||||
package_version="$(node -p "JSON.parse(require(\"node:fs\").readFileSync(process.argv[1] + \"/lib/node_modules/openclaw/package.json\", \"utf8\")).version" "$npm_config_prefix")"
|
||||
OPENCLAW_PACKAGE_ACCEPTANCE_LEGACY_COMPAT="$(
|
||||
node scripts/e2e/lib/package-compat.mjs "$package_version"
|
||||
)"
|
||||
|
||||
Reference in New Issue
Block a user