Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
PROFILER_CODES,
type ProfilerError,
} from "../error-codes/error-codes.js";
import type { ProfileEntry, ProfileMark } from "../types/profiler.types.js";
import { ProfileReporter } from "./reporter.js";
function entry(
label: string,
duration: number,
category?: string
): ProfileEntry {
return { label, startTime: 0, endTime: duration, duration, category };
}
describe("ProfileReporter", () => {
it("throws INVALID_OPTIONS for non-empty bad percentiles", {
timeout: 5000,
}, () => {
try {
new ProfileReporter({ percentiles: [200] });
throw new Error("expected throw");
} catch (err) {
expect((err as ProfilerError).code).toBe(
PROFILER_CODES.INVALID_OPTIONS
);
}
});
it("throws INVALID_OPTIONS for empty percentiles", {
timeout: 5000,
}, () => {
try {
new ProfileReporter({ percentiles: [] });
throw new Error("expected throw");
} catch (err) {
expect((err as ProfilerError).code).toBe(
PROFILER_CODES.INVALID_OPTIONS
);
}
});
it("computes percentiles", { timeout: 5000 }, () => {
const r = new ProfileReporter({
percentiles: [50, 90],
clock: () => 0,
});
const stats = r.computeStats([10, 20, 30, 40, 50]);
expect(stats.count).toBe(5);
expect(stats.min).toBe(10);
expect(stats.max).toBe(50);
expect(stats.mean).toBe(30);
expect(stats.median).toBe(30);
expect(stats.percentiles[50]).toBe(30);
expect(stats.percentiles[90]).toBeGreaterThan(30);
});
it("handles empty entries", { timeout: 5000 }, () => {
const r = new ProfileReporter();
const stats = r.computeStats([]);
expect(stats).toEqual({
count: 0,
min: 0,
max: 0,
mean: 0,
median: 0,
percentiles: {},
totalDuration: 0,
});
});
it("groups by category and uncategorized falls back", {
timeout: 5000,
}, () => {
const r = new ProfileReporter({ clock: () => 0 });
const report = r.generateReport([
entry("a", 10, "render"),
entry("b", 20, "render"),
entry("c", 30),
]);
expect(report.byCategory.render?.count).toBe(2);
expect(report.byCategory.uncategorized?.count).toBe(1);
});
it("flags alerts above threshold", { timeout: 5000 }, () => {
const r = new ProfileReporter({ alertThreshold: 20, clock: () => 0 });
const report = r.generateReport([entry("ok", 10), entry("bad", 30)]);
expect(report.alerts).toHaveLength(1);
expect(report.alerts[0]?.label).toBe("bad");
});
it("formatReport produces stable text", { timeout: 5000 }, () => {
const r = new ProfileReporter({ clock: () => 0, alertThreshold: 1 });
const report = r.generateReport([entry("slow", 10)]);
const text = r.formatReport(report);
expect(text).toContain("Profile Report");
expect(text).toContain("Alerts (1)");
expect(text).toContain("slow");
});
it("formatReport with no alerts skips alerts section", {
timeout: 5000,
}, () => {
const r = new ProfileReporter({ clock: () => 0 });
const text = r.formatReport(r.generateReport([entry("ok", 10)]));
expect(text).not.toContain("Alerts");
});
it("computePercentile handles single-element exact index", {
timeout: 5000,
}, () => {
const r = new ProfileReporter();
const stats = r.computeStats([42]);
expect(stats.median).toBe(42);
});
it("ignores entries without duration", { timeout: 5000 }, () => {
const r = new ProfileReporter();
const report = r.generateReport(
[{ label: "still", startTime: 0 }, entry("ok", 10)],
[{ label: "mark", timestamp: 1 }] as readonly ProfileMark[]
);
expect(report.stats.count).toBe(1);
expect(report.marks).toHaveLength(1);
});
});