mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 12:21:24 +00:00
perf(test): reduce skills + update + memory suite overhead
This commit is contained in:
@@ -4,6 +4,7 @@ import { resolveBundledSkillsDir, type BundledSkillsResolveOptions } from "./bun
|
|||||||
|
|
||||||
const skillsLogger = createSubsystemLogger("skills");
|
const skillsLogger = createSubsystemLogger("skills");
|
||||||
let hasWarnedMissingBundledDir = false;
|
let hasWarnedMissingBundledDir = false;
|
||||||
|
let cachedBundledContext: { dir: string; names: Set<string> } | null = null;
|
||||||
|
|
||||||
export type BundledSkillsContext = {
|
export type BundledSkillsContext = {
|
||||||
dir?: string;
|
dir?: string;
|
||||||
@@ -24,11 +25,16 @@ export function resolveBundledSkillsContext(
|
|||||||
}
|
}
|
||||||
return { dir, names };
|
return { dir, names };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cachedBundledContext?.dir === dir) {
|
||||||
|
return { dir, names: new Set(cachedBundledContext.names) };
|
||||||
|
}
|
||||||
const result = loadSkillsFromDir({ dir, source: "openclaw-bundled" });
|
const result = loadSkillsFromDir({ dir, source: "openclaw-bundled" });
|
||||||
for (const skill of result.skills) {
|
for (const skill of result.skills) {
|
||||||
if (skill.name.trim()) {
|
if (skill.name.trim()) {
|
||||||
names.add(skill.name);
|
names.add(skill.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cachedBundledContext = { dir, names: new Set(names) };
|
||||||
return { dir, names };
|
return { dir, names };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { describe, expect, it } from "vitest";
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
buildWorkspaceSkillStatus,
|
buildWorkspaceSkillStatus,
|
||||||
type SkillStatusEntry,
|
type SkillStatusEntry,
|
||||||
@@ -214,6 +215,18 @@ describe("skills-cli", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("integration: loads real skills from bundled directory", () => {
|
describe("integration: loads real skills from bundled directory", () => {
|
||||||
|
let tempWorkspaceDir = "";
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
tempWorkspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-skills-test-"));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
if (tempWorkspaceDir) {
|
||||||
|
fs.rmSync(tempWorkspaceDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function resolveBundledSkillsDir(): string | undefined {
|
function resolveBundledSkillsDir(): string | undefined {
|
||||||
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const root = path.resolve(moduleDir, "..", "..");
|
const root = path.resolve(moduleDir, "..", "..");
|
||||||
@@ -231,7 +244,7 @@ describe("skills-cli", () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const report = buildWorkspaceSkillStatus("/tmp", {
|
const report = buildWorkspaceSkillStatus(tempWorkspaceDir, {
|
||||||
managedSkillsDir: "/nonexistent",
|
managedSkillsDir: "/nonexistent",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -257,7 +270,7 @@ describe("skills-cli", () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const report = buildWorkspaceSkillStatus("/tmp", {
|
const report = buildWorkspaceSkillStatus(tempWorkspaceDir, {
|
||||||
managedSkillsDir: "/nonexistent",
|
managedSkillsDir: "/nonexistent",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ const select = vi.fn();
|
|||||||
const spinner = vi.fn(() => ({ start: vi.fn(), stop: vi.fn() }));
|
const spinner = vi.fn(() => ({ start: vi.fn(), stop: vi.fn() }));
|
||||||
const isCancel = (value: unknown) => value === "cancel";
|
const isCancel = (value: unknown) => value === "cancel";
|
||||||
|
|
||||||
|
const readPackageName = vi.fn();
|
||||||
|
const readPackageVersion = vi.fn();
|
||||||
|
const resolveGlobalManager = vi.fn();
|
||||||
|
|
||||||
vi.mock("@clack/prompts", () => ({
|
vi.mock("@clack/prompts", () => ({
|
||||||
confirm,
|
confirm,
|
||||||
select,
|
select,
|
||||||
@@ -61,6 +65,16 @@ vi.mock("../process/exec.js", () => ({
|
|||||||
runCommandWithTimeout: vi.fn(),
|
runCommandWithTimeout: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("./update-cli/shared.js", async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import("./update-cli/shared.js")>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
readPackageName,
|
||||||
|
readPackageVersion,
|
||||||
|
resolveGlobalManager,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// Mock doctor (heavy module; should not run in unit tests)
|
// Mock doctor (heavy module; should not run in unit tests)
|
||||||
vi.mock("../commands/doctor.js", () => ({
|
vi.mock("../commands/doctor.js", () => ({
|
||||||
doctorCommand: vi.fn(),
|
doctorCommand: vi.fn(),
|
||||||
@@ -129,7 +143,23 @@ describe("update-cli", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
confirm.mockReset();
|
||||||
|
select.mockReset();
|
||||||
|
vi.mocked(runGatewayUpdate).mockReset();
|
||||||
|
vi.mocked(resolveOpenClawPackageRoot).mockReset();
|
||||||
|
vi.mocked(readConfigFileSnapshot).mockReset();
|
||||||
|
vi.mocked(writeConfigFile).mockReset();
|
||||||
|
vi.mocked(checkUpdateStatus).mockReset();
|
||||||
|
vi.mocked(fetchNpmTagVersion).mockReset();
|
||||||
|
vi.mocked(resolveNpmChannelTag).mockReset();
|
||||||
|
vi.mocked(runCommandWithTimeout).mockReset();
|
||||||
|
vi.mocked(runDaemonRestart).mockReset();
|
||||||
|
vi.mocked(defaultRuntime.log).mockReset();
|
||||||
|
vi.mocked(defaultRuntime.error).mockReset();
|
||||||
|
vi.mocked(defaultRuntime.exit).mockReset();
|
||||||
|
readPackageName.mockReset();
|
||||||
|
readPackageVersion.mockReset();
|
||||||
|
resolveGlobalManager.mockReset();
|
||||||
vi.mocked(resolveOpenClawPackageRoot).mockResolvedValue(process.cwd());
|
vi.mocked(resolveOpenClawPackageRoot).mockResolvedValue(process.cwd());
|
||||||
vi.mocked(readConfigFileSnapshot).mockResolvedValue(baseSnapshot);
|
vi.mocked(readConfigFileSnapshot).mockResolvedValue(baseSnapshot);
|
||||||
vi.mocked(fetchNpmTagVersion).mockResolvedValue({
|
vi.mocked(fetchNpmTagVersion).mockResolvedValue({
|
||||||
@@ -172,6 +202,9 @@ describe("update-cli", () => {
|
|||||||
signal: null,
|
signal: null,
|
||||||
killed: false,
|
killed: false,
|
||||||
});
|
});
|
||||||
|
readPackageName.mockResolvedValue("openclaw");
|
||||||
|
readPackageVersion.mockResolvedValue("1.0.0");
|
||||||
|
resolveGlobalManager.mockResolvedValue("npm");
|
||||||
setTty(false);
|
setTty(false);
|
||||||
setStdoutTty(false);
|
setStdoutTty(false);
|
||||||
});
|
});
|
||||||
@@ -241,11 +274,6 @@ describe("update-cli", () => {
|
|||||||
|
|
||||||
it("defaults to stable channel for package installs when unset", async () => {
|
it("defaults to stable channel for package installs when unset", async () => {
|
||||||
const tempDir = await createCaseDir("openclaw-update");
|
const tempDir = await createCaseDir("openclaw-update");
|
||||||
await fs.writeFile(
|
|
||||||
path.join(tempDir, "package.json"),
|
|
||||||
JSON.stringify({ name: "openclaw", version: "1.0.0" }),
|
|
||||||
"utf-8",
|
|
||||||
);
|
|
||||||
|
|
||||||
vi.mocked(resolveOpenClawPackageRoot).mockResolvedValue(tempDir);
|
vi.mocked(resolveOpenClawPackageRoot).mockResolvedValue(tempDir);
|
||||||
vi.mocked(checkUpdateStatus).mockResolvedValue({
|
vi.mocked(checkUpdateStatus).mockResolvedValue({
|
||||||
@@ -293,11 +321,6 @@ describe("update-cli", () => {
|
|||||||
|
|
||||||
it("falls back to latest when beta tag is older than release", async () => {
|
it("falls back to latest when beta tag is older than release", async () => {
|
||||||
const tempDir = await createCaseDir("openclaw-update");
|
const tempDir = await createCaseDir("openclaw-update");
|
||||||
await fs.writeFile(
|
|
||||||
path.join(tempDir, "package.json"),
|
|
||||||
JSON.stringify({ name: "openclaw", version: "1.0.0" }),
|
|
||||||
"utf-8",
|
|
||||||
);
|
|
||||||
|
|
||||||
vi.mocked(resolveOpenClawPackageRoot).mockResolvedValue(tempDir);
|
vi.mocked(resolveOpenClawPackageRoot).mockResolvedValue(tempDir);
|
||||||
vi.mocked(readConfigFileSnapshot).mockResolvedValue({
|
vi.mocked(readConfigFileSnapshot).mockResolvedValue({
|
||||||
@@ -335,11 +358,6 @@ describe("update-cli", () => {
|
|||||||
|
|
||||||
it("honors --tag override", async () => {
|
it("honors --tag override", async () => {
|
||||||
const tempDir = await createCaseDir("openclaw-update");
|
const tempDir = await createCaseDir("openclaw-update");
|
||||||
await fs.writeFile(
|
|
||||||
path.join(tempDir, "package.json"),
|
|
||||||
JSON.stringify({ name: "openclaw", version: "1.0.0" }),
|
|
||||||
"utf-8",
|
|
||||||
);
|
|
||||||
|
|
||||||
vi.mocked(resolveOpenClawPackageRoot).mockResolvedValue(tempDir);
|
vi.mocked(resolveOpenClawPackageRoot).mockResolvedValue(tempDir);
|
||||||
vi.mocked(runGatewayUpdate).mockResolvedValue({
|
vi.mocked(runGatewayUpdate).mockResolvedValue({
|
||||||
@@ -478,11 +496,7 @@ describe("update-cli", () => {
|
|||||||
it("requires confirmation on downgrade when non-interactive", async () => {
|
it("requires confirmation on downgrade when non-interactive", async () => {
|
||||||
const tempDir = await createCaseDir("openclaw-update");
|
const tempDir = await createCaseDir("openclaw-update");
|
||||||
setTty(false);
|
setTty(false);
|
||||||
await fs.writeFile(
|
readPackageVersion.mockResolvedValue("2.0.0");
|
||||||
path.join(tempDir, "package.json"),
|
|
||||||
JSON.stringify({ name: "openclaw", version: "2.0.0" }),
|
|
||||||
"utf-8",
|
|
||||||
);
|
|
||||||
|
|
||||||
vi.mocked(resolveOpenClawPackageRoot).mockResolvedValue(tempDir);
|
vi.mocked(resolveOpenClawPackageRoot).mockResolvedValue(tempDir);
|
||||||
vi.mocked(checkUpdateStatus).mockResolvedValue({
|
vi.mocked(checkUpdateStatus).mockResolvedValue({
|
||||||
@@ -520,11 +534,7 @@ describe("update-cli", () => {
|
|||||||
it("allows downgrade with --yes in non-interactive mode", async () => {
|
it("allows downgrade with --yes in non-interactive mode", async () => {
|
||||||
const tempDir = await createCaseDir("openclaw-update");
|
const tempDir = await createCaseDir("openclaw-update");
|
||||||
setTty(false);
|
setTty(false);
|
||||||
await fs.writeFile(
|
readPackageVersion.mockResolvedValue("2.0.0");
|
||||||
path.join(tempDir, "package.json"),
|
|
||||||
JSON.stringify({ name: "openclaw", version: "2.0.0" }),
|
|
||||||
"utf-8",
|
|
||||||
);
|
|
||||||
|
|
||||||
vi.mocked(resolveOpenClawPackageRoot).mockResolvedValue(tempDir);
|
vi.mocked(resolveOpenClawPackageRoot).mockResolvedValue(tempDir);
|
||||||
vi.mocked(checkUpdateStatus).mockResolvedValue({
|
vi.mocked(checkUpdateStatus).mockResolvedValue({
|
||||||
|
|||||||
@@ -75,9 +75,9 @@ describe("memory index", () => {
|
|||||||
memorySearch: {
|
memorySearch: {
|
||||||
provider: "openai",
|
provider: "openai",
|
||||||
model: "mock-embed",
|
model: "mock-embed",
|
||||||
store: { path: indexPath },
|
store: { path: indexPath, vector: { enabled: false } },
|
||||||
sync: { watch: false, onSessionStart: false, onSearch: true },
|
sync: { watch: false, onSessionStart: false, onSearch: true },
|
||||||
query: { minScore: 0 },
|
query: { minScore: 0, hybrid: { enabled: false } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
list: [{ id: "main", default: true }],
|
list: [{ id: "main", default: true }],
|
||||||
@@ -114,7 +114,7 @@ describe("memory index", () => {
|
|||||||
provider: "openai",
|
provider: "openai",
|
||||||
store: { path: indexPath },
|
store: { path: indexPath },
|
||||||
sync: { watch: false, onSessionStart: false, onSearch: true },
|
sync: { watch: false, onSessionStart: false, onSearch: true },
|
||||||
query: { minScore: 0 },
|
query: { minScore: 0, hybrid: { enabled: false } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
list: [{ id: "main", default: true }],
|
list: [{ id: "main", default: true }],
|
||||||
@@ -182,7 +182,7 @@ describe("memory index", () => {
|
|||||||
model: "mock-embed",
|
model: "mock-embed",
|
||||||
store: { path: indexPath, vector: { enabled: false } },
|
store: { path: indexPath, vector: { enabled: false } },
|
||||||
sync: { watch: false, onSessionStart: false, onSearch: false },
|
sync: { watch: false, onSessionStart: false, onSearch: false },
|
||||||
query: { minScore: 0 },
|
query: { minScore: 0, hybrid: { enabled: false } },
|
||||||
cache: { enabled: true },
|
cache: { enabled: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -276,8 +276,9 @@ describe("memory index", () => {
|
|||||||
memorySearch: {
|
memorySearch: {
|
||||||
provider: "openai",
|
provider: "openai",
|
||||||
model: "mock-embed",
|
model: "mock-embed",
|
||||||
store: { path: indexPath },
|
store: { path: indexPath, vector: { enabled: false } },
|
||||||
sync: { watch: false, onSessionStart: false, onSearch: true },
|
sync: { watch: false, onSessionStart: false, onSearch: true },
|
||||||
|
query: { minScore: 0, hybrid: { enabled: false } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
list: [{ id: "main", default: true }],
|
list: [{ id: "main", default: true }],
|
||||||
@@ -304,8 +305,9 @@ describe("memory index", () => {
|
|||||||
memorySearch: {
|
memorySearch: {
|
||||||
provider: "openai",
|
provider: "openai",
|
||||||
model: "mock-embed",
|
model: "mock-embed",
|
||||||
store: { path: indexPath },
|
store: { path: indexPath, vector: { enabled: false } },
|
||||||
sync: { watch: false, onSessionStart: false, onSearch: true },
|
sync: { watch: false, onSessionStart: false, onSearch: true },
|
||||||
|
query: { minScore: 0, hybrid: { enabled: false } },
|
||||||
extraPaths: [extraDir],
|
extraPaths: [extraDir],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -67,10 +67,10 @@ describe("memory embedding batches", () => {
|
|||||||
memorySearch: {
|
memorySearch: {
|
||||||
provider: "openai",
|
provider: "openai",
|
||||||
model: "mock-embed",
|
model: "mock-embed",
|
||||||
store: { path: indexPath },
|
store: { path: indexPath, vector: { enabled: false } },
|
||||||
chunking: { tokens: 1250, overlap: 0 },
|
chunking: { tokens: 1250, overlap: 0 },
|
||||||
sync: { watch: false, onSessionStart: false, onSearch: false },
|
sync: { watch: false, onSessionStart: false, onSearch: false },
|
||||||
query: { minScore: 0 },
|
query: { minScore: 0, hybrid: { enabled: false } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
list: [{ id: "main", default: true }],
|
list: [{ id: "main", default: true }],
|
||||||
@@ -114,10 +114,10 @@ describe("memory embedding batches", () => {
|
|||||||
memorySearch: {
|
memorySearch: {
|
||||||
provider: "openai",
|
provider: "openai",
|
||||||
model: "mock-embed",
|
model: "mock-embed",
|
||||||
store: { path: indexPath },
|
store: { path: indexPath, vector: { enabled: false } },
|
||||||
chunking: { tokens: 200, overlap: 0 },
|
chunking: { tokens: 200, overlap: 0 },
|
||||||
sync: { watch: false, onSessionStart: false, onSearch: false },
|
sync: { watch: false, onSessionStart: false, onSearch: false },
|
||||||
query: { minScore: 0 },
|
query: { minScore: 0, hybrid: { enabled: false } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
list: [{ id: "main", default: true }],
|
list: [{ id: "main", default: true }],
|
||||||
@@ -174,10 +174,10 @@ describe("memory embedding batches", () => {
|
|||||||
memorySearch: {
|
memorySearch: {
|
||||||
provider: "openai",
|
provider: "openai",
|
||||||
model: "mock-embed",
|
model: "mock-embed",
|
||||||
store: { path: indexPath },
|
store: { path: indexPath, vector: { enabled: false } },
|
||||||
chunking: { tokens: 200, overlap: 0 },
|
chunking: { tokens: 200, overlap: 0 },
|
||||||
sync: { watch: false, onSessionStart: false, onSearch: false },
|
sync: { watch: false, onSessionStart: false, onSearch: false },
|
||||||
query: { minScore: 0 },
|
query: { minScore: 0, hybrid: { enabled: false } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
list: [{ id: "main", default: true }],
|
list: [{ id: "main", default: true }],
|
||||||
@@ -209,9 +209,9 @@ describe("memory embedding batches", () => {
|
|||||||
memorySearch: {
|
memorySearch: {
|
||||||
provider: "openai",
|
provider: "openai",
|
||||||
model: "mock-embed",
|
model: "mock-embed",
|
||||||
store: { path: indexPath },
|
store: { path: indexPath, vector: { enabled: false } },
|
||||||
sync: { watch: false, onSessionStart: false, onSearch: false },
|
sync: { watch: false, onSessionStart: false, onSearch: false },
|
||||||
query: { minScore: 0 },
|
query: { minScore: 0, hybrid: { enabled: false } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
list: [{ id: "main", default: true }],
|
list: [{ id: "main", default: true }],
|
||||||
|
|||||||
@@ -52,8 +52,22 @@ function windowsPathExtensions(): string[] {
|
|||||||
return ["", ...list.filter(Boolean)];
|
return ["", ...list.filter(Boolean)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cachedHasBinaryPath: string | undefined;
|
||||||
|
let cachedHasBinaryPathExt: string | undefined;
|
||||||
|
const hasBinaryCache = new Map<string, boolean>();
|
||||||
|
|
||||||
export function hasBinary(bin: string): boolean {
|
export function hasBinary(bin: string): boolean {
|
||||||
const pathEnv = process.env.PATH ?? "";
|
const pathEnv = process.env.PATH ?? "";
|
||||||
|
const pathExt = process.platform === "win32" ? (process.env.PATHEXT ?? "") : "";
|
||||||
|
if (cachedHasBinaryPath !== pathEnv || cachedHasBinaryPathExt !== pathExt) {
|
||||||
|
cachedHasBinaryPath = pathEnv;
|
||||||
|
cachedHasBinaryPathExt = pathExt;
|
||||||
|
hasBinaryCache.clear();
|
||||||
|
}
|
||||||
|
if (hasBinaryCache.has(bin)) {
|
||||||
|
return hasBinaryCache.get(bin)!;
|
||||||
|
}
|
||||||
|
|
||||||
const parts = pathEnv.split(path.delimiter).filter(Boolean);
|
const parts = pathEnv.split(path.delimiter).filter(Boolean);
|
||||||
const extensions = process.platform === "win32" ? windowsPathExtensions() : [""];
|
const extensions = process.platform === "win32" ? windowsPathExtensions() : [""];
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
@@ -61,11 +75,13 @@ export function hasBinary(bin: string): boolean {
|
|||||||
const candidate = path.join(part, bin + ext);
|
const candidate = path.join(part, bin + ext);
|
||||||
try {
|
try {
|
||||||
fs.accessSync(candidate, fs.constants.X_OK);
|
fs.accessSync(candidate, fs.constants.X_OK);
|
||||||
|
hasBinaryCache.set(bin, true);
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
// keep scanning
|
// keep scanning
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
hasBinaryCache.set(bin, false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user