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