diff --git a/scripts/lib/docker-e2e-container.sh b/scripts/lib/docker-e2e-container.sh index 022ae7b4a7ac..da924a325757 100644 --- a/scripts/lib/docker-e2e-container.sh +++ b/scripts/lib/docker-e2e-container.sh @@ -241,6 +241,7 @@ docker_e2e_resolve_pids_limit() { docker_e2e_docker_run_resource_args() { DOCKER_E2E_RUN_RESOURCE_ARGS=() + docker_e2e_append_update_check_suppression "$@" if docker_e2e_resource_limits_disabled; then return 0 fi @@ -260,6 +261,45 @@ docker_e2e_docker_run_resource_args() { pids_limit="$(docker_e2e_resolve_pids_limit "$pids_limit")" || return $? DOCKER_E2E_RUN_RESOURCE_ARGS+=(--pids-limit "$pids_limit") fi + +} + +docker_e2e_run_env_present() { + local name="$1" + shift + local arg + local expect_value=0 + for arg in "$@"; do + if [ "$expect_value" = 1 ]; then + expect_value=0 + case "$arg" in + "$name" | "$name"=*) + return 0 + ;; + esac + continue + fi + case "$arg" in + -e | --env) + expect_value=1 + ;; + -e"$name" | -e"$name"=* | --env="$name" | --env="$name"=*) + return 0 + ;; + esac + done + return 1 +} + +# CI containers are not installations. The runner's own CI variable does not +# cross into `docker run`, so without this every E2E container reports a daily +# update check and drowns real operators in the telemetry aggregates. Callers +# that exercise update behavior pass their own value and keep it. +docker_e2e_append_update_check_suppression() { + if docker_e2e_run_env_present OPENCLAW_NO_AUTO_UPDATE "$@"; then + return 0 + fi + DOCKER_E2E_RUN_RESOURCE_ARGS+=(-e OPENCLAW_NO_AUTO_UPDATE=1) } docker_e2e_container_running() { diff --git a/test/scripts/docker-build-helper.test.ts b/test/scripts/docker-build-helper.test.ts index 074e31cc06ec..0baf88e89198 100644 --- a/test/scripts/docker-build-helper.test.ts +++ b/test/scripts/docker-build-helper.test.ts @@ -2131,7 +2131,7 @@ set -e stderr="$(<"$TMPDIR/stderr")" [[ "$status" = "13" ]] [[ "$stderr" = *"timeout command not found; using Node watchdog for Docker command timeout 7s"* ]] -[[ "$(<"$TMPDIR/docker-seen")" = "run --memory 8g --cpus 16 --pids-limit 2048 -i demo|payload" ]] +[[ "$(<"$TMPDIR/docker-seen")" = "run -e OPENCLAW_NO_AUTO_UPDATE=1 --memory 8g --cpus 16 --pids-limit 2048 -i demo|payload" ]] `; execFileSync("bash", ["-lc", script], { encoding: "utf8" }); @@ -2169,11 +2169,11 @@ OPENCLAW_DOCKER_E2E_AVAILABLE_CPUS=8 OPENCLAW_DOCKER_E2E_MEMORY=12g OPENCLAW_DOC docker_e2e_docker_cmd run --memory 2g --cpus 3 --pids-limit 99 demo OPENCLAW_DOCKER_E2E_DISABLE_RESOURCE_LIMITS=1 docker_e2e_docker_cmd run demo -[[ "$(sed -n '1p' "$TMPDIR/docker-seen")" = "run --memory 8g --cpus 16 --pids-limit 2048 demo" ]] -[[ "$(sed -n '2p' "$TMPDIR/docker-seen")" = "run --memory 12g --cpus 4 --pids-limit 512 demo" ]] -[[ "$(sed -n '3p' "$TMPDIR/docker-seen")" = "run --memory 12g --cpus 8 --pids-limit 512 demo" ]] -[[ "$(sed -n '4p' "$TMPDIR/docker-seen")" = "run --memory 2g --cpus 3 --pids-limit 99 demo" ]] -[[ "$(sed -n '5p' "$TMPDIR/docker-seen")" = "run demo" ]] +[[ "$(sed -n '1p' "$TMPDIR/docker-seen")" = "run -e OPENCLAW_NO_AUTO_UPDATE=1 --memory 8g --cpus 16 --pids-limit 2048 demo" ]] +[[ "$(sed -n '2p' "$TMPDIR/docker-seen")" = "run -e OPENCLAW_NO_AUTO_UPDATE=1 --memory 12g --cpus 4 --pids-limit 512 demo" ]] +[[ "$(sed -n '3p' "$TMPDIR/docker-seen")" = "run -e OPENCLAW_NO_AUTO_UPDATE=1 --memory 12g --cpus 8 --pids-limit 512 demo" ]] +[[ "$(sed -n '4p' "$TMPDIR/docker-seen")" = "run -e OPENCLAW_NO_AUTO_UPDATE=1 --memory 2g --cpus 3 --pids-limit 99 demo" ]] +[[ "$(sed -n '5p' "$TMPDIR/docker-seen")" = "run -e OPENCLAW_NO_AUTO_UPDATE=1 demo" ]] `; execFileSync("bash", ["-lc", script], { encoding: "utf8" }); @@ -2328,8 +2328,8 @@ source "$ROOT_DIR/scripts/lib/docker-e2e-container.sh" docker_e2e_docker_run_cmd run demo -[[ "$(<"$TMPDIR/timeout-seen")" = "gtimeout:--kill-after=30s 13s|docker run --memory 8g --cpus 8 --pids-limit 2048 demo" ]] -[[ "$(<"$TMPDIR/docker-seen")" = "run --memory 8g --cpus 8 --pids-limit 2048 demo" ]] +[[ "$(<"$TMPDIR/timeout-seen")" = "gtimeout:--kill-after=30s 13s|docker run -e OPENCLAW_NO_AUTO_UPDATE=1 --memory 8g --cpus 8 --pids-limit 2048 demo" ]] +[[ "$(<"$TMPDIR/docker-seen")" = "run -e OPENCLAW_NO_AUTO_UPDATE=1 --memory 8g --cpus 8 --pids-limit 2048 demo" ]] `; execFileSync("bash", ["-lc", script], { encoding: "utf8" }); @@ -2401,8 +2401,8 @@ source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh" docker_e2e_docker_run_cmd run demo -[[ "$(<"$TMPDIR/timeout-seen")" = "gtimeout:--kill-after=30s 15s|docker run --memory 8g --cpus 8 --pids-limit 2048 demo" ]] -[[ "$(<"$TMPDIR/docker-seen")" = "run --memory 8g --cpus 8 --pids-limit 2048 demo" ]] +[[ "$(<"$TMPDIR/timeout-seen")" = "gtimeout:--kill-after=30s 15s|docker run -e OPENCLAW_NO_AUTO_UPDATE=1 --memory 8g --cpus 8 --pids-limit 2048 demo" ]] +[[ "$(<"$TMPDIR/docker-seen")" = "run -e OPENCLAW_NO_AUTO_UPDATE=1 --memory 8g --cpus 8 --pids-limit 2048 demo" ]] `; execFileSync("bash", ["-lc", script], { encoding: "utf8" }); diff --git a/test/scripts/docker-e2e-update-suppression.test.ts b/test/scripts/docker-e2e-update-suppression.test.ts new file mode 100644 index 000000000000..04b3e8a8ed5c --- /dev/null +++ b/test/scripts/docker-e2e-update-suppression.test.ts @@ -0,0 +1,56 @@ +// Covers the Docker E2E guard that keeps continuous-integration containers out +// of the public telemetry aggregates. +import { execFileSync } from "node:child_process"; +import { describe, expect, it } from "vitest"; + +const CONTAINER_HELPER_PATH = "scripts/lib/docker-e2e-container.sh"; + +/** Returns the docker run arguments the helper injects for the given caller args. */ +function injectedRunArgs(callerArgs: string[], env: Record = {}): string[] { + const script = ` +set -euo pipefail +source ${CONTAINER_HELPER_PATH} +docker_e2e_docker_run_resource_args ${callerArgs.map((arg) => `'${arg}'`).join(" ")} +printf '%s\\n' "\${DOCKER_E2E_RUN_RESOURCE_ARGS[@]}" +`; + return execFileSync("bash", ["-lc", script], { + encoding: "utf8", + env: { ...process.env, ...env }, + }) + .split("\n") + .filter(Boolean); +} + +describe("docker e2e update-check suppression", () => { + it("suppresses automatic update checks inside every container", () => { + const args = injectedRunArgs(["-d", "--name", "openclaw-e2e", "openclaw:test"]); + + expect(args).toContain("-e"); + expect(args).toContain("OPENCLAW_NO_AUTO_UPDATE=1"); + }); + + it("still suppresses when resource limits are disabled", () => { + const args = injectedRunArgs(["-d", "openclaw:test"], { + OPENCLAW_DOCKER_E2E_DISABLE_RESOURCE_LIMITS: "1", + }); + + expect(args).toEqual(["-e", "OPENCLAW_NO_AUTO_UPDATE=1"]); + }); + + it("keeps a caller-provided value so update lanes stay in control", () => { + for (const callerArgs of [ + ["-e", "OPENCLAW_NO_AUTO_UPDATE=0", "openclaw:test"], + ["-eOPENCLAW_NO_AUTO_UPDATE=0", "openclaw:test"], + ["--env", "OPENCLAW_NO_AUTO_UPDATE", "openclaw:test"], + ["--env=OPENCLAW_NO_AUTO_UPDATE=0", "openclaw:test"], + ]) { + expect(injectedRunArgs(callerArgs)).not.toContain("OPENCLAW_NO_AUTO_UPDATE=1"); + } + }); + + it("does not mistake an image or unrelated flag value for the suppression variable", () => { + const args = injectedRunArgs(["-e", "SOMETHING_ELSE=1", "OPENCLAW_NO_AUTO_UPDATE", "-d"]); + + expect(args).toContain("OPENCLAW_NO_AUTO_UPDATE=1"); + }); +}); diff --git a/test/vitest/vitest.tooling-docker.config.ts b/test/vitest/vitest.tooling-docker.config.ts index 1d2ab221bdd6..c717147c548c 100644 --- a/test/vitest/vitest.tooling-docker.config.ts +++ b/test/vitest/vitest.tooling-docker.config.ts @@ -1,7 +1,10 @@ // Vitest tooling Docker config isolates the slow Docker helper contract tests. import { createScopedVitestConfig } from "./vitest.scoped-config.ts"; -export const toolingDockerTestFiles = ["test/scripts/docker-build-helper.test.ts"]; +export const toolingDockerTestFiles = [ + "test/scripts/docker-build-helper.test.ts", + "test/scripts/docker-e2e-update-suppression.test.ts", +]; export function createToolingDockerVitestConfig(env?: Record) { return createScopedVitestConfig(toolingDockerTestFiles, {