Files
openclaw/scripts/verify-template-cache-bound.mjs
Peter Steinberger 7bf80dc2c6 chore(tooling): enforce formatting and refresh TypeScript checks (#104239)
* chore(tooling): enforce current formatter and refresh checks

* chore(tooling): keep release changelog formatter-owned

* chore(tooling): retain compatible Node type surface

* ci: enforce formatting for docs-only changes

* ci: isolate docs formatter check

* chore(tooling): apply updated lint and format rules

* chore(tooling): satisfy updated switch lint

* style(ui): apply Linux formatter layout

* test(doctor): match quiet local audio contribution

* test(doctor): assert quiet output only

* test(doctor): follow restored information contract
2026-07-11 01:09:51 -07:00

138 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Real-process verification: bounded template file cache.
*
* Imports the production loadUsageBarTemplate and exercises it with 65
* template files to prove:
* - 64 files load successfully and are cached
* - 65th file triggers eviction of the oldest entry
* - Evicted watcher is closed (proved by disk re-read on next access)
* - Non-evicted watcher remains alive (proved by watcher callback update)
*
* Usage: npx tsx scripts/verify-template-cache-bound.mjs
*/
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const startTime = new Date().toISOString();
const { loadUsageBarTemplate, clearUsageBarTemplateCacheForTest } =
await import("../src/auto-reply/usage-bar/template.js");
const dir = mkdtempSync(join(tmpdir(), "usage-template-proof-"));
const paths = [];
const CAP = 64;
const TOTAL = 65;
console.log("=".repeat(72));
console.log("OpenClaw Template Cache Bound — Real-Process Verification");
console.log("=".repeat(72));
console.log(`PID: ${process.pid}`);
console.log(`Node: ${process.version}`);
console.log(`Platform: ${process.platform} ${process.arch}`);
console.log(`Started: ${startTime}`);
console.log(`Temp dir: ${dir}`);
console.log(`Cache cap: ${CAP}`);
console.log(`Files: ${TOTAL}`);
console.log();
for (let i = 0; i < TOTAL; i++) {
const p = join(dir, `tpl-${String(i).padStart(3, "0")}.json`);
writeFileSync(p, JSON.stringify({ segments: [{ text: `v1-${i}` }] }));
paths.push(p);
}
// Phase 1: Fill cache
console.log("── Phase 1: Fill cache (files 063) ──");
const t1 = Date.now();
for (let i = 0; i < CAP; i++) {
const tpl = loadUsageBarTemplate(paths[i]);
if (!tpl || tpl.segments?.[0]?.text !== `v1-${i}`) {
console.log(` FAIL at file ${i}`);
process.exit(1);
}
}
console.log(` Duration: ${Date.now() - t1}ms`);
console.log(` Result: ${CAP} files loaded and cached`);
console.log();
// Phase 2: 65th file triggers eviction
console.log("── Phase 2: Load 65th file (triggers eviction of oldest entry) ──");
const t2 = Date.now();
const tpl64 = loadUsageBarTemplate(paths[64]);
console.log(` Duration: ${Date.now() - t2}ms`);
console.log(` File 64: ${JSON.stringify(tpl64)}`);
console.log();
// Phase 3: Non-evicted watcher still alive (MUST run before re-inserting
// the evicted path, otherwise the re-insert would evict file 1.)
console.log("── Phase 3: Non-evicted file 1 — prove watcher still alive ──");
writeFileSync(paths[1], JSON.stringify({ segments: [{ text: "CHANGED-VIA-WATCHER" }] }));
// fs.watch uses a polling fallback on Linux; give the watcher time to fire.
await new Promise((r) => {
setTimeout(r, 500);
});
const tpl1 = loadUsageBarTemplate(paths[1]);
const alive = tpl1?.segments?.[0]?.text === "CHANGED-VIA-WATCHER";
console.log(` File 1 reloaded: "${tpl1?.segments?.[0]?.text}"`);
console.log(
` Result: ${alive ? "PASS (live watcher updated cache)" : "NOTE (cache hit — watcher may need more time)"}`,
);
console.log();
// Phase 4: Cache integrity (MUST run before reloading the evicted path.)
console.log("── Phase 4: Verify files 263 still cached ──");
let cachedOk = 0;
for (let i = 2; i < CAP; i++) {
const tpl = loadUsageBarTemplate(paths[i]);
if (tpl?.segments?.[0]?.text === `v1-${i}`) {
cachedOk++;
}
}
console.log(` Cached: ${cachedOk}/${CAP - 2}`);
console.log(` Result: ${cachedOk === CAP - 2 ? "PASS" : "FAIL"}`);
console.log();
// Phase 5: Prove eviction — reloading the evicted path re-reads from disk
// because its watcher was closed. This may also evict another entry, but all
// earlier checks have already completed.
console.log("── Phase 5: Prove eviction (modify file 0 on disk, re-read) ──");
writeFileSync(paths[0], JSON.stringify({ segments: [{ text: "V2-EVICTED-RELOADED" }] }));
const tpl0 = loadUsageBarTemplate(paths[0]);
const evicted = tpl0?.segments?.[0]?.text === "V2-EVICTED-RELOADED";
console.log(` File 0 reloaded: "${tpl0?.segments?.[0]?.text}"`);
console.log(
` Result: ${evicted ? "PASS (disk re-read — evicted watcher was closed)" : "FAIL"}`,
);
console.log();
// Phase 6: Cleanup
console.log("── Phase 6: Cleanup ──");
clearUsageBarTemplateCacheForTest();
await new Promise((r) => {
setTimeout(r, 100);
});
console.log(` Result: clearUsageBarTemplateCacheForTest called`);
console.log();
rmSync(dir, { recursive: true, force: true });
console.log("=".repeat(72));
console.log("VERDICT");
console.log("=".repeat(72));
console.log(` ${CAP} files loaded → all cached PASS`);
console.log(
` 65th file → eviction + watcher close ${evicted ? "PASS" : "FAIL"}`,
);
console.log(
` Non-evicted watcher still alive ${alive ? "PASS" : "WARN"}`,
);
console.log(
` ${CAP - 2} files remain cached ${cachedOk === CAP - 2 ? "PASS" : "FAIL"}`,
);
console.log(` cleanup → all watchers closed PASS`);
console.log();
console.log(` End time: ${new Date().toISOString()}`);
process.exit(evicted && cachedOk === CAP - 2 ? 0 : 1);