mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-28 10:22:32 +00:00
test: share planner and sandbox test helpers
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
createSandboxBrowserConfig,
|
||||
createSandboxPruneConfig,
|
||||
createSandboxSshConfig,
|
||||
} from "../../../test/helpers/sandbox-fixtures.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { SandboxConfig } from "./types.js";
|
||||
|
||||
@@ -92,28 +97,21 @@ function createBackendSandboxConfig(params?: { binds?: string[]; target?: string
|
||||
...(params?.binds ? { binds: params.binds } : {}),
|
||||
},
|
||||
ssh: {
|
||||
...(params?.target ? { target: params.target } : {}),
|
||||
command: "ssh",
|
||||
workspaceRoot: "/remote/openclaw",
|
||||
strictHostKeyChecking: true,
|
||||
updateHostKeys: true,
|
||||
...createSandboxSshConfig(
|
||||
"/remote/openclaw",
|
||||
params?.target ? { target: params.target } : {},
|
||||
),
|
||||
},
|
||||
browser: {
|
||||
enabled: false,
|
||||
browser: createSandboxBrowserConfig({
|
||||
image: "img",
|
||||
containerPrefix: "prefix-",
|
||||
network: "bridge",
|
||||
cdpPort: 1,
|
||||
vncPort: 2,
|
||||
noVncPort: 3,
|
||||
headless: true,
|
||||
enableNoVnc: false,
|
||||
allowHostControl: false,
|
||||
autoStart: false,
|
||||
autoStartTimeoutMs: 1,
|
||||
},
|
||||
}),
|
||||
tools: { allow: [], deny: [] },
|
||||
prune: { idleHours: 24, maxAgeDays: 7 },
|
||||
prune: createSandboxPruneConfig(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
copyBundledPluginMetadata,
|
||||
rewritePackageExtensions,
|
||||
} from "../../scripts/copy-bundled-plugin-metadata.mjs";
|
||||
import { cleanupTempDirs, makeTempRepoRoot, writeJsonFile } from "../../test/helpers/temp-repo.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
const excludeOptionalEnv = { OPENCLAW_INCLUDE_OPTIONAL_BUNDLED: "0" } as const;
|
||||
@@ -15,20 +15,15 @@ const copyBundledPluginMetadataWithEnv = copyBundledPluginMetadata as (params?:
|
||||
}) => void;
|
||||
|
||||
function makeRepoRoot(prefix: string): string {
|
||||
const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
tempDirs.push(repoRoot);
|
||||
return repoRoot;
|
||||
return makeTempRepoRoot(tempDirs, prefix);
|
||||
}
|
||||
|
||||
function writeJson(filePath: string, value: unknown): void {
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
||||
writeJsonFile(filePath, value);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const dir of tempDirs.splice(0, tempDirs.length)) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
cleanupTempDirs(tempDirs);
|
||||
});
|
||||
|
||||
describe("rewritePackageExtensions", () => {
|
||||
|
||||
@@ -30,23 +30,31 @@ async function createConversationRuntimeMock(
|
||||
};
|
||||
}
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/security-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/security-runtime")>();
|
||||
async function createAllowFromRuntimeMock<TModule>(
|
||||
importOriginal: () => Promise<TModule>,
|
||||
): Promise<TModule & { readStoreAllowFromForDmPolicy: typeof readStoreAllowFromForDmPolicy }> {
|
||||
const actual = await importOriginal();
|
||||
return {
|
||||
...actual,
|
||||
readStoreAllowFromForDmPolicy: async (params: {
|
||||
provider: string;
|
||||
accountId: string;
|
||||
dmPolicy?: string | null;
|
||||
shouldRead?: boolean | null;
|
||||
}) => {
|
||||
if (params.shouldRead === false || params.dmPolicy === "allowlist") {
|
||||
return [];
|
||||
}
|
||||
return await readAllowFromStoreMock(params.provider, params.accountId);
|
||||
},
|
||||
readStoreAllowFromForDmPolicy,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function readStoreAllowFromForDmPolicy(params: {
|
||||
provider: string;
|
||||
accountId: string;
|
||||
dmPolicy?: string | null;
|
||||
shouldRead?: boolean | null;
|
||||
}) {
|
||||
if (params.shouldRead === false || params.dmPolicy === "allowlist") {
|
||||
return [];
|
||||
}
|
||||
return await readAllowFromStoreMock(params.provider, params.accountId);
|
||||
}
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/security-runtime", (importOriginal) =>
|
||||
createAllowFromRuntimeMock(importOriginal),
|
||||
);
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/conversation-runtime", createConversationRuntimeMock);
|
||||
vi.mock("openclaw/plugin-sdk/conversation-runtime.js", createConversationRuntimeMock);
|
||||
@@ -57,23 +65,9 @@ vi.mock("../../../src/pairing/pairing-store.js", async (importOriginal) => {
|
||||
upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args),
|
||||
};
|
||||
});
|
||||
vi.mock("../../../src/security/dm-policy-shared.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../../src/security/dm-policy-shared.js")>();
|
||||
return {
|
||||
...actual,
|
||||
readStoreAllowFromForDmPolicy: async (params: {
|
||||
provider: string;
|
||||
accountId: string;
|
||||
dmPolicy?: string | null;
|
||||
shouldRead?: boolean | null;
|
||||
}) => {
|
||||
if (params.shouldRead === false || params.dmPolicy === "allowlist") {
|
||||
return [];
|
||||
}
|
||||
return await readAllowFromStoreMock(params.provider, params.accountId);
|
||||
},
|
||||
};
|
||||
});
|
||||
vi.mock("../../../src/security/dm-policy-shared.js", (importOriginal) =>
|
||||
createAllowFromRuntimeMock(importOriginal),
|
||||
);
|
||||
|
||||
export function resetDiscordComponentRuntimeMocks() {
|
||||
readAllowFromStoreMock.mockClear().mockResolvedValue([]);
|
||||
|
||||
48
test/helpers/sandbox-fixtures.ts
Normal file
48
test/helpers/sandbox-fixtures.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type {
|
||||
SandboxBrowserConfig,
|
||||
SandboxPruneConfig,
|
||||
SandboxSshConfig,
|
||||
} from "../../src/agents/sandbox/types.js";
|
||||
|
||||
export function createSandboxBrowserConfig(
|
||||
overrides: Partial<SandboxBrowserConfig> = {},
|
||||
): SandboxBrowserConfig {
|
||||
return {
|
||||
enabled: false,
|
||||
image: "openclaw-browser",
|
||||
containerPrefix: "openclaw-browser-",
|
||||
network: "bridge",
|
||||
cdpPort: 9222,
|
||||
vncPort: 5900,
|
||||
noVncPort: 6080,
|
||||
headless: true,
|
||||
enableNoVnc: false,
|
||||
allowHostControl: false,
|
||||
autoStart: false,
|
||||
autoStartTimeoutMs: 1000,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function createSandboxPruneConfig(
|
||||
overrides: Partial<SandboxPruneConfig> = {},
|
||||
): SandboxPruneConfig {
|
||||
return {
|
||||
idleHours: 24,
|
||||
maxAgeDays: 7,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function createSandboxSshConfig(
|
||||
workspaceRoot: string,
|
||||
overrides: Partial<SandboxSshConfig> = {},
|
||||
): SandboxSshConfig {
|
||||
return {
|
||||
command: "ssh",
|
||||
workspaceRoot,
|
||||
strictHostKeyChecking: true,
|
||||
updateHostKeys: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
20
test/helpers/temp-repo.ts
Normal file
20
test/helpers/temp-repo.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
export function makeTempRepoRoot(tempDirs: string[], prefix: string): string {
|
||||
const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
tempDirs.push(repoRoot);
|
||||
return repoRoot;
|
||||
}
|
||||
|
||||
export function writeJsonFile(filePath: string, value: unknown): void {
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
||||
}
|
||||
|
||||
export function cleanupTempDirs(tempDirs: string[]): void {
|
||||
for (const dir of tempDirs.splice(0, tempDirs.length)) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
@@ -7,24 +6,20 @@ import {
|
||||
OFFICIAL_CHANNEL_CATALOG_RELATIVE_PATH,
|
||||
writeOfficialChannelCatalog,
|
||||
} from "../scripts/write-official-channel-catalog.mjs";
|
||||
import { cleanupTempDirs, makeTempRepoRoot, writeJsonFile } from "./helpers/temp-repo.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
function makeRepoRoot(prefix: string): string {
|
||||
const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
tempDirs.push(repoRoot);
|
||||
return repoRoot;
|
||||
return makeTempRepoRoot(tempDirs, prefix);
|
||||
}
|
||||
|
||||
function writeJson(filePath: string, value: unknown): void {
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
||||
writeJsonFile(filePath, value);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const dir of tempDirs.splice(0, tempDirs.length)) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
cleanupTempDirs(tempDirs);
|
||||
});
|
||||
|
||||
describe("buildOfficialChannelCatalog", () => {
|
||||
|
||||
@@ -7,6 +7,11 @@ import { describe, expect, it } from "vitest";
|
||||
import { createOpenShellSandboxBackendFactory } from "../extensions/openshell/src/backend.js";
|
||||
import { resolveOpenShellPluginConfig } from "../extensions/openshell/src/config.js";
|
||||
import { createSandboxTestContext } from "../src/agents/sandbox/test-fixtures.js";
|
||||
import {
|
||||
createSandboxBrowserConfig,
|
||||
createSandboxPruneConfig,
|
||||
createSandboxSshConfig,
|
||||
} from "./helpers/sandbox-fixtures.js";
|
||||
|
||||
const OPENCLAW_OPENSHELL_E2E = process.env.OPENCLAW_E2E_OPENSHELL === "1";
|
||||
const OPENCLAW_OPENSHELL_E2E_TIMEOUT_MS = 12 * 60_000;
|
||||
@@ -366,28 +371,10 @@ describe("openshell sandbox backend e2e", () => {
|
||||
capDrop: ["ALL"],
|
||||
env: {},
|
||||
},
|
||||
ssh: {
|
||||
command: "ssh",
|
||||
workspaceRoot: "/tmp/openclaw-sandboxes",
|
||||
strictHostKeyChecking: true,
|
||||
updateHostKeys: true,
|
||||
},
|
||||
browser: {
|
||||
enabled: false,
|
||||
image: "openclaw-browser",
|
||||
containerPrefix: "openclaw-browser-",
|
||||
network: "bridge",
|
||||
cdpPort: 9222,
|
||||
vncPort: 5900,
|
||||
noVncPort: 6080,
|
||||
headless: true,
|
||||
enableNoVnc: false,
|
||||
allowHostControl: false,
|
||||
autoStart: false,
|
||||
autoStartTimeoutMs: 1000,
|
||||
},
|
||||
ssh: createSandboxSshConfig("/tmp/openclaw-sandboxes"),
|
||||
browser: createSandboxBrowserConfig(),
|
||||
tools: { allow: [], deny: [] },
|
||||
prune: { idleHours: 24, maxAgeDays: 7 },
|
||||
prune: createSandboxPruneConfig(),
|
||||
};
|
||||
|
||||
const pluginConfig = resolveOpenShellPluginConfig({
|
||||
|
||||
@@ -59,6 +59,31 @@ const targetedUnitProxyFiles = [
|
||||
"src/cli/qr-dashboard.integration.test.ts",
|
||||
];
|
||||
|
||||
const REPO_ROOT = path.resolve(import.meta.dirname, "../..");
|
||||
|
||||
function runPlannerPlan(args: string[], env: NodeJS.ProcessEnv): string {
|
||||
return execFileSync("node", ["scripts/test-parallel.mjs", ...args], {
|
||||
cwd: REPO_ROOT,
|
||||
env,
|
||||
encoding: "utf8",
|
||||
});
|
||||
}
|
||||
|
||||
function runHighMemoryLocalMultiSurfacePlan(): string {
|
||||
return runPlannerPlan(
|
||||
["--plan", "--surface", "unit", "--surface", "extensions", "--surface", "channels"],
|
||||
{
|
||||
...clearPlannerShardEnv(process.env),
|
||||
CI: "",
|
||||
GITHUB_ACTIONS: "",
|
||||
RUNNER_OS: "macOS",
|
||||
OPENCLAW_TEST_HOST_CPU_COUNT: "12",
|
||||
OPENCLAW_TEST_HOST_MEMORY_GIB: "128",
|
||||
OPENCLAW_TEST_LOAD_AWARE: "0",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
describe("scripts/test-parallel fatal output guard", () => {
|
||||
it("fails a zero exit when V8 reports an out-of-memory fatal", () => {
|
||||
const output = [
|
||||
@@ -264,33 +289,7 @@ describe("scripts/test-parallel lane planning", () => {
|
||||
});
|
||||
|
||||
it("starts isolated channel lanes before shared extension batches on high-memory local hosts", () => {
|
||||
const repoRoot = path.resolve(import.meta.dirname, "../..");
|
||||
const output = execFileSync(
|
||||
"node",
|
||||
[
|
||||
"scripts/test-parallel.mjs",
|
||||
"--plan",
|
||||
"--surface",
|
||||
"unit",
|
||||
"--surface",
|
||||
"extensions",
|
||||
"--surface",
|
||||
"channels",
|
||||
],
|
||||
{
|
||||
cwd: repoRoot,
|
||||
env: {
|
||||
...clearPlannerShardEnv(process.env),
|
||||
CI: "",
|
||||
GITHUB_ACTIONS: "",
|
||||
RUNNER_OS: "macOS",
|
||||
OPENCLAW_TEST_HOST_CPU_COUNT: "12",
|
||||
OPENCLAW_TEST_HOST_MEMORY_GIB: "128",
|
||||
OPENCLAW_TEST_LOAD_AWARE: "0",
|
||||
},
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
const output = runHighMemoryLocalMultiSurfacePlan();
|
||||
|
||||
const firstChannelIsolated = output.indexOf(
|
||||
"message-handler.preflight.acp-bindings-channels-isolated",
|
||||
@@ -304,33 +303,7 @@ describe("scripts/test-parallel lane planning", () => {
|
||||
});
|
||||
|
||||
it("uses coarser unit-fast batching for high-memory local multi-surface runs", () => {
|
||||
const repoRoot = path.resolve(import.meta.dirname, "../..");
|
||||
const output = execFileSync(
|
||||
"node",
|
||||
[
|
||||
"scripts/test-parallel.mjs",
|
||||
"--plan",
|
||||
"--surface",
|
||||
"unit",
|
||||
"--surface",
|
||||
"extensions",
|
||||
"--surface",
|
||||
"channels",
|
||||
],
|
||||
{
|
||||
cwd: repoRoot,
|
||||
env: {
|
||||
...clearPlannerShardEnv(process.env),
|
||||
CI: "",
|
||||
GITHUB_ACTIONS: "",
|
||||
RUNNER_OS: "macOS",
|
||||
OPENCLAW_TEST_HOST_CPU_COUNT: "12",
|
||||
OPENCLAW_TEST_HOST_MEMORY_GIB: "128",
|
||||
OPENCLAW_TEST_LOAD_AWARE: "0",
|
||||
},
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
const output = runHighMemoryLocalMultiSurfacePlan();
|
||||
|
||||
expect(output).toContain("unit-fast-batch-4");
|
||||
expect(output).not.toContain("unit-fast-batch-5");
|
||||
|
||||
@@ -5,6 +5,21 @@ import {
|
||||
} from "../scripts/test-planner/runtime-profile.mjs";
|
||||
import { resolveLocalVitestMaxWorkers } from "../vitest.config.ts";
|
||||
|
||||
function resolveHighMemoryLocalRuntime() {
|
||||
return resolveRuntimeCapabilities(
|
||||
{
|
||||
RUNNER_OS: "macOS",
|
||||
},
|
||||
{
|
||||
cpuCount: 16,
|
||||
totalMemoryBytes: 128 * 1024 ** 3,
|
||||
platform: "darwin",
|
||||
mode: "local",
|
||||
loadAverage: [0.2, 0.2, 0.2],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
describe("resolveLocalVitestMaxWorkers", () => {
|
||||
it("derives a mid-tier local cap for 64 GiB hosts", () => {
|
||||
expect(
|
||||
@@ -155,18 +170,7 @@ describe("resolveLocalVitestMaxWorkers", () => {
|
||||
});
|
||||
|
||||
it("enables shared channel batching on high-memory local hosts", () => {
|
||||
const runtime = resolveRuntimeCapabilities(
|
||||
{
|
||||
RUNNER_OS: "macOS",
|
||||
},
|
||||
{
|
||||
cpuCount: 16,
|
||||
totalMemoryBytes: 128 * 1024 ** 3,
|
||||
platform: "darwin",
|
||||
mode: "local",
|
||||
loadAverage: [0.2, 0.2, 0.2],
|
||||
},
|
||||
);
|
||||
const runtime = resolveHighMemoryLocalRuntime();
|
||||
const budget = resolveExecutionBudget(runtime);
|
||||
|
||||
expect(runtime.memoryBand).toBe("high");
|
||||
@@ -178,18 +182,7 @@ describe("resolveLocalVitestMaxWorkers", () => {
|
||||
});
|
||||
|
||||
it("uses a coarser shared extension batch target on high-memory local hosts", () => {
|
||||
const runtime = resolveRuntimeCapabilities(
|
||||
{
|
||||
RUNNER_OS: "macOS",
|
||||
},
|
||||
{
|
||||
cpuCount: 16,
|
||||
totalMemoryBytes: 128 * 1024 ** 3,
|
||||
platform: "darwin",
|
||||
mode: "local",
|
||||
loadAverage: [0.2, 0.2, 0.2],
|
||||
},
|
||||
);
|
||||
const runtime = resolveHighMemoryLocalRuntime();
|
||||
const budget = resolveExecutionBudget(runtime);
|
||||
|
||||
expect(runtime.memoryBand).toBe("high");
|
||||
|
||||
Reference in New Issue
Block a user