mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 23:14:31 +00:00
test(runtime): trim timer-heavy regression suites
This commit is contained in:
@@ -115,7 +115,7 @@ function createIsolatedRegressionJob(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function writeCronJobs(storePath: string, jobs: CronJob[]) {
|
async function writeCronJobs(storePath: string, jobs: CronJob[]) {
|
||||||
await fs.writeFile(storePath, JSON.stringify({ version: 1, jobs }, null, 2), "utf-8");
|
await fs.writeFile(storePath, JSON.stringify({ version: 1, jobs }), "utf-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startCronForStore(params: {
|
async function startCronForStore(params: {
|
||||||
@@ -665,7 +665,7 @@ describe("Cron issue regressions", () => {
|
|||||||
if (targetJob?.delivery?.channel === "telegram") {
|
if (targetJob?.delivery?.channel === "telegram") {
|
||||||
targetJob.delivery.to = rewrittenTarget;
|
targetJob.delivery.to = rewrittenTarget;
|
||||||
}
|
}
|
||||||
await fs.writeFile(store.storePath, JSON.stringify(persisted, null, 2), "utf-8");
|
await fs.writeFile(store.storePath, JSON.stringify(persisted), "utf-8");
|
||||||
return { status: "ok" as const, summary: "done", delivered: true };
|
return { status: "ok" as const, summary: "done", delivered: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -736,11 +736,7 @@ describe("Cron issue regressions", () => {
|
|||||||
];
|
];
|
||||||
for (const { id, state } of terminalStates) {
|
for (const { id, state } of terminalStates) {
|
||||||
const job: CronJob = { id, ...baseJob, state };
|
const job: CronJob = { id, ...baseJob, state };
|
||||||
await fs.writeFile(
|
await fs.writeFile(store.storePath, JSON.stringify({ version: 1, jobs: [job] }), "utf-8");
|
||||||
store.storePath,
|
|
||||||
JSON.stringify({ version: 1, jobs: [job] }, null, 2),
|
|
||||||
"utf-8",
|
|
||||||
);
|
|
||||||
const enqueueSystemEvent = vi.fn();
|
const enqueueSystemEvent = vi.fn();
|
||||||
const cron = await startCronForStore({
|
const cron = await startCronForStore({
|
||||||
storePath: store.storePath,
|
storePath: store.storePath,
|
||||||
@@ -1410,7 +1406,7 @@ describe("Cron issue regressions", () => {
|
|||||||
const second = createDueIsolatedJob({ id: "batch-second", nowMs: dueAt, nextRunAtMs: dueAt });
|
const second = createDueIsolatedJob({ id: "batch-second", nowMs: dueAt, nextRunAtMs: dueAt });
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
store.storePath,
|
store.storePath,
|
||||||
JSON.stringify({ version: 1, jobs: [first, second] }, null, 2),
|
JSON.stringify({ version: 1, jobs: [first, second] }),
|
||||||
"utf-8",
|
"utf-8",
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1460,7 +1456,7 @@ describe("Cron issue regressions", () => {
|
|||||||
});
|
});
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
store.storePath,
|
store.storePath,
|
||||||
JSON.stringify({ version: 1, jobs: [first, second] }, null, 2),
|
JSON.stringify({ version: 1, jobs: [first, second] }),
|
||||||
"utf-8",
|
"utf-8",
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1524,9 +1520,9 @@ describe("Cron issue regressions", () => {
|
|||||||
const store = await makeStorePath();
|
const store = await makeStorePath();
|
||||||
const scheduledAt = Date.parse("2026-02-15T13:00:00.000Z");
|
const scheduledAt = Date.parse("2026-02-15T13:00:00.000Z");
|
||||||
|
|
||||||
// Use a short but observable timeout: 300 ms.
|
// Keep this short for suite speed while still separating expected timeout
|
||||||
// Before the fix, premature timeout would fire at ~100 ms (1/3 of 300 ms).
|
// from the 1/3-regression timeout.
|
||||||
const timeoutSeconds = 0.3;
|
const timeoutSeconds = 0.16;
|
||||||
const cronJob = createIsolatedRegressionJob({
|
const cronJob = createIsolatedRegressionJob({
|
||||||
id: "timeout-fraction-29774",
|
id: "timeout-fraction-29774",
|
||||||
name: "timeout fraction regression",
|
name: "timeout fraction regression",
|
||||||
@@ -1537,10 +1533,10 @@ describe("Cron issue regressions", () => {
|
|||||||
});
|
});
|
||||||
await writeCronJobs(store.storePath, [cronJob]);
|
await writeCronJobs(store.storePath, [cronJob]);
|
||||||
|
|
||||||
const tempFile = path.join(os.tmpdir(), `cron-29774-${Date.now()}.txt`);
|
|
||||||
let now = scheduledAt;
|
let now = scheduledAt;
|
||||||
const wallStart = Date.now();
|
const wallStart = Date.now();
|
||||||
let abortWallMs: number | undefined;
|
let abortWallMs: number | undefined;
|
||||||
|
let started = false;
|
||||||
|
|
||||||
const state = createCronServiceState({
|
const state = createCronServiceState({
|
||||||
cronEnabled: true,
|
cronEnabled: true,
|
||||||
@@ -1550,8 +1546,7 @@ describe("Cron issue regressions", () => {
|
|||||||
enqueueSystemEvent: vi.fn(),
|
enqueueSystemEvent: vi.fn(),
|
||||||
requestHeartbeatNow: vi.fn(),
|
requestHeartbeatNow: vi.fn(),
|
||||||
runIsolatedAgentJob: vi.fn(async ({ abortSignal }: { abortSignal?: AbortSignal }) => {
|
runIsolatedAgentJob: vi.fn(async ({ abortSignal }: { abortSignal?: AbortSignal }) => {
|
||||||
// Real side effect: confirm the job actually started.
|
started = true;
|
||||||
await fs.writeFile(tempFile, "started", "utf-8");
|
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
if (!abortSignal) {
|
if (!abortSignal) {
|
||||||
resolve();
|
resolve();
|
||||||
@@ -1578,15 +1573,12 @@ describe("Cron issue regressions", () => {
|
|||||||
|
|
||||||
await onTimer(state);
|
await onTimer(state);
|
||||||
|
|
||||||
// Confirm job started (real side effect).
|
expect(started).toBe(true);
|
||||||
await expect(fs.readFile(tempFile, "utf-8")).resolves.toBe("started");
|
|
||||||
await fs.unlink(tempFile).catch(() => {});
|
|
||||||
|
|
||||||
// The outer cron timeout fires at timeoutSeconds * 1000 = 300 ms.
|
// The abort must not fire at the old ~1/3 regression value.
|
||||||
// The abort must not have fired at ~100 ms (the 1/3 regression value).
|
// Keep the lower bound conservative for loaded CI runners.
|
||||||
// Allow generous lower bound (80%) to keep the test stable on loaded CI runners.
|
|
||||||
const elapsedMs = (abortWallMs ?? Date.now()) - wallStart;
|
const elapsedMs = (abortWallMs ?? Date.now()) - wallStart;
|
||||||
expect(elapsedMs).toBeGreaterThanOrEqual(timeoutSeconds * 1000 * 0.8);
|
expect(elapsedMs).toBeGreaterThanOrEqual(timeoutSeconds * 1000 * 0.7);
|
||||||
|
|
||||||
const job = state.store?.jobs.find((entry) => entry.id === "timeout-fraction-29774");
|
const job = state.store?.jobs.find((entry) => entry.id === "timeout-fraction-29774");
|
||||||
expect(job?.state.lastStatus).toBe("error");
|
expect(job?.state.lastStatus).toBe("error");
|
||||||
|
|||||||
@@ -234,21 +234,6 @@ describe("loadOpenClawPlugins", () => {
|
|||||||
|
|
||||||
const bundled = registry.plugins.find((entry) => entry.id === "bundled");
|
const bundled = registry.plugins.find((entry) => entry.id === "bundled");
|
||||||
expect(bundled?.status).toBe("disabled");
|
expect(bundled?.status).toBe("disabled");
|
||||||
|
|
||||||
const enabledRegistry = loadOpenClawPlugins({
|
|
||||||
cache: false,
|
|
||||||
config: {
|
|
||||||
plugins: {
|
|
||||||
allow: ["bundled"],
|
|
||||||
entries: {
|
|
||||||
bundled: { enabled: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const enabled = enabledRegistry.plugins.find((entry) => entry.id === "bundled");
|
|
||||||
expect(enabled?.status).toBe("loaded");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("loads bundled telegram plugin when enabled", () => {
|
it("loads bundled telegram plugin when enabled", () => {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { withEnvAsync } from "../test-utils/env.js";
|
|||||||
import { attachChildProcessBridge } from "./child-process-bridge.js";
|
import { attachChildProcessBridge } from "./child-process-bridge.js";
|
||||||
import { runCommandWithTimeout, shouldSpawnWithShell } from "./exec.js";
|
import { runCommandWithTimeout, shouldSpawnWithShell } from "./exec.js";
|
||||||
|
|
||||||
const CHILD_READY_TIMEOUT_MS = 4_000;
|
const CHILD_READY_TIMEOUT_MS = 2_000;
|
||||||
const CHILD_EXIT_TIMEOUT_MS = 4_000;
|
const CHILD_EXIT_TIMEOUT_MS = 2_000;
|
||||||
|
|
||||||
function waitForLine(
|
function waitForLine(
|
||||||
stream: NodeJS.ReadableStream,
|
stream: NodeJS.ReadableStream,
|
||||||
@@ -79,10 +79,10 @@ describe("runCommandWithTimeout", () => {
|
|||||||
|
|
||||||
it("kills command when no output timeout elapses", async () => {
|
it("kills command when no output timeout elapses", async () => {
|
||||||
const result = await runCommandWithTimeout(
|
const result = await runCommandWithTimeout(
|
||||||
[process.execPath, "-e", "setTimeout(() => {}, 40)"],
|
[process.execPath, "-e", "setTimeout(() => {}, 80)"],
|
||||||
{
|
{
|
||||||
timeoutMs: 500,
|
timeoutMs: 500,
|
||||||
noOutputTimeoutMs: 20,
|
noOutputTimeoutMs: 25,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -101,24 +101,24 @@ describe("runCommandWithTimeout", () => {
|
|||||||
"let count = 0;",
|
"let count = 0;",
|
||||||
'const ticker = setInterval(() => { process.stdout.write(".");',
|
'const ticker = setInterval(() => { process.stdout.write(".");',
|
||||||
"count += 1;",
|
"count += 1;",
|
||||||
"if (count === 6) {",
|
"if (count === 3) {",
|
||||||
"clearInterval(ticker);",
|
"clearInterval(ticker);",
|
||||||
"process.exit(0);",
|
"process.exit(0);",
|
||||||
"}",
|
"}",
|
||||||
"}, 25);",
|
"}, 15);",
|
||||||
].join(" "),
|
].join(" "),
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
timeoutMs: 3_000,
|
timeoutMs: 3_000,
|
||||||
// Keep a healthy margin above the emit interval while avoiding a 1s+ test delay.
|
// Keep a healthy margin above the emit interval while avoiding long idle waits.
|
||||||
noOutputTimeoutMs: 400,
|
noOutputTimeoutMs: 120,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.code ?? 0).toBe(0);
|
expect(result.code ?? 0).toBe(0);
|
||||||
expect(result.termination).toBe("exit");
|
expect(result.termination).toBe("exit");
|
||||||
expect(result.noOutputTimedOut).toBe(false);
|
expect(result.noOutputTimedOut).toBe(false);
|
||||||
expect(result.stdout.length).toBeGreaterThanOrEqual(7);
|
expect(result.stdout.length).toBeGreaterThanOrEqual(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reports global timeout termination when overall timeout elapses", async () => {
|
it("reports global timeout termination when overall timeout elapses", async () => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { createProcessSupervisor } from "./supervisor.js";
|
|||||||
type ProcessSupervisor = ReturnType<typeof createProcessSupervisor>;
|
type ProcessSupervisor = ReturnType<typeof createProcessSupervisor>;
|
||||||
type SpawnOptions = Parameters<ProcessSupervisor["spawn"]>[0];
|
type SpawnOptions = Parameters<ProcessSupervisor["spawn"]>[0];
|
||||||
type ChildSpawnOptions = Omit<Extract<SpawnOptions, { mode: "child" }>, "backendId" | "mode">;
|
type ChildSpawnOptions = Omit<Extract<SpawnOptions, { mode: "child" }>, "backendId" | "mode">;
|
||||||
const OUTPUT_DELAY_MS = 40;
|
const OUTPUT_DELAY_MS = 15;
|
||||||
|
|
||||||
async function spawnChild(supervisor: ProcessSupervisor, options: ChildSpawnOptions) {
|
async function spawnChild(supervisor: ProcessSupervisor, options: ChildSpawnOptions) {
|
||||||
return supervisor.spawn({
|
return supervisor.spawn({
|
||||||
@@ -38,9 +38,9 @@ describe("process supervisor", () => {
|
|||||||
const supervisor = createProcessSupervisor();
|
const supervisor = createProcessSupervisor();
|
||||||
const run = await spawnChild(supervisor, {
|
const run = await spawnChild(supervisor, {
|
||||||
sessionId: "s1",
|
sessionId: "s1",
|
||||||
argv: [process.execPath, "-e", "setTimeout(() => {}, 40)"],
|
argv: [process.execPath, "-e", "setTimeout(() => {}, 30)"],
|
||||||
timeoutMs: 500,
|
timeoutMs: 500,
|
||||||
noOutputTimeoutMs: 20,
|
noOutputTimeoutMs: 12,
|
||||||
stdinMode: "pipe-closed",
|
stdinMode: "pipe-closed",
|
||||||
});
|
});
|
||||||
const exit = await run.wait();
|
const exit = await run.wait();
|
||||||
@@ -84,7 +84,7 @@ describe("process supervisor", () => {
|
|||||||
const supervisor = createProcessSupervisor();
|
const supervisor = createProcessSupervisor();
|
||||||
const run = await spawnChild(supervisor, {
|
const run = await spawnChild(supervisor, {
|
||||||
sessionId: "s-timeout",
|
sessionId: "s-timeout",
|
||||||
argv: [process.execPath, "-e", "setTimeout(() => {}, 40)"],
|
argv: [process.execPath, "-e", "setTimeout(() => {}, 20)"],
|
||||||
timeoutMs: 1,
|
timeoutMs: 1,
|
||||||
stdinMode: "pipe-closed",
|
stdinMode: "pipe-closed",
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user