diff --git a/scripts/e2e/doctor-install-switch-docker.sh b/scripts/e2e/doctor-install-switch-docker.sh index 55427958fa1..12a30fb7938 100755 --- a/scripts/e2e/doctor-install-switch-docker.sh +++ b/scripts/e2e/doctor-install-switch-docker.sh @@ -15,6 +15,7 @@ docker run --rm -e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 "$IMAGE_NAME" bash -lc ' export npm_config_loglevel=error export npm_config_fund=false export npm_config_audit=false + export OPENCLAW_DISABLE_BUNDLED_PLUGINS=1 # Stub systemd/loginctl so doctor + daemon flows work in Docker. export PATH="/tmp/openclaw-bin:$PATH" @@ -130,13 +131,14 @@ LOGINCTL local doctor_expected="$5" local install_log="/tmp/openclaw-doctor-switch-${name}-install.log" local doctor_log="/tmp/openclaw-doctor-switch-${name}-doctor.log" + local command_timeout="${OPENCLAW_DOCKER_DOCTOR_SWITCH_COMMAND_TIMEOUT:-300s}" echo "== Flow: $name ==" home_dir=$(mktemp -d "/tmp/openclaw-switch-${name}.XXXXXX") export HOME="$home_dir" export USER="testuser" - if ! eval "$install_cmd" >"$install_log" 2>&1; then + if ! timeout "$command_timeout" bash -c "$install_cmd" >"$install_log" 2>&1; then cat "$install_log" exit 1 fi @@ -150,7 +152,7 @@ LOGINCTL fi assert_entrypoint "$unit_path" "$install_expected" - if ! eval "$doctor_cmd" >"$doctor_log" 2>&1; then + if ! timeout "$command_timeout" bash -c "$doctor_cmd" >"$doctor_log" 2>&1; then cat "$doctor_log" exit 1 fi diff --git a/scripts/e2e/mcp-channels-docker.sh b/scripts/e2e/mcp-channels-docker.sh index e52f20cd10e..a0b73fceba2 100644 --- a/scripts/e2e/mcp-channels-docker.sh +++ b/scripts/e2e/mcp-channels-docker.sh @@ -35,12 +35,25 @@ docker run --rm \ bash -lc "set -euo pipefail entry=dist/index.mjs [ -f \"\$entry\" ] || entry=dist/index.js + mock_port=44081 + export OPENCLAW_DOCKER_OPENAI_BASE_URL=\"http://127.0.0.1:\$mock_port/v1\" + MOCK_PORT=\"\$mock_port\" node scripts/e2e/mock-openai-server.mjs >/tmp/mcp-channels-mock-openai.log 2>&1 & + mock_pid=\$! + for _ in \$(seq 1 80); do + if node -e \"fetch('http://127.0.0.1:' + process.argv[1] + '/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\" \"\$mock_port\"; then + break + fi + sleep 0.1 + done + node -e \"fetch('http://127.0.0.1:' + process.argv[1] + '/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\" \"\$mock_port\" node --import tsx scripts/e2e/mcp-channels-seed.ts >/tmp/mcp-channels-seed.log node \"\$entry\" gateway --port $PORT --bind loopback --allow-unconfigured >/tmp/mcp-channels-gateway.log 2>&1 & gateway_pid=\$! cleanup_inner() { kill \"\$gateway_pid\" >/dev/null 2>&1 || true wait \"\$gateway_pid\" >/dev/null 2>&1 || true + kill \"\$mock_pid\" >/dev/null 2>&1 || true + wait \"\$mock_pid\" >/dev/null 2>&1 || true } dump_gateway_log_on_error() { status=\$? diff --git a/scripts/lib/live-docker-stage.sh b/scripts/lib/live-docker-stage.sh index 18d98850ef9..74bb8a71472 100644 --- a/scripts/lib/live-docker-stage.sh +++ b/scripts/lib/live-docker-stage.sh @@ -2,6 +2,25 @@ openclaw_live_stage_source_tree() { local dest_dir="${1:?destination directory required}" + local stage_mode="${OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE:-copy}" + + if [ "$stage_mode" = "symlink" ]; then + mkdir -p "$dest_dir" + local entry + while IFS= read -r -d "" entry; do + entry="${entry#./}" + case "$entry" in + .git | node_modules | dist | .pnpm-store | .tmp | .tmp-precommit-venv | .worktrees | __openclaw_vitest__ | relay.sock) + continue + ;; + *.sock) + continue + ;; + esac + ln -s "/src/$entry" "$dest_dir/$entry" + done < <(cd /src && find . -mindepth 1 -maxdepth 1 -print0) + return 0 + fi set +e tar -C /src \ diff --git a/scripts/test-docker-all.mjs b/scripts/test-docker-all.mjs index 07fd49a21a9..d53d810da48 100644 --- a/scripts/test-docker-all.mjs +++ b/scripts/test-docker-all.mjs @@ -43,15 +43,21 @@ const bundledScenarioLanes = [ ]; const lanes = [ - ["live-models", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-models"], - ["live-gateway", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-gateway"], + [ + "live-models", + "OPENCLAW_SKIP_DOCKER_BUILD=1 OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE=symlink pnpm test:docker:live-models", + ], + [ + "live-gateway", + "OPENCLAW_SKIP_DOCKER_BUILD=1 OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE=symlink pnpm test:docker:live-gateway", + ], [ "live-cli-backend-claude", - "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-cli-backend:claude", + "OPENCLAW_SKIP_DOCKER_BUILD=1 OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE=symlink pnpm test:docker:live-cli-backend:claude", ], [ "live-cli-backend-gemini", - "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-cli-backend:gemini", + "OPENCLAW_SKIP_DOCKER_BUILD=1 OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE=symlink pnpm test:docker:live-cli-backend:gemini", ], ["openwebui", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:openwebui"], ["onboard", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:onboard"], @@ -77,15 +83,30 @@ const exclusiveLanes = [ "openai-web-search-minimal", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:openai-web-search-minimal", ], - ["live-codex-harness", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-codex-harness"], - ["live-codex-bind", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-codex-bind"], + [ + "live-codex-harness", + "OPENCLAW_SKIP_DOCKER_BUILD=1 OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE=symlink pnpm test:docker:live-codex-harness", + ], + [ + "live-codex-bind", + "OPENCLAW_SKIP_DOCKER_BUILD=1 OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE=symlink pnpm test:docker:live-codex-bind", + ], [ "live-cli-backend-codex", - "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-cli-backend:codex", + "OPENCLAW_SKIP_DOCKER_BUILD=1 OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE=symlink pnpm test:docker:live-cli-backend:codex", + ], + [ + "live-acp-bind-claude", + "OPENCLAW_SKIP_DOCKER_BUILD=1 OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE=symlink pnpm test:docker:live-acp-bind:claude", + ], + [ + "live-acp-bind-codex", + "OPENCLAW_SKIP_DOCKER_BUILD=1 OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE=symlink pnpm test:docker:live-acp-bind:codex", + ], + [ + "live-acp-bind-gemini", + "OPENCLAW_SKIP_DOCKER_BUILD=1 OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE=symlink pnpm test:docker:live-acp-bind:gemini", ], - ["live-acp-bind-claude", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-acp-bind:claude"], - ["live-acp-bind-codex", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-acp-bind:codex"], - ["live-acp-bind-gemini", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-acp-bind:gemini"], ]; const tailLanes = exclusiveLanes;