import { QuickJSWorker } from "../dist"; const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); describe("Stats API compatibility", () => { let worker: QuickJSWorker; beforeEach(() => { worker = new QuickJSWorker(); }); afterEach(async () => { if (worker && !worker.isClosed()) await worker.close(); }); it("exposes stats.lastExecution after evalSync and eval", async () => { expect(worker.stats.lastExecution).toBeNull(); expect(worker.evalSync("41 + 1")).toBe(42); expect(worker.stats.lastExecution).toMatchObject({ cpuTimeMs: expect.any(Number), evalTimeMs: expect.any(Number), }); await expect(worker.eval("2 + 3")).resolves.toBe(5); expect(worker.stats.lastExecution).toMatchObject({ cpuTimeMs: expect.any(Number), evalTimeMs: expect.any(Number), }); }); it("tracks active async operations", async () => { await worker.global.set("nodeDelayForStats", async () => { await sleep(80); return 1; }); expect(worker.stats.activeOps).toBe(0); const pending = worker.eval(` (async () => { await nodeDelayForStats(); return 42; })() `); await sleep(10); expect(worker.stats.activeOps).toBeGreaterThan(0); await expect(pending).resolves.toBe(42); expect(worker.stats.activeOps).toBe(0); }); it("returns per-thread cpu stats in a sane range", async () => { await expect(worker.eval(` (() => { const end = Date.now() + 40; while (Date.now() < end) {} return 1; })() `)).resolves.toBe(1); const cpu = await worker.stats.cpu({ measureMs: 50 }); expect(cpu.measureMs).toBe(50); expect(typeof cpu.cpuTimeMs).toBe("number"); expect(cpu.cpuTimeMs).toBeGreaterThanOrEqual(0); expect(cpu.usagePercentage).toBeGreaterThanOrEqual(0); expect(cpu.usagePercentage).toBeLessThanOrEqual(100); }); it("exposes rates, latency, eventLoopLag, totals, and reset", async () => { await expect(worker.eval("1 + 1")).resolves.toBe(2); await expect(worker.global.get("Math.PI")).resolves.toBeCloseTo(Math.PI); const handle = await worker.handle.get("Math"); await expect(handle.get("PI")).resolves.toBeCloseTo(Math.PI); await handle.dispose(); const seen: any[] = []; worker.on("message", (msg) => seen.push(msg)); await worker.eval(`postMessage({ ok: 1 })`); await sleep(20); worker.postMessage({ ping: true }); const rates = await worker.stats.rates({ windowMs: 1000 }); expect(rates.windowMs).toBe(1000); expect(rates.evalPerSec).toBeGreaterThan(0); expect(rates.handlePerSec).toBeGreaterThan(0); expect(rates.globalPerSec).toBeGreaterThan(0); expect(rates.messagesPerSec).toBeGreaterThan(0); const latency = await worker.stats.latency({ windowMs: 1000 }); expect(latency.windowMs).toBe(1000); expect(latency.count).toBeGreaterThan(0); expect(latency.avgMs).toBeGreaterThanOrEqual(0); expect(latency.maxMs).toBeGreaterThanOrEqual(latency.avgMs); const lag = await worker.stats.eventLoopLag({ measureMs: 20 }); expect(lag.measureMs).toBe(20); expect(lag.lagMs).toBeGreaterThanOrEqual(0); const totals = worker.stats.totals; expect(totals.ops).toBeGreaterThan(0); expect(totals.eval).toBeGreaterThan(0); expect(totals.handle).toBeGreaterThan(0); expect(totals.global).toBeGreaterThan(0); expect(totals.messagesOut).toBeGreaterThan(0); expect(totals.messagesIn).toBeGreaterThan(0); expect(seen).toContainEqual({ ok: 1 }); worker.stats.reset(); expect(worker.stats.totals).toEqual({ ops: 0, errors: 0, eval: 0, handle: 0, global: 0, module: 0, other: 0, messagesOut: 0, messagesIn: 0, bytesOut: 0, bytesIn: 0, }); }); it("keeps control ops responsive during message flood", async () => { await worker.eval(` globalThis.messageCount = 0; on("message", () => { messageCount += 1; }); `); let stop = false; const flood = (async () => { while (!stop) { worker.postMessages(new Array(200).fill(0).map((_, i) => ({ i, ok: true }))); await sleep(0); } })(); const started = Date.now(); const [mem, value] = await Promise.all([ worker.memory(), worker.eval("40 + 2"), ]); const elapsed = Date.now() - started; stop = true; await flood; expect(mem).toBeDefined(); expect(value).toBe(42); expect(elapsed).toBeLessThan(1000); }); });