test: stabilize alpha plugin prerelease gates

This commit is contained in:
Tideclaw
2026-06-01 06:19:04 +00:00
parent 598281eff5
commit b25b6ce2ee
3 changed files with 116 additions and 34 deletions

View File

@@ -1894,7 +1894,6 @@ describe("TelegramPollingSession", () => {
});
it("keeps active spooled lanes blocked across isolated ingress restarts", async () => {
vi.useFakeTimers({ shouldAdvanceTime: true });
const abort = new AbortController();
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-telegram-spool-"));
let releaseRegularTurn: (() => void) | undefined;
@@ -1921,24 +1920,32 @@ describe("TelegramPollingSession", () => {
},
});
let workerTaskCalls = 0;
const createWorker = vi.fn(() => ({
onMessage: vi.fn(() => () => undefined),
stop: vi.fn(async () => undefined),
task: vi.fn(async () => {
workerTaskCalls += 1;
if (workerTaskCalls === 1) {
return;
}
await new Promise<void>((resolve) => {
abort.signal.addEventListener("abort", () => resolve(), { once: true });
});
}),
}));
const workerStops: Array<() => void> = [];
const createWorker = vi.fn(() => {
let stopWorker: (() => void) | undefined;
const workerDone = new Promise<void>((resolve) => {
stopWorker = resolve;
});
workerStops.push(() => {
stopWorker?.();
});
return {
onMessage: vi.fn(() => () => undefined),
stop: vi.fn(async () => {
stopWorker?.();
}),
task: vi.fn(async () => {
await workerDone;
}),
};
});
const watchdogHarness = installPollingStallWatchdogHarness([0]);
let runPromise: Promise<void> | undefined;
try {
const session = createPollingSession({
abortSignal: abort.signal,
stallThresholdMs: 30_000,
isolatedIngress: {
enabled: true,
spoolDir: tempDir,
@@ -1947,14 +1954,15 @@ describe("TelegramPollingSession", () => {
},
});
const runPromise = session.runUntilAbort();
runPromise = session.runUntilAbort();
await vi.waitFor(() => expect(handleUpdate).toHaveBeenCalledTimes(1));
await vi.advanceTimersByTimeAsync(16_000);
const watchdog = await watchdogHarness.waitForWatchdog();
watchdogHarness.setNow(31_000);
watchdog();
await vi.waitFor(() => expect(createWorker).toHaveBeenCalledTimes(2));
expect(handleUpdate).toHaveBeenCalledTimes(1);
releaseRegularTurn?.();
await vi.advanceTimersByTimeAsync(1_000);
await vi.waitFor(async () =>
expect(
(await listTelegramSpooledUpdates({ spoolDir: tempDir })).map(
@@ -1963,11 +1971,15 @@ describe("TelegramPollingSession", () => {
).toEqual([]),
);
abort.abort();
await vi.advanceTimersByTimeAsync(20_000);
await runPromise;
} finally {
releaseRegularTurn?.();
vi.useRealTimers();
for (const stopWorker of workerStops) {
stopWorker();
}
abort.abort();
watchdogHarness.restore();
await runPromise?.catch(() => undefined);
await fs.rm(tempDir, { recursive: true, force: true });
}
});

View File

@@ -9,6 +9,9 @@ import { isDirectScriptRun, runVitestBatch } from "./lib/vitest-batch-runner.mjs
const FS_MODULE_CACHE_PATH_ENV_KEY = "OPENCLAW_VITEST_FS_MODULE_CACHE_PATH";
const PARALLEL_ENV_KEY = "OPENCLAW_EXTENSION_BATCH_PARALLEL";
const TARGET_CHUNK_SIZE_ENV_KEY = "OPENCLAW_EXTENSION_BATCH_TARGET_CHUNK_SIZE";
const TELEGRAM_VITEST_CONFIG = "test/vitest/vitest.extension-telegram.config.ts";
const TELEGRAM_TARGET_CHUNK_SIZE = 40;
const ALLOW_NO_TESTS_FLAG = "--allow-no-tests";
const ALLOW_EMPTY_AFTER_EXCLUDE_FLAG = "--allow-empty-after-exclude";
@@ -130,8 +133,8 @@ export function parseExactVitestExcludePaths(vitestArgs) {
return excludePaths;
}
function resolveGroupTargets(group, exactExcludePaths) {
if (exactExcludePaths.size === 0) {
function resolveGroupTargets(group, exactExcludePaths, forceFileTargets = false) {
if (exactExcludePaths.size === 0 && !forceFileTargets) {
return group.roots;
}
@@ -143,8 +146,28 @@ function resolveGroupTargets(group, exactExcludePaths) {
return testFiles.filter((file) => !exactExcludePaths.has(file));
}
function resolveGroupTargetChunkSize(group, env) {
const override = parsePositiveInt(env[TARGET_CHUNK_SIZE_ENV_KEY]);
if (override !== null) {
return override;
}
return group.config === TELEGRAM_VITEST_CONFIG ? TELEGRAM_TARGET_CHUNK_SIZE : null;
}
function chunkTargets(targets, chunkSize) {
if (!chunkSize || targets.length <= chunkSize) {
return [targets];
}
const chunks = [];
for (let index = 0; index < targets.length; index += chunkSize) {
chunks.push(targets.slice(index, index + chunkSize));
}
return chunks;
}
async function runPlanGroup(group, params) {
const targets = resolveGroupTargets(group, params.exactExcludePaths);
const targetChunkSize = resolveGroupTargetChunkSize(group, params.env);
const targets = resolveGroupTargets(group, params.exactExcludePaths, targetChunkSize !== null);
if (targets.length === 0) {
console.error(`[test-extension-batch] ${group.config}: no test files remain after excludes`);
return params.allowEmptyAfterExclude ? 0 : 1;
@@ -153,17 +176,29 @@ async function runPlanGroup(group, params) {
console.log(
`[test-extension-batch] ${group.config}: ${group.extensionIds.join(", ")} (${targets.length} targets)`,
);
return await params.runGroup({
args: params.vitestArgs,
config: group.config,
env: createGroupEnv({
baseEnv: params.env,
group,
groupIndex: params.groupIndex,
useDedicatedCache: params.useDedicatedCache,
}),
targets,
});
const targetChunks = chunkTargets(targets, targetChunkSize);
for (const [index, chunk] of targetChunks.entries()) {
if (targetChunks.length > 1) {
console.log(
`[test-extension-batch] ${group.config}: chunk ${index + 1}/${targetChunks.length} (${chunk.length} targets)`,
);
}
const exitCode = await params.runGroup({
args: params.vitestArgs,
config: group.config,
env: createGroupEnv({
baseEnv: params.env,
group,
groupIndex: params.groupIndex,
useDedicatedCache: params.useDedicatedCache,
}),
targets: chunk,
});
if (exitCode !== 0) {
return exitCode;
}
}
return 0;
}
export async function runExtensionBatchPlan(batchPlan, params = {}) {

View File

@@ -675,6 +675,41 @@ describe("scripts/test-extension.mjs", () => {
expect(runParams.targets).toContain("extensions/codex/src/app-server/client.test.ts");
});
it("chunks large extension batch groups into separate Vitest processes", async () => {
const runGroup = vi.fn<() => Promise<number>>().mockResolvedValue(0);
const result = await runExtensionBatchPlan(
{
extensionCount: 1,
extensionIds: ["telegram"],
estimatedCost: 125,
hasTests: true,
planGroups: [
{
config: "test/vitest/vitest.extension-telegram.config.ts",
estimatedCost: 125,
extensionIds: ["telegram"],
roots: [bundledPluginRoot("telegram")],
testFileCount: 125,
},
],
testFileCount: 125,
},
{
env: { OPENCLAW_EXTENSION_BATCH_TARGET_CHUNK_SIZE: "50" },
runGroup,
},
);
expect(result).toBe(0);
expect(runGroup).toHaveBeenCalledTimes(3);
expect(runGroup.mock.calls.map(([params]) => params.targets)).toEqual([
expect.arrayContaining([bundledPluginFile("telegram", "index.test.ts")]),
expect.any(Array),
expect.any(Array),
]);
expect(runGroup.mock.calls.map(([params]) => params.targets.length)).toEqual([50, 50, 25]);
});
it("fails extension batch groups when exact excludes remove every test", async () => {
const runGroup = vi.fn<() => Promise<number>>().mockResolvedValue(0);
const result = await runExtensionBatchPlan(