diff --git a/scripts/lib/docker-e2e-package.sh b/scripts/lib/docker-e2e-package.sh index a326f1f1a6f..c98e3dca672 100644 --- a/scripts/lib/docker-e2e-package.sh +++ b/scripts/lib/docker-e2e-package.sh @@ -46,6 +46,7 @@ docker_e2e_prepare_package_tgz() { rm -rf "$pack_dir" return 1 fi + touch "$pack_dir/.openclaw-docker-e2e-generated-package" docker_e2e_abs_path "$package_tgz" } @@ -70,6 +71,33 @@ docker_e2e_package_mount_args() { DOCKER_E2E_PACKAGE_ARGS=(-v "$package_tgz:$target:ro" -e "OPENCLAW_CURRENT_PACKAGE_TGZ=$target") } +docker_e2e_cleanup_package_tgz() { + local package_tgz="${1:-}" + [ -n "$package_tgz" ] || return 0 + [ "$(basename "$package_tgz")" = "openclaw-current.tgz" ] || return 0 + + local pack_dir + pack_dir="$(dirname "$package_tgz")" + if [ -f "$pack_dir/.openclaw-docker-e2e-generated-package" ]; then + rm -rf "$pack_dir" + fi +} + +docker_e2e_cleanup_package_mount_args() { + local expect_volume_path=0 + local arg + for arg in "${DOCKER_E2E_PACKAGE_ARGS[@]:-}"; do + if [ "$expect_volume_path" = "1" ]; then + docker_e2e_cleanup_package_tgz "${arg%%:*}" + expect_volume_path=0 + continue + fi + if [ "$arg" = "-v" ]; then + expect_volume_path=1 + fi + done +} + docker_e2e_harness_mount_args() { DOCKER_E2E_HARNESS_ARGS=( -v "$ROOT_DIR/scripts/e2e:/app/scripts/e2e:ro" @@ -80,7 +108,10 @@ docker_e2e_harness_mount_args() { docker_e2e_run_with_harness() { docker_e2e_harness_mount_args - docker run --rm "${DOCKER_E2E_HARNESS_ARGS[@]}" "$@" + local run_status=0 + docker run --rm "${DOCKER_E2E_HARNESS_ARGS[@]}" "$@" || run_status="$?" + docker_e2e_cleanup_package_mount_args + return "$run_status" } docker_e2e_run_detached_with_harness() { diff --git a/test/scripts/docker-build-helper.test.ts b/test/scripts/docker-build-helper.test.ts index 1a29135e420..33f1a860861 100644 --- a/test/scripts/docker-build-helper.test.ts +++ b/test/scripts/docker-build-helper.test.ts @@ -255,6 +255,95 @@ fi } }); + it("cleans generated package mounts after harness Docker runs", () => { + const workDir = mkdtempSync(join(tmpdir(), "openclaw-docker-package-mount-cleanup-")); + + try { + const rootDir = process.cwd(); + const script = ` +set -euo pipefail +ROOT_DIR=${shellQuote(rootDir)} +TMPDIR=${shellQuote(workDir)} +export ROOT_DIR TMPDIR + +node() { + local script="$1" + shift + if [[ "$script" != "$ROOT_DIR/scripts/package-openclaw-for-docker.mjs" ]]; then + command node "$script" "$@" + return + fi + + local output_dir="" + local output_name="" + while [[ $# -gt 0 ]]; do + case "$1" in + --output-dir) + output_dir="$2" + shift 2 + ;; + --output-name) + output_name="$2" + shift 2 + ;; + *) + shift + ;; + esac + done + + mkdir -p "$output_dir" + printf fixture >"$output_dir/$output_name" + printf "%s\\n" "$output_dir/$output_name" +} +export -f node + +source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh" + +docker() { + local mount_path="" + local expect_volume_path=0 + local arg + for arg in "$@"; do + if [[ "$expect_volume_path" == "1" ]]; then + mount_path="\${arg%%:*}" + expect_volume_path=0 + continue + fi + if [[ "$arg" == "-v" ]]; then + expect_volume_path=1 + fi + done + + test -n "$mount_path" + test -f "$mount_path" + printf "%s\\n" "$mount_path" >"$TMPDIR/package-mount-seen" + return "\${DOCKER_STUB_STATUS:-0}" +} +export -f docker + +package_tgz="$(docker_e2e_prepare_package_tgz mount-cleanup)" +pack_dir="$(dirname "$package_tgz")" +docker_e2e_package_mount_args "$package_tgz" +DOCKER_STUB_STATUS=7 docker_e2e_run_with_harness image-name bash -lc true || run_status="$?" +test "\${run_status:-0}" = "7" +test -f "$TMPDIR/package-mount-seen" +test ! -e "$pack_dir" + +external_dir="$TMPDIR/external-package" +mkdir -p "$external_dir" +printf fixture >"$external_dir/openclaw-current.tgz" +docker_e2e_package_mount_args "$external_dir/openclaw-current.tgz" +docker_e2e_run_with_harness image-name bash -lc true +test -f "$external_dir/openclaw-current.tgz" +`; + + execFileSync("bash", ["-lc", script], { encoding: "utf8" }); + } finally { + rmSync(workDir, { recursive: true, force: true }); + } + }); + it("includes procps in the shared Docker E2E image for process watchdogs", () => { const dockerfile = readFileSync("scripts/e2e/Dockerfile", "utf8");