openclaw/test/setup-openclaw-runtime.plugin-cache.test.ts
Peter Steinberger 931c7b40b2
fix(test): first test or hook times out while compiled worker subprocesses prepare (#162792)
* fix(test): load compiled-subprocess declarations at collection

The first load of a compiled-subprocess declaration in a Vitest invocation
prepares the whole compiled worker generation (tens of seconds warm, minutes
cold). 387 test files first reached a declaration through an await import()
inside a test body or hook, so their first test or hook absorbed that
preparation and could time out.

Move those loads to collection at their owner: static subject imports,
static imports in the shared helpers that owned the lazy load, and a named
side-effect preload (src/test-utils/prepare-compiled-subprocesses.ts) for
suites that re-import their subject per test or whose first load happens in
production code they call. plugin-test-runtime keeps the host-capability
fixture lazy so its other consumers do not start loading that graph.
Document the rule in docs/help/testing/writing-tests.md.

335 of the 387 files now load their first declaration before collection
ends; 52 remain (48 extension tests needing an SDK preload, one package
test, three files over the line cap).

* fix(test): retain npm install fixture reuse

Preserve the existing failedSpawn reuse after merging main so the collection-time preload stays within the line-cap ratchet. Assertions and import ordering are unchanged.

* fix(test): keep memory-core facade cold-import assertion meaningful

The static subject import ran before beforeEach reset the loader mock, so the cold-import assertion passed vacuously. Restore the in-test subject imports after mock setup and preload the compiled-subprocess declaration during collection instead.
2026-10-01 11:42:59 -07:00

75 lines
2.5 KiB
TypeScript

import { expect, it, vi } from "vitest";
import "../src/test-utils/prepare-compiled-subprocesses.js";
import { createDeferredCore } from "../src/shared/deferred.js";
const hooks = vi.hoisted(() => ({ afterEach: [] as Array<() => Promise<void>> }));
vi.mock("vitest", async (importOriginal) => ({
...(await importOriginal<typeof import("vitest")>()),
beforeAll: () => {},
afterAll: () => {},
afterEach: (cleanup: () => Promise<void>) => hooks.afterEach.push(cleanup),
}));
it.each([false, true])(
"joins retired plugin caches in the registered runtime reset (cleanup fails: %s)",
async (fails) => {
vi.resetModules();
hooks.afterEach.length = 0;
await import("./setup-openclaw-runtime.js");
const cleanup = hooks.afterEach.at(-1);
if (!cleanup) {
throw new Error("Runtime setup did not register its cleanup hook");
}
const { getProcessPluginCache, retainPluginCacheInstance, waitForPluginCacheRetirement } =
await import("../src/plugins/plugin-cache.js");
const { PluginInstance } = await import("../src/plugins/plugin-instance.js");
const entered = createDeferredCore();
const finish = createDeferredCore();
const failure = new Error("synthetic plugin retirement failure");
const instance = new PluginInstance("runtime-reset-fixture");
instance.lifecycle.onDispose(async () => {
entered.resolve();
await finish.promise;
if (fails) {
throw failure;
}
});
retainPluginCacheInstance(instance, getProcessPluginCache());
let settled = false;
const result = cleanup().then(
() => {
settled = true;
return undefined;
},
(error: unknown) => {
settled = true;
return error;
},
);
try {
await entered.promise;
expect(settled).toBe(false);
finish.resolve();
if (fails) {
const error = await result;
expect(error).toBeInstanceOf(AggregateError);
if (!(error instanceof AggregateError)) {
throw new Error("Runtime cleanup did not report the plugin retirement failure");
}
expect(error.errors).toEqual([
{ pluginId: "runtime-reset-fixture", hookId: "instance", error: failure },
]);
} else {
await expect(result).resolves.toBeUndefined();
}
await expect(waitForPluginCacheRetirement()).resolves.toEqual({
cleanupCount: 0,
failures: [],
});
} finally {
finish.resolve();
await result;
await waitForPluginCacheRetirement();
}
},
);