Files
wehub-resource-sync 426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

253 lines
8.6 KiB
JavaScript

/**
* Unit tests for the GPU vision service's pure logic: lockfile reconciliation
* (the fail-loud integrity gate), the llama.cpp version-gate boundary, port and
* arg parsing, the torn-download size floor, the setup skip-path wiring (a
* present-but-wrong blob must still fail the sha256 gate), and the readiness
* poller against a real in-process HTTP stub. The download, process launch, and
* OCR request are exercised by the real smoke run, not here.
*/
import assert from "node:assert/strict";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import http from "node:http";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import {
assertLlamaBuildSupported,
assertPlausibleSize,
formatBytes,
lockKey,
MODEL_SETS,
modelFilePath,
parseArgs,
parseLlamaBuild,
parsePort,
parsePositiveInteger,
reconcileLock,
sha256File,
waitForReady,
} from "./lib.mjs";
import { ensureFile } from "./setup.mjs";
test("model sets pin exact repo, revision, and filenames", () => {
assert.equal(MODEL_SETS.ocr.repo, "sahilchachra/Unlimited-OCR-GGUF");
assert.equal(MODEL_SETS.ocr.files.model.name, "Unlimited-OCR-Q4_K_M.gguf");
assert.equal(
MODEL_SETS.ocr.files.mmproj.name,
"mmproj-Unlimited-OCR-F16.gguf",
);
assert.match(MODEL_SETS.ocr.revision, /^[0-9a-f]{40}$/);
assert.equal(MODEL_SETS.vlm.repo, "Qwen/Qwen3-VL-4B-Instruct-GGUF");
assert.match(MODEL_SETS.vlm.revision, /^[0-9a-f]{40}$/);
});
test("lockKey encodes repo, revision, and file", () => {
assert.equal(
lockKey("ocr", "model"),
"sahilchachra/Unlimited-OCR-GGUF@0dc781d8a23f52963918ebd5b2d1b9fe61504661/Unlimited-OCR-Q4_K_M.gguf",
);
});
test("reconcileLock records a new pin when none exists", () => {
const lock = {};
const result = reconcileLock(lock, "k", { sha256: "abc" });
assert.equal(result.status, "recorded");
assert.equal(result.entry.sha256, "abc");
});
test("reconcileLock verifies a matching pin", () => {
const lock = { k: { sha256: "abc" } };
const result = reconcileLock(lock, "k", { sha256: "abc" });
assert.equal(result.status, "verified");
});
test("reconcileLock throws loud on sha256 mismatch", () => {
const lock = { k: { sha256: "expected" } };
assert.throws(
() => reconcileLock(lock, "k", { sha256: "different" }),
/sha256 mismatch/,
);
});
test("parseLlamaBuild extracts the build integer", () => {
assert.equal(
parseLlamaBuild("version: 9870 (2d973636e)\nbuilt with ..."),
9870,
);
assert.equal(parseLlamaBuild("version: 8525 (abc)"), 8525);
assert.equal(parseLlamaBuild("no version here"), null);
});
test("version gate boundary: b8524 rejected, b8525 accepted, garbage throws", () => {
assert.throws(
() => assertLlamaBuildSupported("version: 8524 (deadbeef)"),
/too old for DeepSeek-OCR/,
);
assert.equal(assertLlamaBuildSupported("version: 8525 (deadbeef)"), 8525);
assert.equal(assertLlamaBuildSupported("version: 9870 (2d973636e)"), 9870);
assert.throws(
() => assertLlamaBuildSupported("not a version banner"),
/could not parse/,
);
});
test("parsePort validates flag and env port values", () => {
assert.equal(parsePort(undefined, "X"), undefined);
assert.equal(parsePort("", "X"), undefined);
assert.equal(parsePort(" ", "X"), undefined);
assert.equal(parsePort("8080", "X"), 8080);
assert.equal(parsePort(8080, "X"), 8080);
assert.throws(() => parsePort("not-a-port", "X"), /integer port/);
assert.throws(() => parsePort("0", "X"), /integer port/);
assert.throws(() => parsePort("70000", "X"), /integer port/);
assert.throws(() => parsePort("80.5", "X"), /integer port/);
// A bare `--port` flag parses to boolean true — a usage error, never port 1.
assert.throws(() => parsePort(true, "X"), /requires a port number/);
});
test("parsePositiveInteger rejects bare value flags instead of coercing them", () => {
assert.equal(parsePositiveInteger(undefined, "--parallel", 2), 2);
assert.equal(parsePositiveInteger("", "--parallel", 2), 2);
assert.equal(parsePositiveInteger("4", "--parallel", 2), 4);
assert.equal(parsePositiveInteger(4, "--parallel", 2), 4);
assert.throws(
() => parsePositiveInteger(true, "--parallel", 2),
/requires a positive integer/,
);
assert.throws(
() => parsePositiveInteger("0", "--parallel", 2),
/positive integer/,
);
assert.throws(
() => parsePositiveInteger("1.5", "--parallel", 2),
/positive integer/,
);
});
test("assertPlausibleSize accepts near-pinned sizes and rejects torn downloads", () => {
assert.doesNotThrow(() => assertPlausibleSize(1000, 1000, "f"));
assert.doesNotThrow(() => assertPlausibleSize(960, 1000, "f"));
assert.throws(() => assertPlausibleSize(500, 1000, "f"), /truncated/);
assert.throws(() => assertPlausibleSize(2000, 1000, "f"), /truncated/);
});
test("parseArgs handles flags, key=value, key value, and positionals", () => {
const { flags, positionals } = parseArgs(
["--with-vlm", "--parallel", "4", "--port=9999", "fixture.png"],
{ booleans: ["with-vlm"] },
);
assert.equal(flags["with-vlm"], true);
assert.equal(flags.parallel, "4");
assert.equal(flags.port, "9999");
assert.deepEqual(positionals, ["fixture.png"]);
});
test("parseArgs treats a bare boolean flag before a value flag correctly", () => {
const { flags } = parseArgs(["--stop", "--vlm"], {
booleans: ["stop", "vlm"],
});
assert.equal(flags.stop, true);
assert.equal(flags.vlm, true);
});
test("formatBytes renders GiB and MiB", () => {
assert.equal(formatBytes(2 * 1024 ** 3), "2.00 GiB");
assert.equal(formatBytes(512 * 1024 ** 2), "512.0 MiB");
assert.equal(formatBytes(Number.NaN), "unknown");
});
test("ensureFile skip-if-present path still enforces the lockfile sha256 gate", async (t) => {
const tmp = await mkdtemp(path.join(os.tmpdir(), "gpu-vision-test-"));
const prevCache = process.env.ELIZA_GPU_VISION_CACHE;
process.env.ELIZA_GPU_VISION_CACHE = tmp;
t.after(async () => {
if (prevCache === undefined) delete process.env.ELIZA_GPU_VISION_CACHE;
else process.env.ELIZA_GPU_VISION_CACHE = prevCache;
await rm(tmp, { recursive: true, force: true });
});
const destPath = modelFilePath("ocr", "model");
await mkdir(path.dirname(destPath), { recursive: true });
await writeFile(destPath, "definitely not the pinned model bytes");
const actualSha = await sha256File(destPath);
const key = lockKey("ocr", "model");
// Present file whose hash contradicts the pin must fail loud — the skip
// path re-hashing is what makes "already downloaded" trustworthy.
await assert.rejects(
ensureFile({
setKey: "ocr",
role: "model",
hfBin: null,
lock: { [key]: { sha256: "0".repeat(64) } },
}),
/sha256 mismatch/,
);
// Present file matching the pin is skipped without a download.
const result = await ensureFile({
setKey: "ocr",
role: "model",
hfBin: null,
lock: { [key]: { sha256: actualSha } },
});
assert.equal(result.downloaded, false);
});
test("waitForReady resolves once the stub returns 200", async () => {
let hits = 0;
const server = http.createServer((_req, res) => {
hits += 1;
// Fail the first probe, succeed the second — exercises the poll loop.
res.statusCode = hits >= 2 ? 200 : 503;
res.end("ok");
});
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const { port } = server.address();
try {
const ready = await waitForReady(`http://127.0.0.1:${port}/health`, {
timeoutMs: 5000,
intervalMs: 20,
});
assert.equal(ready, true);
assert.ok(hits >= 2);
} finally {
server.close();
}
});
test("waitForReady throws on timeout with the last error", async () => {
// Nothing listening on this port; the poller must give up loud, not hang.
await assert.rejects(
waitForReady("http://127.0.0.1:1/health", {
timeoutMs: 200,
intervalMs: 50,
}),
/server not ready/,
);
});
test("waitForReady aborts a probe that accepts but never responds", async () => {
// A server that accepts the TCP connection and then goes silent must not
// hang the poller — the per-probe abort converts it into a poll failure.
const server = http.createServer(() => {
// never respond; the probe's AbortSignal is what ends this request
});
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const { port } = server.address();
try {
await assert.rejects(
waitForReady(`http://127.0.0.1:${port}/health`, {
timeoutMs: 400,
intervalMs: 50,
probeTimeoutMs: 100,
}),
/server not ready/,
);
} finally {
server.closeAllConnections();
server.close();
}
});