mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
test(docker): extract setup harness (#108797)
This commit is contained in:
committed by
GitHub
parent
0317d7e628
commit
f180638e87
@@ -790,7 +790,6 @@ src/daemon/schtasks.startup-fallback.test.ts
|
||||
src/daemon/schtasks.ts
|
||||
src/daemon/systemd.test.ts
|
||||
src/daemon/systemd.ts
|
||||
src/docker-setup.e2e.test.ts
|
||||
src/fleet/backup.runtime.ts
|
||||
src/fleet/containers.runtime.ts
|
||||
src/fleet/service.runtime.ts
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { chmod, copyFile, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
||||
import { createServer } from "node:net";
|
||||
import { join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { expect } from "vitest";
|
||||
import { createSuiteTempRootTracker } from "./test-helpers/temp-dir.js";
|
||||
|
||||
export const repoRoot = resolve(fileURLToPath(new URL(".", import.meta.url)), "..");
|
||||
|
||||
export type DockerSetupSandbox = {
|
||||
rootDir: string;
|
||||
scriptPath: string;
|
||||
logPath: string;
|
||||
binDir: string;
|
||||
};
|
||||
|
||||
const sandboxRootTracker = createSuiteTempRootTracker({ prefix: "openclaw-docker-setup-" });
|
||||
|
||||
export async function setupDockerSetupSandboxRoot(): Promise<void> {
|
||||
await sandboxRootTracker.setup();
|
||||
}
|
||||
|
||||
export async function cleanupDockerSetupSandboxRoot(): Promise<void> {
|
||||
await sandboxRootTracker.cleanup();
|
||||
}
|
||||
|
||||
async function writeDockerStub(binDir: string, logPath: string) {
|
||||
const stub = `#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
log="$DOCKER_STUB_LOG"
|
||||
fail_match="\${DOCKER_STUB_FAIL_MATCH:-}"
|
||||
docker_host=""
|
||||
if [[ "\${1:-}" == "--host" ]]; then
|
||||
docker_host="\${2:-}"
|
||||
shift 2
|
||||
fi
|
||||
if [[ "\${1:-}" == "compose" && "\${2:-}" == "version" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
if [[ "\${1:-}" == "image" && "\${2:-}" == "inspect" ]]; then
|
||||
format=""
|
||||
if [[ "\${3:-}" == "-f" || "\${3:-}" == "--format" ]]; then
|
||||
format="\${4:-}"
|
||||
image="\${5:-}"
|
||||
else
|
||||
image="\${3:-}"
|
||||
fi
|
||||
echo "image inspect $image host=$docker_host" >>"$log"
|
||||
missing_images=",\${DOCKER_STUB_MISSING_IMAGES:-},"
|
||||
if [[ "$missing_images" == *",$image,"* ]]; then
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "$format" ]]; then
|
||||
printf '%s\n' "\${DOCKER_STUB_BROWSER_CONTRACT:-<no value>}"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
if [[ "\${1:-}" == "pull" ]]; then
|
||||
echo "pull $*" >>"$log"
|
||||
exit 0
|
||||
fi
|
||||
if [[ "\${1:-}" == "build" ]]; then
|
||||
if [[ -n "$fail_match" && "$*" == *"$fail_match"* ]]; then
|
||||
echo "build-fail $*" >>"$log"
|
||||
exit 1
|
||||
fi
|
||||
echo "build DOCKER_BUILDKIT=\${DOCKER_BUILDKIT:-} $*" >>"$log"
|
||||
exit 0
|
||||
fi
|
||||
if [[ "\${1:-}" == "compose" ]]; then
|
||||
if [[ -n "$fail_match" && "$*" == *"$fail_match"* ]]; then
|
||||
echo "compose-fail $*" >>"$log"
|
||||
exit 1
|
||||
fi
|
||||
echo "compose $*" >>"$log"
|
||||
if [[ "$*" == *"config get tools.sandbox.tools --json"* ]]; then
|
||||
if [[ -n "\${DOCKER_STUB_SANDBOX_TOOLS_JSON:-}" ]]; then
|
||||
printf '%s\n' "$DOCKER_STUB_SANDBOX_TOOLS_JSON"
|
||||
else
|
||||
printf '{}\n'
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
if [[ "$*" == *"config get agents --json"* ]]; then
|
||||
if [[ -n "\${DOCKER_STUB_AGENTS_JSON:-}" ]]; then
|
||||
printf '%s\n' "$DOCKER_STUB_AGENTS_JSON"
|
||||
else
|
||||
printf '{}\n'
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
args=("$@")
|
||||
for ((i = 0; i + 4 < \${#args[@]}; i++)); do
|
||||
if [[ "\${args[$i]}" == "--entrypoint" &&
|
||||
"\${args[$((i + 1))]}" == "node" &&
|
||||
"\${args[$((i + 2))]}" == "openclaw-gateway" &&
|
||||
"\${args[$((i + 3))]}" == "-e" ]]; then
|
||||
node -e "\${args[$((i + 4))]}" "\${args[@]:$((i + 5))}"
|
||||
exit $?
|
||||
fi
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
echo "unknown $*" >>"$log"
|
||||
exit 0
|
||||
`;
|
||||
|
||||
const timeoutStub = `#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
if [[ "\${1:-}" == --kill-after=* ]]; then
|
||||
shift
|
||||
elif [[ "\${1:-}" == "--kill-after" ]]; then
|
||||
shift 2
|
||||
fi
|
||||
if [[ $# -gt 0 ]]; then
|
||||
shift
|
||||
fi
|
||||
exec "$@"
|
||||
`;
|
||||
|
||||
await mkdir(binDir, { recursive: true });
|
||||
await writeFile(join(binDir, "docker"), stub, { mode: 0o755 });
|
||||
await writeFile(join(binDir, "timeout"), timeoutStub, { mode: 0o755 });
|
||||
await writeFile(logPath, "");
|
||||
}
|
||||
|
||||
export async function expectMissingPath(path: string): Promise<void> {
|
||||
try {
|
||||
await stat(path);
|
||||
} catch (error) {
|
||||
expect((error as NodeJS.ErrnoException).code).toBe("ENOENT");
|
||||
return;
|
||||
}
|
||||
throw new Error(`Expected missing path: ${path}`);
|
||||
}
|
||||
|
||||
export async function createDockerSetupSandbox(): Promise<DockerSetupSandbox> {
|
||||
const rootDir = await sandboxRootTracker.make("suite");
|
||||
const scriptPath = join(rootDir, "scripts", "docker", "setup.sh");
|
||||
const dockerfilePath = join(rootDir, "Dockerfile");
|
||||
const composePath = join(rootDir, "docker-compose.yml");
|
||||
const binDir = join(rootDir, "bin");
|
||||
const logPath = join(rootDir, "docker-stub.log");
|
||||
|
||||
await mkdir(join(rootDir, "scripts", "docker"), { recursive: true });
|
||||
await mkdir(join(rootDir, "scripts", "lib"), { recursive: true });
|
||||
await copyFile(join(repoRoot, "scripts", "docker", "setup.sh"), scriptPath);
|
||||
await copyFile(
|
||||
join(repoRoot, "scripts", "lib", "docker-build.sh"),
|
||||
join(rootDir, "scripts", "lib", "docker-build.sh"),
|
||||
);
|
||||
await copyFile(
|
||||
join(repoRoot, "scripts", "lib", "build-metadata.sh"),
|
||||
join(rootDir, "scripts", "lib", "build-metadata.sh"),
|
||||
);
|
||||
await copyFile(
|
||||
join(repoRoot, "scripts", "lib", "docker-e2e-logs.sh"),
|
||||
join(rootDir, "scripts", "lib", "docker-e2e-logs.sh"),
|
||||
);
|
||||
await copyFile(
|
||||
join(repoRoot, "scripts", "lib", "docker-e2e-container.sh"),
|
||||
join(rootDir, "scripts", "lib", "docker-e2e-container.sh"),
|
||||
);
|
||||
await copyFile(
|
||||
join(repoRoot, "scripts", "lib", "host-timeout.sh"),
|
||||
join(rootDir, "scripts", "lib", "host-timeout.sh"),
|
||||
);
|
||||
await chmod(scriptPath, 0o755);
|
||||
await writeFile(dockerfilePath, "FROM scratch\n");
|
||||
await writeFile(
|
||||
composePath,
|
||||
"services:\n openclaw-gateway:\n image: noop\n openclaw-cli:\n image: noop\n",
|
||||
);
|
||||
await writeDockerStub(binDir, logPath);
|
||||
|
||||
return { rootDir, scriptPath, logPath, binDir };
|
||||
}
|
||||
|
||||
export const prestartContainerEnvFlags = [
|
||||
"-e HOME=/home/node",
|
||||
"-e OPENCLAW_HOME=/home/node",
|
||||
"-e OPENCLAW_STATE_DIR=/home/node/.openclaw",
|
||||
"-e OPENCLAW_CONFIG_PATH=/home/node/.openclaw/openclaw.json",
|
||||
"-e OPENCLAW_CONFIG_DIR=/home/node/.openclaw",
|
||||
"-e OPENCLAW_WORKSPACE_DIR=/home/node/.openclaw/workspace",
|
||||
].join(" ");
|
||||
|
||||
export const noFollowOwnershipRepair = (root: string) =>
|
||||
`/usr/bin/find -P ${root} -xdev -execdir /usr/bin/chown -h node:node {} +`;
|
||||
export const prestartSafePath = "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
||||
|
||||
export function requireSandbox(sandbox: DockerSetupSandbox | null): DockerSetupSandbox {
|
||||
if (!sandbox) {
|
||||
throw new Error("sandbox missing");
|
||||
}
|
||||
return sandbox;
|
||||
}
|
||||
|
||||
export async function resetDockerLog(sandbox: DockerSetupSandbox) {
|
||||
await writeFile(sandbox.logPath, "");
|
||||
}
|
||||
|
||||
export async function readDockerLog(sandbox: DockerSetupSandbox) {
|
||||
return readFile(sandbox.logPath, "utf8");
|
||||
}
|
||||
|
||||
export async function readDockerLogLines(sandbox: DockerSetupSandbox) {
|
||||
const lines: string[] = [];
|
||||
for (const line of (await readDockerLog(sandbox)).split("\n")) {
|
||||
if (line) {
|
||||
lines.push(line);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
export function collectMatchingLines(
|
||||
lines: string[],
|
||||
predicate: (line: string) => boolean,
|
||||
): string[] {
|
||||
const matches: string[] = [];
|
||||
for (const line of lines) {
|
||||
if (predicate(line)) {
|
||||
matches.push(line);
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
export function isGatewayStartLine(line: string) {
|
||||
return line.includes("compose") && line.includes(" up -d") && line.includes("openclaw-gateway");
|
||||
}
|
||||
|
||||
export function findGatewayStartLineIndex(lines: string[]) {
|
||||
return lines.findIndex((line) => isGatewayStartLine(line));
|
||||
}
|
||||
|
||||
export function expectOfflineComposePolicy(
|
||||
lines: string[],
|
||||
options: { gatewayStarts?: boolean } = {},
|
||||
) {
|
||||
const composeLines = collectMatchingLines(lines, (line) => line.startsWith("compose "));
|
||||
expect(composeLines.length).toBeGreaterThan(0);
|
||||
for (const line of composeLines) {
|
||||
if (line.includes(" run ")) {
|
||||
expect(line).toContain(" run --pull never ");
|
||||
}
|
||||
}
|
||||
const gatewayStarts = collectMatchingLines(composeLines, (line) => isGatewayStartLine(line));
|
||||
if (options.gatewayStarts === false) {
|
||||
expect(gatewayStarts).toHaveLength(0);
|
||||
return;
|
||||
}
|
||||
expect(gatewayStarts.length).toBeGreaterThan(0);
|
||||
for (const line of gatewayStarts) {
|
||||
expect(line).toContain(" up -d --pull never --no-build");
|
||||
}
|
||||
}
|
||||
|
||||
export async function withUnixSocket<T>(socketPath: string, run: () => Promise<T>): Promise<T> {
|
||||
const server = createServer();
|
||||
await new Promise<void>((resolveValue, reject) => {
|
||||
const onError = (error: Error) => {
|
||||
server.off("listening", onListening);
|
||||
reject(error);
|
||||
};
|
||||
const onListening = () => {
|
||||
server.off("error", onError);
|
||||
resolveValue();
|
||||
};
|
||||
server.once("error", onError);
|
||||
server.once("listening", onListening);
|
||||
server.listen(socketPath);
|
||||
});
|
||||
|
||||
try {
|
||||
return await run();
|
||||
} finally {
|
||||
await new Promise<void>((resolveLocal) => {
|
||||
server.close(() => resolveLocal());
|
||||
});
|
||||
await rm(socketPath, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveBashForCompatCheck(): string | null {
|
||||
for (const candidate of ["/bin/bash", "bash"]) {
|
||||
const probe = spawnSync(candidate, ["-c", "exit 0"], { encoding: "utf8" });
|
||||
if (!probe.error && probe.status === 0) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
+26
-284
@@ -1,187 +1,29 @@
|
||||
// E2E tests for Docker setup script behavior and generated commands.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { chmod, copyFile, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
||||
import { createServer } from "node:net";
|
||||
import { join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { createSuiteTempRootTracker } from "./test-helpers/temp-dir.js";
|
||||
|
||||
const repoRoot = resolve(fileURLToPath(new URL(".", import.meta.url)), "..");
|
||||
|
||||
type DockerSetupSandbox = {
|
||||
rootDir: string;
|
||||
scriptPath: string;
|
||||
logPath: string;
|
||||
binDir: string;
|
||||
};
|
||||
|
||||
async function writeDockerStub(binDir: string, logPath: string) {
|
||||
const stub = `#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
log="$DOCKER_STUB_LOG"
|
||||
fail_match="\${DOCKER_STUB_FAIL_MATCH:-}"
|
||||
docker_host=""
|
||||
if [[ "\${1:-}" == "--host" ]]; then
|
||||
docker_host="\${2:-}"
|
||||
shift 2
|
||||
fi
|
||||
if [[ "\${1:-}" == "compose" && "\${2:-}" == "version" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
if [[ "\${1:-}" == "image" && "\${2:-}" == "inspect" ]]; then
|
||||
format=""
|
||||
if [[ "\${3:-}" == "-f" || "\${3:-}" == "--format" ]]; then
|
||||
format="\${4:-}"
|
||||
image="\${5:-}"
|
||||
else
|
||||
image="\${3:-}"
|
||||
fi
|
||||
echo "image inspect $image host=$docker_host" >>"$log"
|
||||
missing_images=",\${DOCKER_STUB_MISSING_IMAGES:-},"
|
||||
if [[ "$missing_images" == *",$image,"* ]]; then
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "$format" ]]; then
|
||||
printf '%s\n' "\${DOCKER_STUB_BROWSER_CONTRACT:-<no value>}"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
if [[ "\${1:-}" == "pull" ]]; then
|
||||
echo "pull $*" >>"$log"
|
||||
exit 0
|
||||
fi
|
||||
if [[ "\${1:-}" == "build" ]]; then
|
||||
if [[ -n "$fail_match" && "$*" == *"$fail_match"* ]]; then
|
||||
echo "build-fail $*" >>"$log"
|
||||
exit 1
|
||||
fi
|
||||
echo "build DOCKER_BUILDKIT=\${DOCKER_BUILDKIT:-} $*" >>"$log"
|
||||
exit 0
|
||||
fi
|
||||
if [[ "\${1:-}" == "compose" ]]; then
|
||||
if [[ -n "$fail_match" && "$*" == *"$fail_match"* ]]; then
|
||||
echo "compose-fail $*" >>"$log"
|
||||
exit 1
|
||||
fi
|
||||
echo "compose $*" >>"$log"
|
||||
if [[ "$*" == *"config get tools.sandbox.tools --json"* ]]; then
|
||||
if [[ -n "\${DOCKER_STUB_SANDBOX_TOOLS_JSON:-}" ]]; then
|
||||
printf '%s\n' "$DOCKER_STUB_SANDBOX_TOOLS_JSON"
|
||||
else
|
||||
printf '{}\n'
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
if [[ "$*" == *"config get agents --json"* ]]; then
|
||||
if [[ -n "\${DOCKER_STUB_AGENTS_JSON:-}" ]]; then
|
||||
printf '%s\n' "$DOCKER_STUB_AGENTS_JSON"
|
||||
else
|
||||
printf '{}\n'
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
args=("$@")
|
||||
for ((i = 0; i + 4 < \${#args[@]}; i++)); do
|
||||
if [[ "\${args[$i]}" == "--entrypoint" &&
|
||||
"\${args[$((i + 1))]}" == "node" &&
|
||||
"\${args[$((i + 2))]}" == "openclaw-gateway" &&
|
||||
"\${args[$((i + 3))]}" == "-e" ]]; then
|
||||
node -e "\${args[$((i + 4))]}" "\${args[@]:$((i + 5))}"
|
||||
exit $?
|
||||
fi
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
echo "unknown $*" >>"$log"
|
||||
exit 0
|
||||
`;
|
||||
|
||||
const timeoutStub = `#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
if [[ "\${1:-}" == --kill-after=* ]]; then
|
||||
shift
|
||||
elif [[ "\${1:-}" == "--kill-after" ]]; then
|
||||
shift 2
|
||||
fi
|
||||
if [[ $# -gt 0 ]]; then
|
||||
shift
|
||||
fi
|
||||
exec "$@"
|
||||
`;
|
||||
|
||||
await mkdir(binDir, { recursive: true });
|
||||
await writeFile(join(binDir, "docker"), stub, { mode: 0o755 });
|
||||
await writeFile(join(binDir, "timeout"), timeoutStub, { mode: 0o755 });
|
||||
await writeFile(logPath, "");
|
||||
}
|
||||
|
||||
async function expectMissingPath(path: string): Promise<void> {
|
||||
try {
|
||||
await stat(path);
|
||||
} catch (error) {
|
||||
expect((error as NodeJS.ErrnoException).code).toBe("ENOENT");
|
||||
return;
|
||||
}
|
||||
throw new Error(`Expected missing path: ${path}`);
|
||||
}
|
||||
|
||||
async function createDockerSetupSandbox(): Promise<DockerSetupSandbox> {
|
||||
const rootDir = await sandboxRootTracker.make("suite");
|
||||
const scriptPath = join(rootDir, "scripts", "docker", "setup.sh");
|
||||
const dockerfilePath = join(rootDir, "Dockerfile");
|
||||
const composePath = join(rootDir, "docker-compose.yml");
|
||||
const binDir = join(rootDir, "bin");
|
||||
const logPath = join(rootDir, "docker-stub.log");
|
||||
|
||||
await mkdir(join(rootDir, "scripts", "docker"), { recursive: true });
|
||||
await mkdir(join(rootDir, "scripts", "lib"), { recursive: true });
|
||||
await copyFile(join(repoRoot, "scripts", "docker", "setup.sh"), scriptPath);
|
||||
await copyFile(
|
||||
join(repoRoot, "scripts", "lib", "docker-build.sh"),
|
||||
join(rootDir, "scripts", "lib", "docker-build.sh"),
|
||||
);
|
||||
await copyFile(
|
||||
join(repoRoot, "scripts", "lib", "build-metadata.sh"),
|
||||
join(rootDir, "scripts", "lib", "build-metadata.sh"),
|
||||
);
|
||||
await copyFile(
|
||||
join(repoRoot, "scripts", "lib", "docker-e2e-logs.sh"),
|
||||
join(rootDir, "scripts", "lib", "docker-e2e-logs.sh"),
|
||||
);
|
||||
await copyFile(
|
||||
join(repoRoot, "scripts", "lib", "docker-e2e-container.sh"),
|
||||
join(rootDir, "scripts", "lib", "docker-e2e-container.sh"),
|
||||
);
|
||||
await copyFile(
|
||||
join(repoRoot, "scripts", "lib", "host-timeout.sh"),
|
||||
join(rootDir, "scripts", "lib", "host-timeout.sh"),
|
||||
);
|
||||
await chmod(scriptPath, 0o755);
|
||||
await writeFile(dockerfilePath, "FROM scratch\n");
|
||||
await writeFile(
|
||||
composePath,
|
||||
"services:\n openclaw-gateway:\n image: noop\n openclaw-cli:\n image: noop\n",
|
||||
);
|
||||
await writeDockerStub(binDir, logPath);
|
||||
|
||||
return { rootDir, scriptPath, logPath, binDir };
|
||||
}
|
||||
|
||||
const sandboxRootTracker = createSuiteTempRootTracker({ prefix: "openclaw-docker-setup-" });
|
||||
|
||||
const prestartContainerEnvFlags = [
|
||||
"-e HOME=/home/node",
|
||||
"-e OPENCLAW_HOME=/home/node",
|
||||
"-e OPENCLAW_STATE_DIR=/home/node/.openclaw",
|
||||
"-e OPENCLAW_CONFIG_PATH=/home/node/.openclaw/openclaw.json",
|
||||
"-e OPENCLAW_CONFIG_DIR=/home/node/.openclaw",
|
||||
"-e OPENCLAW_WORKSPACE_DIR=/home/node/.openclaw/workspace",
|
||||
].join(" ");
|
||||
|
||||
const noFollowOwnershipRepair = (root: string) =>
|
||||
`/usr/bin/find -P ${root} -xdev -execdir /usr/bin/chown -h node:node {} +`;
|
||||
const prestartSafePath = "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
||||
import {
|
||||
cleanupDockerSetupSandboxRoot,
|
||||
collectMatchingLines,
|
||||
createDockerSetupSandbox,
|
||||
expectMissingPath,
|
||||
expectOfflineComposePolicy,
|
||||
findGatewayStartLineIndex,
|
||||
isGatewayStartLine,
|
||||
noFollowOwnershipRepair,
|
||||
prestartContainerEnvFlags,
|
||||
prestartSafePath,
|
||||
readDockerLog,
|
||||
readDockerLogLines,
|
||||
repoRoot,
|
||||
requireSandbox,
|
||||
resetDockerLog,
|
||||
resolveBashForCompatCheck,
|
||||
setupDockerSetupSandboxRoot,
|
||||
withUnixSocket,
|
||||
type DockerSetupSandbox,
|
||||
} from "./docker-setup.e2e.test-support.js";
|
||||
|
||||
function createEnv(
|
||||
sandbox: DockerSetupSandbox,
|
||||
@@ -210,13 +52,6 @@ function createEnv(
|
||||
return env;
|
||||
}
|
||||
|
||||
function requireSandbox(sandbox: DockerSetupSandbox | null): DockerSetupSandbox {
|
||||
if (!sandbox) {
|
||||
throw new Error("sandbox missing");
|
||||
}
|
||||
return sandbox;
|
||||
}
|
||||
|
||||
function runDockerSetup(
|
||||
sandbox: DockerSetupSandbox,
|
||||
overrides: Record<string, string | undefined> = {},
|
||||
@@ -231,61 +66,6 @@ function runDockerSetup(
|
||||
});
|
||||
}
|
||||
|
||||
async function resetDockerLog(sandbox: DockerSetupSandbox) {
|
||||
await writeFile(sandbox.logPath, "");
|
||||
}
|
||||
|
||||
async function readDockerLog(sandbox: DockerSetupSandbox) {
|
||||
return readFile(sandbox.logPath, "utf8");
|
||||
}
|
||||
|
||||
async function readDockerLogLines(sandbox: DockerSetupSandbox) {
|
||||
const lines: string[] = [];
|
||||
for (const line of (await readDockerLog(sandbox)).split("\n")) {
|
||||
if (line) {
|
||||
lines.push(line);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function collectMatchingLines(lines: string[], predicate: (line: string) => boolean): string[] {
|
||||
const matches: string[] = [];
|
||||
for (const line of lines) {
|
||||
if (predicate(line)) {
|
||||
matches.push(line);
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
function isGatewayStartLine(line: string) {
|
||||
return line.includes("compose") && line.includes(" up -d") && line.includes("openclaw-gateway");
|
||||
}
|
||||
|
||||
function findGatewayStartLineIndex(lines: string[]) {
|
||||
return lines.findIndex((line) => isGatewayStartLine(line));
|
||||
}
|
||||
|
||||
function expectOfflineComposePolicy(lines: string[], options: { gatewayStarts?: boolean } = {}) {
|
||||
const composeLines = collectMatchingLines(lines, (line) => line.startsWith("compose "));
|
||||
expect(composeLines.length).toBeGreaterThan(0);
|
||||
for (const line of composeLines) {
|
||||
if (line.includes(" run ")) {
|
||||
expect(line).toContain(" run --pull never ");
|
||||
}
|
||||
}
|
||||
const gatewayStarts = collectMatchingLines(composeLines, (line) => isGatewayStartLine(line));
|
||||
if (options.gatewayStarts === false) {
|
||||
expect(gatewayStarts).toHaveLength(0);
|
||||
return;
|
||||
}
|
||||
expect(gatewayStarts.length).toBeGreaterThan(0);
|
||||
for (const line of gatewayStarts) {
|
||||
expect(line).toContain(" up -d --pull never --no-build");
|
||||
}
|
||||
}
|
||||
|
||||
async function runDockerSetupWithUnsetGatewayToken(
|
||||
sandbox: DockerSetupSandbox,
|
||||
suffix: string,
|
||||
@@ -306,58 +86,21 @@ async function runDockerSetupWithUnsetGatewayToken(
|
||||
return { result, envFile };
|
||||
}
|
||||
|
||||
async function withUnixSocket<T>(socketPath: string, run: () => Promise<T>): Promise<T> {
|
||||
const server = createServer();
|
||||
await new Promise<void>((resolveValue, reject) => {
|
||||
const onError = (error: Error) => {
|
||||
server.off("listening", onListening);
|
||||
reject(error);
|
||||
};
|
||||
const onListening = () => {
|
||||
server.off("error", onError);
|
||||
resolveValue();
|
||||
};
|
||||
server.once("error", onError);
|
||||
server.once("listening", onListening);
|
||||
server.listen(socketPath);
|
||||
});
|
||||
|
||||
try {
|
||||
return await run();
|
||||
} finally {
|
||||
await new Promise<void>((resolveLocal) => {
|
||||
server.close(() => resolveLocal());
|
||||
});
|
||||
await rm(socketPath, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function resolveBashForCompatCheck(): string | null {
|
||||
for (const candidate of ["/bin/bash", "bash"]) {
|
||||
const probe = spawnSync(candidate, ["-c", "exit 0"], { encoding: "utf8" });
|
||||
if (!probe.error && probe.status === 0) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
describe("scripts/docker/setup.sh", () => {
|
||||
let sandbox: DockerSetupSandbox | null = null;
|
||||
|
||||
beforeAll(async () => {
|
||||
await sandboxRootTracker.setup();
|
||||
await setupDockerSetupSandboxRoot();
|
||||
sandbox = await createDockerSetupSandbox();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (!sandbox) {
|
||||
await sandboxRootTracker.cleanup();
|
||||
await cleanupDockerSetupSandboxRoot();
|
||||
return;
|
||||
}
|
||||
await rm(sandbox.rootDir, { recursive: true, force: true });
|
||||
await sandboxRootTracker.cleanup();
|
||||
await cleanupDockerSetupSandboxRoot();
|
||||
sandbox = null;
|
||||
});
|
||||
|
||||
@@ -1217,4 +960,3 @@ describe("scripts/docker/setup.sh", () => {
|
||||
expect(argLine).toBe("ARG OPENCLAW_IMAGE_APT_PACKAGES");
|
||||
});
|
||||
});
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
||||
Reference in New Issue
Block a user