test(mnemopi): reworked mnemopi test embedding setup and CI bucketing

- Removed the mnemopi Bun preload file and added explicit `./setup` imports in the changed test suites.
- Deleted the `RUN_EMBEDDINGS` flag path and shifted `MNEMOPI_NO_EMBEDDINGS` handling into per-suite before/after hooks.
- Excluded `packages/mnemopi` from the fast parallel CI test bucket with a note about missing fastembed models.
This commit is contained in:
can1357
2026-06-15 02:45:03 +02:00
parent 12950af096
commit 4952b28a8e
9 changed files with 37 additions and 25 deletions
-2
View File
@@ -1,2 +0,0 @@
[test]
preload = ["./test/setup.ts"]
+14 -1
View File
@@ -1,10 +1,23 @@
import { Database } from "bun:sqlite";
import { afterEach, describe, expect, it } from "bun:test";
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import { existsSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { BeamMemory } from "@oh-my-pi/pi-mnemopi/core/beam";
// Real embeddings (fastembed + onnxruntime-node, ~270MB) install on demand via
// `bun install` on first use. These tests never exercise embeddings — the
// consolidation dry-run touches no vectors — so disable them; otherwise the
// on-demand install hangs each test past the 5s timeout (and starves siblings
// under parallel CI).
beforeEach(() => {
process.env.MNEMOPI_NO_EMBEDDINGS = "1";
});
afterEach(() => {
delete process.env.MNEMOPI_NO_EMBEDDINGS;
});
type TempDb = { dir: string; path: string };
const tempDbs: TempDb[] = [];
@@ -8,9 +8,9 @@
import { Database } from "bun:sqlite";
import { describe, expect, it } from "bun:test";
import "./setup";
import { initBeam } from "@oh-my-pi/pi-mnemopi/core/beam";
import { Mnemopi } from "@oh-my-pi/pi-mnemopi/core/memory";
import { RUN_EMBEDDINGS } from "./setup";
const OLD_MODEL = "BAAI/bge-small-en-v1.5";
const NEW_MODEL = "intfloat/multilingual-e5-large";
@@ -47,7 +47,7 @@ function countEmbeddings(memory: Mnemopi): number {
return (memory.conn.query("SELECT COUNT(*) AS n FROM memory_embeddings").get() as { n: number }).n;
}
describe.skipIf(!RUN_EMBEDDINGS)("reconcileEmbeddingModel on store open", () => {
describe("reconcileEmbeddingModel on store open", () => {
it("wipes stale embeddings + binary vectors and re-embeds when the model changed", async () => {
const { db, ids } = seedDb(OLD_MODEL);
const memory = new Mnemopi({ db, embeddings: { model: NEW_MODEL, provider: fakeEmbed() } });
@@ -1,4 +1,5 @@
import { describe, expect, it } from "bun:test";
import "./setup";
import {
cosineSimilarity,
embed,
@@ -7,7 +8,6 @@ import {
resetEmbeddingProviderForTests,
setEmbeddingProviderForTests,
} from "@oh-my-pi/pi-mnemopi/core/embeddings";
import { RUN_EMBEDDINGS } from "./setup";
function withEnvValue<T>(key: string, value: string | undefined, fn: () => T): T {
const previous = process.env[key];
@@ -121,7 +121,7 @@ describe("multilingual embedding metadata", () => {
});
});
describe.skipIf(!RUN_EMBEDDINGS)("multilingual embedding ordering", () => {
describe("multilingual embedding ordering", () => {
it("preserves semantic ordering with a deterministic fake multilingual provider", async () => {
setEmbeddingProviderForTests({
async *embed(texts) {
@@ -1,10 +1,19 @@
import { afterEach, describe, expect, it } from "bun:test";
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { BeamMemory } from "@oh-my-pi/pi-mnemopi/core/beam";
import { Mnemopi } from "@oh-my-pi/pi-mnemopi/core/memory";
// Real embeddings (fastembed + onnxruntime-node, ~270MB) install on demand via
// `bun install` on first use. These tests assert identity filtering and FTS
// recall, not vector search, so disable embeddings; otherwise the on-demand
// install hangs each test past the 5s timeout (and starves siblings under
// parallel CI).
beforeEach(() => {
process.env.MNEMOPI_NO_EMBEDDINGS = "1";
});
const roots: string[] = [];
function tempDb(): string {
@@ -19,6 +28,7 @@ afterEach(() => {
if (root === undefined) break;
rmSync(root, { recursive: true, force: true });
}
delete process.env.MNEMOPI_NO_EMBEDDINGS;
});
describe("identity memory parity", () => {
@@ -16,6 +16,7 @@ import { describe, expect, it } from "bun:test";
import { randomBytes } from "node:crypto";
import { rmSync } from "node:fs";
import { tmpdir } from "node:os";
import "./setup";
import { cmdRemember } from "@oh-my-pi/pi-mnemopi/cli";
import { BeamMemory } from "@oh-my-pi/pi-mnemopi/core/beam";
import { Mnemopi } from "@oh-my-pi/pi-mnemopi/core/memory";
@@ -23,7 +24,6 @@ import {
type ResolvedMnemopiRuntimeOptions,
withMnemopiRuntimeOptions,
} from "@oh-my-pi/pi-mnemopi/core/runtime-options";
import { RUN_EMBEDDINGS } from "./setup";
interface EmbeddingRow {
readonly memory_id: string;
@@ -77,7 +77,7 @@ function readEmbeddings(memory: Mnemopi): EmbeddingRow[] {
.all() as EmbeddingRow[];
}
describe.skipIf(!RUN_EMBEDDINGS)("issue #1832 — embedding write/read coverage", () => {
describe("issue #1832 — embedding write/read coverage", () => {
it("remember() writes a row to memory_embeddings after flushExtractions()", async () => {
await withFakeMemory(async (memory, calls) => {
const memId = memory.remember("alpha facts about migration", { source: "test", importance: 0.5 });
@@ -1,4 +1,6 @@
import { afterEach, describe, expect, it } from "bun:test";
import { getFastembedCacheDir } from "@oh-my-pi/pi-utils";
import "./setup";
import {
available,
embed,
@@ -10,9 +12,7 @@ import {
} from "@oh-my-pi/pi-mnemopi/core/embeddings";
import { Mnemopi } from "@oh-my-pi/pi-mnemopi/core/memory";
import { withMnemopiRuntimeOptions } from "@oh-my-pi/pi-mnemopi/core/runtime-options";
import { getFastembedCacheDir } from "@oh-my-pi/pi-utils";
import packageJson from "../package.json" with { type: "json" };
import { RUN_EMBEDDINGS } from "./setup";
const ENV_KEYS = [
"NODE_ENV",
@@ -202,7 +202,7 @@ describe("optional embeddings", () => {
}
});
it.skipIf(!RUN_EMBEDDINGS)("uses a constructor-scoped embedding provider", async () => {
it("uses a constructor-scoped embedding provider", async () => {
const memory = new Mnemopi({
embeddings: {
provider: streamRows(texts => texts.map(text => [text.length, text.charCodeAt(0) || 0])),
-11
View File
@@ -62,18 +62,8 @@ class FakeLocalLlmBackend implements LlmBackend {
return { choices: [{ message: { content: this.response } }] };
}
}
export const RUN_EMBEDDINGS = Bun.env.EMBEDDINGS === "1";
beforeEach(() => {
// Real embeddings (fastembed + onnxruntime-node, ~270MB peers) install on
// demand via `bun install` on first use. Default the suite to the lightweight
// FTS-only mode; embedding-specific tests opt back in explicitly with withEnv()
// or a fake provider.
if (!RUN_EMBEDDINGS) {
process.env.MNEMOPI_NO_EMBEDDINGS = "1";
} else {
delete process.env.MNEMOPI_NO_EMBEDDINGS;
}
resetModuleStateForTests();
disableLocalLlmForTests();
});
@@ -81,5 +71,4 @@ beforeEach(() => {
afterEach(() => {
resetModuleStateForTests();
disableLocalLlmForTests();
delete process.env.MNEMOPI_NO_EMBEDDINGS;
});
+3 -1
View File
@@ -49,6 +49,9 @@ const codingAgentBucketPlans: Record<CodingAgentBucket, { label: string; paralle
// Smaller workspace packages stay separate from native/TUI/integration suites so
// their short TS suites can run together. CI still downloads the Linux x64 native
// addon before this bucket: shared utility barrels may load native-backed modules.
// mnemopi is intentionally excluded — its embedding suites depend on a ~270MB
// fastembed model absent from CI runners, so they flake/time out under the parallel
// bucket; run `bun --cwd=packages/mnemopi test` locally instead.
const fastWorkspacePackages = [
"packages/hashline",
"packages/wire",
@@ -57,7 +60,6 @@ const fastWorkspacePackages = [
"packages/ai",
"packages/snapcompact",
"packages/agent",
"packages/mnemopi",
];
// These suites cover the native package, TUI/browser-ish behavior, local servers,