206 lines
5.5 KiB
TypeScript
206 lines
5.5 KiB
TypeScript
// Debug Logger Tests
|
|
|
|
import assert from "node:assert";
|
|
import {
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
readdirSync,
|
|
readFileSync,
|
|
writeFileSync,
|
|
} from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { test } from "node:test";
|
|
|
|
import {
|
|
debugCount,
|
|
debugLog,
|
|
debugPeak,
|
|
debugTime,
|
|
disableDebug,
|
|
enableDebug,
|
|
getDebugLogPath,
|
|
isDebugEnabled,
|
|
writeDebugSummary,
|
|
} from "../debug-logger.ts";
|
|
|
|
function createTempSfDir(): string {
|
|
const tmp = mkdtempSync(join(tmpdir(), "sf-debug-test-"));
|
|
mkdirSync(join(tmp, ".sf"), { recursive: true });
|
|
return tmp;
|
|
}
|
|
|
|
function readLogLines(logPath: string): Record<string, unknown>[] {
|
|
const content = readFileSync(logPath, "utf-8").trim();
|
|
if (!content) return [];
|
|
return content.split("\n").map((line) => JSON.parse(line));
|
|
}
|
|
|
|
test("enableDebug creates log file and sets enabled", () => {
|
|
const tmp = createTempSfDir();
|
|
enableDebug(tmp);
|
|
|
|
assert.strictEqual(isDebugEnabled(), true);
|
|
const logPath = getDebugLogPath();
|
|
assert.ok(logPath, "log path should be set");
|
|
// Normalize path separators for Windows compatibility
|
|
const normalized = logPath!.replace(/\\/g, "/");
|
|
assert.ok(
|
|
normalized.includes(".sf/debug/debug-"),
|
|
"log path should be in .sf/debug/",
|
|
);
|
|
assert.ok(logPath!.endsWith(".log"), "log path should end with .log");
|
|
|
|
disableDebug();
|
|
assert.strictEqual(isDebugEnabled(), false);
|
|
});
|
|
|
|
test("debugLog writes JSONL events", () => {
|
|
const tmp = createTempSfDir();
|
|
enableDebug(tmp);
|
|
|
|
debugLog("test-event", { foo: "bar", num: 42 });
|
|
debugLog("another-event");
|
|
|
|
const logPath = getDebugLogPath()!;
|
|
const lines = readLogLines(logPath);
|
|
|
|
assert.strictEqual(lines.length, 2);
|
|
assert.strictEqual(lines[0].event, "test-event");
|
|
assert.strictEqual((lines[0] as any).foo, "bar");
|
|
assert.strictEqual((lines[0] as any).num, 42);
|
|
assert.ok(lines[0].ts, "should have timestamp");
|
|
assert.strictEqual(lines[1].event, "another-event");
|
|
|
|
disableDebug();
|
|
});
|
|
|
|
test("debugLog is no-op when disabled", () => {
|
|
assert.strictEqual(isDebugEnabled(), false);
|
|
// Should not throw
|
|
debugLog("should-not-appear", { data: "test" });
|
|
});
|
|
|
|
test("debugTime measures elapsed time", async () => {
|
|
const tmp = createTempSfDir();
|
|
enableDebug(tmp);
|
|
|
|
const stop = debugTime("timed-op");
|
|
// Small delay to ensure measurable time
|
|
await new Promise((r) => setTimeout(r, 10));
|
|
stop({ extra: "data" });
|
|
|
|
const logPath = getDebugLogPath()!;
|
|
const lines = readLogLines(logPath);
|
|
|
|
assert.strictEqual(lines.length, 1);
|
|
assert.strictEqual(lines[0].event, "timed-op");
|
|
assert.ok(
|
|
(lines[0] as any).elapsed_ms >= 0,
|
|
"elapsed_ms should be non-negative",
|
|
);
|
|
assert.strictEqual((lines[0] as any).extra, "data");
|
|
|
|
disableDebug();
|
|
});
|
|
|
|
test("debugTime returns no-op when disabled", () => {
|
|
assert.strictEqual(isDebugEnabled(), false);
|
|
const stop = debugTime("should-not-appear");
|
|
stop({ data: "test" }); // Should not throw
|
|
});
|
|
|
|
test("debugCount increments counters", () => {
|
|
const tmp = createTempSfDir();
|
|
enableDebug(tmp);
|
|
|
|
debugCount("dispatches");
|
|
debugCount("dispatches");
|
|
debugCount("dispatches", 3);
|
|
|
|
// Counters are tested via writeDebugSummary
|
|
const logPath = writeDebugSummary()!;
|
|
const lines = readLogLines(logPath);
|
|
|
|
const summary = lines.find((l) => l.event === "debug-summary") as any;
|
|
assert.ok(summary, "should have debug-summary event");
|
|
assert.strictEqual(summary.dispatches, 5);
|
|
});
|
|
|
|
test("debugPeak tracks max values", () => {
|
|
const tmp = createTempSfDir();
|
|
enableDebug(tmp);
|
|
|
|
debugPeak("ttsrPeakBuffer", 100);
|
|
debugPeak("ttsrPeakBuffer", 500);
|
|
debugPeak("ttsrPeakBuffer", 200); // Should not overwrite 500
|
|
|
|
const logPath = writeDebugSummary()!;
|
|
const lines = readLogLines(logPath);
|
|
|
|
const summary = lines.find((l) => l.event === "debug-summary") as any;
|
|
assert.strictEqual(summary.ttsrPeakBuffer, 500);
|
|
});
|
|
|
|
test("writeDebugSummary includes all counters and disables debug", () => {
|
|
const tmp = createTempSfDir();
|
|
enableDebug(tmp);
|
|
|
|
debugCount("deriveStateCalls", 10);
|
|
debugCount("deriveStateTotalMs", 80);
|
|
debugCount("ttsrChecks", 50);
|
|
debugCount("parseRoadmapCalls", 3);
|
|
debugCount("dispatches", 2);
|
|
|
|
const logPath = writeDebugSummary()!;
|
|
assert.ok(logPath, "should return log path");
|
|
assert.strictEqual(
|
|
isDebugEnabled(),
|
|
false,
|
|
"should be disabled after summary",
|
|
);
|
|
|
|
const lines = readLogLines(logPath);
|
|
const summary = lines.find((l) => l.event === "debug-summary") as any;
|
|
assert.ok(summary);
|
|
assert.strictEqual(summary.deriveStateCalls, 10);
|
|
assert.strictEqual(summary.avgDeriveState_ms, 8);
|
|
assert.strictEqual(summary.ttsrChecks, 50);
|
|
assert.strictEqual(summary.dispatches, 2);
|
|
assert.ok(summary.totalElapsed_ms >= 0);
|
|
});
|
|
|
|
test("auto-prunes old debug logs", () => {
|
|
const tmp = createTempSfDir();
|
|
const debugDir = join(tmp, ".sf", "debug");
|
|
mkdirSync(debugDir, { recursive: true });
|
|
|
|
// Create 6 old log files
|
|
for (let i = 0; i < 6; i++) {
|
|
writeFileSync(join(debugDir, `debug-2026-01-0${i + 1}.log`), "old");
|
|
}
|
|
|
|
enableDebug(tmp);
|
|
|
|
const files = readdirSync(debugDir).filter(
|
|
(f) => f.startsWith("debug-") && f.endsWith(".log"),
|
|
);
|
|
// Should have at most MAX_DEBUG_LOGS (5) = 5 old + 1 new, but pruned to 5 total
|
|
// Actually: prunes to < 5 old, then creates 1 new = at most 5
|
|
assert.ok(
|
|
files.length <= 6,
|
|
`should have pruned old logs, got ${files.length}`,
|
|
);
|
|
|
|
disableDebug();
|
|
});
|
|
|
|
test("disableDebug returns log path", () => {
|
|
const tmp = createTempSfDir();
|
|
enableDebug(tmp);
|
|
|
|
const logPath = getDebugLogPath();
|
|
const returned = disableDebug();
|
|
assert.strictEqual(returned, logPath);
|
|
assert.strictEqual(getDebugLogPath(), null);
|
|
});
|