mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 12:44:46 +00:00
test: speed extension and contract scenarios
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user