132 lines
3.9 KiB
TypeScript
132 lines
3.9 KiB
TypeScript
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import { clearExtensionCache, loadExtensions, loadExtensionsCached } from "../../../src/core/extensions/loader.ts";
|
|
import { DefaultResourceLoader } from "../../../src/core/resource-loader.ts";
|
|
|
|
interface TestState {
|
|
moduleLoads?: number;
|
|
factoryRuns?: number;
|
|
}
|
|
|
|
function state(): TestState {
|
|
const global = globalThis as typeof globalThis & { __extensionFactoryCacheTest?: TestState };
|
|
if (!global.__extensionFactoryCacheTest) {
|
|
global.__extensionFactoryCacheTest = {};
|
|
}
|
|
return global.__extensionFactoryCacheTest;
|
|
}
|
|
|
|
function resetState(): void {
|
|
delete (globalThis as typeof globalThis & { __extensionFactoryCacheTest?: TestState }).__extensionFactoryCacheTest;
|
|
}
|
|
|
|
function writeCountingExtension(filePath: string): void {
|
|
writeFileSync(
|
|
filePath,
|
|
`
|
|
const state = (globalThis.__extensionFactoryCacheTest ??= {});
|
|
state.moduleLoads = (state.moduleLoads ?? 0) + 1;
|
|
|
|
export default function () {
|
|
state.factoryRuns = (state.factoryRuns ?? 0) + 1;
|
|
}
|
|
`,
|
|
"utf-8",
|
|
);
|
|
}
|
|
|
|
describe("extension factory cache", () => {
|
|
const roots: string[] = [];
|
|
|
|
function fixture(name: string) {
|
|
const root = join(tmpdir(), `pi-extension-cache-${name}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
const cwd = join(root, "project");
|
|
const agentDir = join(root, "agent");
|
|
mkdirSync(cwd, { recursive: true });
|
|
mkdirSync(agentDir, { recursive: true });
|
|
roots.push(root);
|
|
return { root, cwd, agentDir };
|
|
}
|
|
|
|
beforeEach(() => {
|
|
resetState();
|
|
clearExtensionCache();
|
|
});
|
|
|
|
afterEach(() => {
|
|
while (roots.length > 0) {
|
|
const root = roots.pop();
|
|
if (root && existsSync(root)) {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
}
|
|
resetState();
|
|
clearExtensionCache();
|
|
});
|
|
|
|
it("caches extension modules for cached same-cwd loads but reruns factories", async () => {
|
|
const { root, cwd } = fixture("same-cwd");
|
|
const extensionPath = join(root, "counting.ts");
|
|
writeCountingExtension(extensionPath);
|
|
|
|
const first = await loadExtensionsCached([extensionPath], cwd);
|
|
const second = await loadExtensionsCached([extensionPath], cwd);
|
|
|
|
expect(state().moduleLoads).toBe(1);
|
|
expect(state().factoryRuns).toBe(2);
|
|
expect(first.extensions[0]).not.toBe(second.extensions[0]);
|
|
expect(first.runtime).not.toBe(second.runtime);
|
|
});
|
|
|
|
it("does not cache direct loadExtensions calls", async () => {
|
|
const { root, cwd } = fixture("direct");
|
|
const extensionPath = join(root, "counting.ts");
|
|
writeCountingExtension(extensionPath);
|
|
|
|
await loadExtensions([extensionPath], cwd);
|
|
await loadExtensions([extensionPath], cwd);
|
|
|
|
expect(state().moduleLoads).toBe(2);
|
|
expect(state().factoryRuns).toBe(2);
|
|
});
|
|
|
|
it("clears the cache on resource loader reload", async () => {
|
|
const { cwd, agentDir } = fixture("reload");
|
|
const extensionDir = join(agentDir, "extensions");
|
|
mkdirSync(extensionDir, { recursive: true });
|
|
writeCountingExtension(join(extensionDir, "counting.ts"));
|
|
const loader = new DefaultResourceLoader({
|
|
cwd,
|
|
agentDir,
|
|
noSkills: true,
|
|
noPromptTemplates: true,
|
|
noThemes: true,
|
|
});
|
|
|
|
await loader.reload();
|
|
await loader.reload();
|
|
|
|
expect(state().moduleLoads).toBe(2);
|
|
expect(state().factoryRuns).toBe(2);
|
|
});
|
|
|
|
it("keeps the cache scoped to one cwd", async () => {
|
|
const { root } = fixture("cross-cwd");
|
|
const firstCwd = join(root, "first");
|
|
const secondCwd = join(root, "second");
|
|
mkdirSync(firstCwd, { recursive: true });
|
|
mkdirSync(secondCwd, { recursive: true });
|
|
const extensionPath = join(root, "counting.ts");
|
|
writeCountingExtension(extensionPath);
|
|
|
|
await loadExtensionsCached([extensionPath], firstCwd);
|
|
await loadExtensionsCached([extensionPath], secondCwd);
|
|
await loadExtensionsCached([extensionPath], secondCwd);
|
|
|
|
expect(state().moduleLoads).toBe(2);
|
|
expect(state().factoryRuns).toBe(3);
|
|
});
|
|
});
|