From dc0d4c263e09fc9ff933406b7d4b4444dc3df23c Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 26 May 2026 17:03:57 +0200 Subject: [PATCH] fix(e2e): kill timed live docker runs --- scripts/lib/live-docker-auth.sh | 11 +++- test/scripts/live-docker-auth.test.ts | 90 +++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 test/scripts/live-docker-auth.test.ts diff --git a/scripts/lib/live-docker-auth.sh b/scripts/lib/live-docker-auth.sh index 41d8c1dcf77..62e7f4f3826 100644 --- a/scripts/lib/live-docker-auth.sh +++ b/scripts/lib/live-docker-auth.sh @@ -206,6 +206,11 @@ openclaw_live_append_array() { eval "${target_array}+=(\"\${${source_array}[@]}\")" } +openclaw_live_timeout_supports_kill_after() { + command -v timeout >/dev/null 2>&1 || return 1 + timeout --kill-after=1s 1s true >/dev/null 2>&1 +} + openclaw_live_init_docker_run_args() { local target_array="${1:?target array required}" local timeout_value="${2:-${OPENCLAW_LIVE_DOCKER_RUN_TIMEOUT:-2700s}}" @@ -213,7 +218,11 @@ openclaw_live_init_docker_run_args() { if command -v timeout >/dev/null 2>&1; then quoted_timeout="$(printf '%q' "$timeout_value")" - eval "${target_array}=(timeout ${quoted_timeout} docker run)" + if openclaw_live_timeout_supports_kill_after; then + eval "${target_array}=(timeout --kill-after=30s ${quoted_timeout} docker run)" + else + eval "${target_array}=(timeout ${quoted_timeout} docker run)" + fi return fi eval "${target_array}=(docker run)" diff --git a/test/scripts/live-docker-auth.test.ts b/test/scripts/live-docker-auth.test.ts new file mode 100644 index 00000000000..3667c0c8f83 --- /dev/null +++ b/test/scripts/live-docker-auth.test.ts @@ -0,0 +1,90 @@ +import { execFileSync } from "node:child_process"; +import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; + +const tempDirs: string[] = []; + +function makeTempBin(prefix: string) { + const dir = mkdtempSync(path.join(tmpdir(), prefix)); + tempDirs.push(dir); + return dir; +} + +function writeExecutable(filePath: string, contents: string) { + writeFileSync(filePath, contents, "utf8"); + chmodSync(filePath, 0o755); +} + +function resolveDockerRunArgs(pathPrefix: string) { + const script = [ + "source scripts/lib/live-docker-auth.sh", + "ARGS=()", + "openclaw_live_init_docker_run_args ARGS 42s", + "printf '%s\\n' \"${ARGS[@]}\"", + ].join("\n"); + + return execFileSync("/bin/bash", ["-c", script], { + cwd: process.cwd(), + encoding: "utf8", + env: { + ...process.env, + PATH: pathPrefix, + }, + }).trimEnd().split("\n"); +} + +afterEach(() => { + while (tempDirs.length > 0) { + rmSync(tempDirs.pop()!, { force: true, recursive: true }); + } +}); + +describe("scripts/lib/live-docker-auth.sh", () => { + it("adds a kill-after grace period when timeout supports it", () => { + const binDir = makeTempBin("openclaw-live-docker-auth-gnu-"); + writeExecutable( + path.join(binDir, "timeout"), + [ + "#!/bin/sh", + 'if [ "$1" = "--kill-after=1s" ] && [ "$2" = "1s" ] && [ "$3" = "true" ]; then', + " exit 0", + "fi", + "exit 64", + "", + ].join("\n"), + ); + + expect(resolveDockerRunArgs(binDir)).toEqual([ + "timeout", + "--kill-after=30s", + "42s", + "docker", + "run", + ]); + }); + + it("falls back to plain timeout when kill-after is unavailable", () => { + const binDir = makeTempBin("openclaw-live-docker-auth-plain-"); + writeExecutable( + path.join(binDir, "timeout"), + [ + "#!/bin/sh", + 'if [ "$1" = "--kill-after=1s" ]; then', + " exit 1", + "fi", + "exit 0", + "", + ].join("\n"), + ); + + expect(resolveDockerRunArgs(binDir)).toEqual(["timeout", "42s", "docker", "run"]); + }); + + it("uses docker directly when timeout is unavailable", () => { + const binDir = makeTempBin("openclaw-live-docker-auth-no-timeout-"); + + expect(resolveDockerRunArgs(binDir)).toEqual(["docker", "run"]); + }); +});