test: speed extension and contract scenarios

This commit is contained in:
Peter Steinberger
2026-05-06 00:54:06 +01:00
parent cb42efb6e6
commit 093b2b9b5f
16 changed files with 270 additions and 89 deletions

View File

@@ -416,21 +416,8 @@ function collectWorkspaceCodeFiles(): string[] {
return files;
}
function countIdentifierReferences(
files: readonly string[],
excludedFile: string,
name: string,
): number {
let count = 0;
const pattern = new RegExp(`\\b${name}\\b`, "g");
for (const file of files) {
if (file === excludedFile) {
continue;
}
const source = readFileSync(file, "utf8");
count += [...source.matchAll(pattern)].length;
}
return count;
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function collectUnusedExtensionTestApiExports(): Array<{ file: string; exportName: string }> {
@@ -439,12 +426,54 @@ function collectUnusedExtensionTestApiExports(): Array<{ file: string; exportNam
const testApiFiles = collectCodeFiles(resolve(REPO_ROOT, "extensions")).filter((file) =>
file.endsWith("/test-api.ts"),
);
const testApiExports = new Map<string, string[]>();
const exportNames = new Set<string>();
for (const file of testApiFiles) {
const repoRelativePath = relative(REPO_ROOT, file).replaceAll("\\", "/");
const source = readFileSync(file, "utf8");
for (const exportName of parseTestApiNamedExports(source)) {
if (countIdentifierReferences(workspaceCodeFiles, file, exportName) === 0) {
const namedExports = parseTestApiNamedExports(source);
testApiExports.set(file, namedExports);
for (const exportName of namedExports) {
exportNames.add(exportName);
}
}
if (exportNames.size === 0) {
return [];
}
const identifierPattern = new RegExp(
`\\b(${[...exportNames].map(escapeRegExp).join("|")})\\b`,
"g",
);
const referenceCounts = new Map<string, number>();
const selfReferenceCounts = new Map<string, Map<string, number>>();
for (const file of workspaceCodeFiles) {
const source = readFileSync(file, "utf8");
const selfCounts = testApiExports.has(file) ? new Map<string, number>() : undefined;
for (const match of source.matchAll(identifierPattern)) {
const exportName = match[1];
if (!exportName) {
continue;
}
referenceCounts.set(exportName, (referenceCounts.get(exportName) ?? 0) + 1);
if (selfCounts) {
selfCounts.set(exportName, (selfCounts.get(exportName) ?? 0) + 1);
}
}
if (selfCounts) {
selfReferenceCounts.set(file, selfCounts);
}
}
for (const [file, namedExports] of testApiExports) {
const repoRelativePath = relative(REPO_ROOT, file).replaceAll("\\", "/");
for (const exportName of namedExports) {
const referenceCount =
(referenceCounts.get(exportName) ?? 0) -
(selfReferenceCounts.get(file)?.get(exportName) ?? 0);
if (referenceCount === 0) {
leaks.push({ file: repoRelativePath, exportName });
}
}

View File

@@ -9,7 +9,23 @@ const require = createRequire(import.meta.url);
const rootAliasPath = fileURLToPath(new URL("../../plugin-sdk/root-alias.cjs", import.meta.url));
const rootSdk = require(rootAliasPath) as Record<string, unknown>;
const rootAliasSource = fs.readFileSync(rootAliasPath, "utf-8");
const compatPath = fileURLToPath(new URL("../../plugin-sdk/compat.ts", import.meta.url));
const packageJsonPath = fileURLToPath(new URL("../../../package.json", import.meta.url));
const legacyRootExportNames = [
"registerContextEngine",
"buildMemorySystemPromptAddition",
"delegateCompactionToRuntime",
"optionalStringEnum",
"stringEnum",
"buildChannelConfigSchema",
"normalizeAccountId",
"createReplyPrefixContext",
"createReplyPrefixOptions",
"createTypingCallbacks",
"createChannelReplyPipeline",
"resolveChannelSourceReplyDeliveryMode",
"resolvePreferredOpenClawTmpDir",
] as const;
type EmptySchema = {
safeParse: (value: unknown) =>
@@ -153,6 +169,52 @@ function expectDiagnosticEventAccessor(lazyModule: ReturnType<typeof loadRootAli
).toBe("function");
}
function collectRuntimeExports(filePath: string, seen = new Set<string>()): Set<string> {
const normalizedPath = path.resolve(filePath);
if (seen.has(normalizedPath)) {
return new Set();
}
seen.add(normalizedPath);
const source = fs.readFileSync(normalizedPath, "utf-8");
const exportNames = new Set<string>();
for (const match of source.matchAll(/export\s+(?:const|function|class)\s+([A-Za-z_$][\w$]*)/g)) {
exportNames.add(match[1]);
}
for (const match of source.matchAll(/export\s+(?!type\b)\{([\s\S]*?)\}\s+from\s+"([^"]+)";/g)) {
const names = match[1]
.split(",")
.map((part) => part.trim())
.filter((part) => part.length > 0 && !part.startsWith("type "))
.map(
(part) =>
part
.split(/\s+as\s+/u)
.at(-1)
?.trim() ?? part,
);
for (const name of names) {
exportNames.add(name);
}
}
for (const match of source.matchAll(/export\s+\*\s+from\s+"([^"]+)";/g)) {
const specifier = match[1];
if (!specifier.startsWith(".")) {
continue;
}
const nestedPath = path.resolve(
path.dirname(normalizedPath),
specifier.replace(/\.(?:mjs|js)$/u, ".ts"),
);
const nestedExports = collectRuntimeExports(nestedPath, seen);
for (const name of nestedExports) {
exportNames.add(name);
}
}
return exportNames;
}
describe("plugin-sdk root alias", () => {
it("exposes the fast empty config schema helper", () => {
const factory = rootSdk.emptyPluginConfigSchema as (() => EmptySchema) | undefined;
@@ -457,28 +519,35 @@ describe("plugin-sdk root alias", () => {
expect(exportName in lazyModule.moduleExports).toBe(true);
});
it("loads legacy root exports through the merged root wrapper", { timeout: 240_000 }, () => {
it("forwards legacy root exports through the merged root wrapper", () => {
const monolithicExports = Object.fromEntries(
legacyRootExportNames.map((name) => [name, () => name]),
);
const lazyModule = loadRootAliasWithStubs({ monolithicExports });
expect(typeof rootSdk.emptyPluginConfigSchema).toBe("function");
expect(typeof rootSdk.registerContextEngine).toBe("function");
expect(typeof rootSdk.buildMemorySystemPromptAddition).toBe("function");
expect(typeof rootSdk.delegateCompactionToRuntime).toBe("function");
expect(typeof rootSdk.resolveControlCommandGate).toBe("function");
expect(typeof rootSdk.onDiagnosticEvent).toBe("function");
expect(typeof rootSdk.optionalStringEnum).toBe("function");
expect(typeof rootSdk.stringEnum).toBe("function");
expect(typeof rootSdk.buildChannelConfigSchema).toBe("function");
expect(typeof rootSdk.normalizeAccountId).toBe("function");
expect(typeof rootSdk.createReplyPrefixContext).toBe("function");
expect(typeof rootSdk.createReplyPrefixOptions).toBe("function");
expect(typeof rootSdk.createTypingCallbacks).toBe("function");
expect(typeof rootSdk.createChannelReplyPipeline).toBe("function");
expect(typeof rootSdk.resolveChannelSourceReplyDeliveryMode).toBe("function");
expect(typeof rootSdk.resolvePreferredOpenClawTmpDir).toBe("function");
for (const name of legacyRootExportNames) {
expect(typeof lazyModule.moduleExports[name]).toBe("function");
}
expect(lazyModule.jitiLoadCalls).toBe(1);
expect(Object.keys(lazyModule.moduleExports)).toEqual(
expect.arrayContaining([...legacyRootExportNames]),
);
expect(typeof rootSdk.default).toBe("object");
expect(rootSdk.default).toBe(rootSdk);
expect(rootSdk.__esModule).toBe(true);
});
it("keeps legacy root export names present in the compat source", () => {
const compatExports = collectRuntimeExports(compatPath);
for (const name of legacyRootExportNames) {
expect(compatExports.has(name)).toBe(true);
}
});
it("does not publish private local-only plugin-sdk subpaths", () => {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) as {
exports?: Record<string, unknown>;