openclaw/scripts/lib/docker-e2e-logs.sh
Peter Steinberger 3cb68020b6
refactor(scripts): deslop shared tooling
## What Problem This Solves

Repository scripts retain private copies of shared tooling helpers and an unused translation subprocess runner, increasing maintenance work across sibling scripts.

## User Impact

Internal tooling cleanup: existing flags, diagnostics, generated bytes, JSON formats, exit codes, and cleanup policies stay the same. The build wrapper additionally detects forbidden dynamic-import warnings when compiler output splits the warning marker across chunks.

## Why This Change Was Made

- Remove the retired i18n process runner. Its final production caller moved to the shared formatter in #95534; retain real CLI/privacy/provider tests and run their subprocesses with the existing dependency.
- Share Docker resource, signal-trap, platform, workspace-staging, and plugin-selection helpers while retaining each caller's environment precedence and lifecycle decisions.
- Reuse existing E2E fixture JSON/assertion and package-path helpers; retain distinct write modes, recovery ordering, and mounted/frozen harness contracts.
- Share macOS guest desktop-user/home resolution through its existing owner while preserving per-caller timeout policy.
- Route release/mobile flags through the existing version flag specifications, preserving split-only values, duplicate/mode rules, help timing, and error text. Reuse existing retry sleep and comparator owners and remove an unreachable iOS output branch.
- Reuse existing guard entrypoint, failure-trailer, diagnostic-line, and metadata-normalization owners.

Measured reduction: **732 net production lines**, with test changes counted separately. PR tooling and its protected import closure, CI planners/shards, baselines, and generated artifacts are unchanged.

## Fixes Found Along the Way

The tsdown scanner checked each raw output chunk for its warning marker. It now checks the existing combined-line buffer. The regression exercises every split inside the marker and fails on the original source for the intended missing-warning assertion.

## Evidence

- Blacksmith Testbox `tbx_01m3tp4sabwd0807kj9jkqqmbr`: frozen dependency install and candidate source-byte verification; all nine changed test files and 59 selected sibling files passed, including real CLI, package fixture, recovery, Docker harness, and Parallels transport contracts.
- Generated channel metadata is byte-identical; differential metadata normalization cases passed.
- SDK surface check passed. Independent isolated Codex review completed with no P0–P2 findings.
- Initial changed checks caught an invalid direct source import in a proposed snapshot-distance consolidation; that independent change was withdrawn. No boundary exception was added. The final changed checks pass, including script/test lint and Docker shell/scheduler checks; madge reports 0 cycles and the runtime import check reports 0 cycles.
- Per-file single-worker wall time for changed suites: translation 9.92s; Docker helper 38.88s; live Docker auth 1.71s; mobile ref 2.36s; mobile release 6.23s; release preparation 2.42s; version 2.04s; installer 15.01s; tsdown 2.70s. The existing Docker helper suite executes shell/process cleanup and container-command boundary fixtures; new assertions reuse those fixtures. New parser/scanner cases use no sleeps or polling.

Hosted CI will be verified against this PR's exact pushed head. No live deployment or release was performed.
2026-10-01 05:14:07 +00:00

211 lines
5.8 KiB
Bash

#!/usr/bin/env bash
#
# Shared logging helpers for shell-based Docker E2E lanes.
# They centralize temporary log naming and the small success/failure print
# pattern used by Docker scenario scripts.
docker_e2e_lifecycle_trace_enabled() {
case "${OPENCLAW_PLUGIN_LIFECYCLE_TRACE:-}" in
1 | true | TRUE | yes | YES)
return 0
;;
*)
return 1
;;
esac
}
docker_e2e_normalize_positive_int_value() {
local label="${1:?missing value label}"
local value="${2-}"
if [[ ! "$value" =~ ^[0-9]+$ ]] || (( 10#$value < 1 )); then
echo "invalid $label: $value" >&2
return 2
fi
printf '%s\n' "$((10#$value))"
}
docker_e2e_read_positive_int_env() {
local name="${1:?missing environment variable name}"
local fallback="${2:?missing fallback value}"
local value="${!name-}"
if [ -z "${!name+x}" ]; then
value="$fallback"
fi
docker_e2e_normalize_positive_int_value "$name" "$value"
}
docker_e2e_restore_signal_traps() {
local signal
for signal in INT TERM HUP; do
if [ -n "$1" ]; then
eval "$1"
else
trap - "$signal"
fi
shift
done
}
run_logged() {
docker_e2e_run_logged 0 "$@"
}
run_logged_print() {
docker_e2e_run_logged 1 "$@"
}
docker_e2e_run_logged() {
local print_success="$1"
local label="$2"
shift 2
docker_e2e_read_positive_int_env OPENCLAW_DOCKER_E2E_LOG_PRINT_BYTES 65536 >/dev/null || return $?
local log_file
log_file="$(docker_e2e_run_log "$label")"
if ! "$@" >"$log_file" 2>&1; then
local print_status=0
docker_e2e_print_log "$log_file" || print_status="$?"
rm -f "$log_file"
if [ "$print_status" -ne 0 ]; then
return "$print_status"
fi
return 1
fi
if [ "$print_success" = 1 ]; then
docker_e2e_print_log "$log_file" || {
local print_status="$?"
rm -f "$log_file"
return "$print_status"
}
fi
rm -f "$log_file"
}
docker_e2e_maybe_print_log_heartbeat() {
local label="$1"
local elapsed_seconds="$2"
local next_heartbeat="$3"
local log_file="$4"
if [ "$elapsed_seconds" -lt "$next_heartbeat" ]; then
return 1
fi
local log_bytes="0"
if [ -f "$log_file" ]; then
log_bytes="$(wc -c <"$log_file" 2>/dev/null || echo 0)"
log_bytes="${log_bytes//[[:space:]]/}"
fi
echo "still running $label (${elapsed_seconds}s elapsed, ${log_bytes} log bytes captured)"
}
run_logged_print_heartbeat() {
local label="$1"
local interval_seconds="$2"
shift 2
docker_e2e_read_positive_int_env OPENCLAW_DOCKER_E2E_LOG_PRINT_BYTES 65536 >/dev/null || return $?
interval_seconds="$(docker_e2e_normalize_positive_int_value "Docker E2E log heartbeat interval" "$interval_seconds")" || return $?
local heartbeat_term_grace_seconds
heartbeat_term_grace_seconds="$(
docker_e2e_read_positive_int_env OPENCLAW_DOCKER_E2E_HEARTBEAT_TERM_GRACE_SECONDS 30
)" || return $?
local log_file
log_file="$(docker_e2e_run_log "$label")"
local command_pid=""
local cleanup_done=0
local previous_int_trap
local previous_term_trap
local previous_hup_trap
previous_int_trap="$(trap -p INT || true)"
previous_term_trap="$(trap -p TERM || true)"
previous_hup_trap="$(trap -p HUP || true)"
terminate_heartbeat_command() {
if [ -z "$command_pid" ]; then
return 0
fi
kill -TERM "$command_pid" 2>/dev/null || true
local wait_attempt
for wait_attempt in $(seq 1 "$((heartbeat_term_grace_seconds * 10))"); do
if ! kill -0 "$command_pid" 2>/dev/null; then
return 0
fi
/bin/sleep 0.1
done
kill -KILL "$command_pid" 2>/dev/null || true
}
cleanup_heartbeat_command() {
local cleanup_status="${1:-$?}"
if [ "$cleanup_done" = "1" ]; then
return "$cleanup_status"
fi
cleanup_done=1
trap - INT TERM HUP
if kill -0 "$command_pid" 2>/dev/null; then
terminate_heartbeat_command
wait "$command_pid" 2>/dev/null || true
fi
if [ "$cleanup_status" -ne 0 ]; then
docker_e2e_print_log "$log_file" || true
fi
rm -f "$log_file"
docker_e2e_restore_signal_traps "$previous_int_trap" "$previous_term_trap" "$previous_hup_trap"
if [ "$cleanup_status" -ge 128 ]; then
exit "$cleanup_status"
fi
return "$cleanup_status"
}
trap 'cleanup_heartbeat_command 130' INT
trap 'cleanup_heartbeat_command 143' TERM
trap 'cleanup_heartbeat_command 129' HUP
"$@" <&0 >"$log_file" 2>&1 &
command_pid=$!
local started_at="$SECONDS"
local next_heartbeat=$interval_seconds
local status=0
while kill -0 "$command_pid" 2>/dev/null; do
# Poll promptly so short commands do not pay a one-second wrapper tax.
/bin/sleep 0.1
local elapsed_seconds=$((SECONDS - started_at))
if kill -0 "$command_pid" 2>/dev/null && \
docker_e2e_maybe_print_log_heartbeat "$label" "$elapsed_seconds" "$next_heartbeat" "$log_file"; then
next_heartbeat=$((elapsed_seconds + interval_seconds))
fi
done
set +e
wait "$command_pid"
status=$?
set -e
docker_e2e_print_log "$log_file" || {
local print_status="$?"
cleanup_heartbeat_command 0
return "$print_status"
}
cleanup_heartbeat_command 0
return "$status"
}
docker_e2e_run_log() {
local label="$1"
local tmp_dir="${TMPDIR:-/tmp}"
tmp_dir="${tmp_dir%/}"
mktemp "$tmp_dir/openclaw-${label}.XXXXXX"
}
docker_e2e_print_log() {
local log_file="$1"
local max_bytes
max_bytes="$(docker_e2e_read_positive_int_env OPENCLAW_DOCKER_E2E_LOG_PRINT_BYTES 65536)" || return $?
if [ ! -f "$log_file" ]; then
return 0
fi
local log_bytes
log_bytes="$(wc -c <"$log_file" 2>/dev/null || echo 0)"
log_bytes="${log_bytes//[[:space:]]/}"
if ! [[ "$log_bytes" =~ ^[0-9]+$ ]]; then
log_bytes="0"
fi
if [ "$log_bytes" -le "$max_bytes" ]; then
cat "$log_file"
return 0
fi
echo "--- ${log_file} truncated: showing last ${max_bytes} of ${log_bytes} bytes ---"
tail -c "$max_bytes" "$log_file"
}