Files
HealthLog/src/__tests__/linear-fit-single-implementation-guard.test.ts
T
Marc-André Bombeck 760f261a5f test(guards): count fixed-path reads through readSourceText in the source-read ratchet
The ratchet on tests that read a source file by path matched readFileSync
and readFile only, so a guard that called readSourceText with a fixed path,
directly or through a local read(rel) wrapper, read the file without the
anchors readSource demands and was never counted. The classifier now counts
both shapes, and leaves alone a readSource call with anchors and a sweep that
feeds the reader from a walk.

The twelve guards that read a fixed file that way now go through readSource
with an anchor each read asserts on, so the baseline stays where it was.
2026-10-22 19:08:15 +02:00

82 lines
2.6 KiB
TypeScript
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.
/**
* Structural guard: a least-squares line is fitted in `lib/stats/linear-fit`.
*
* Six readers fitted one by hand, four with the determinant form
* `n·Σxy − Σx·Σy` and two with centred sums, and two charts carried the same
* function twice. The matcher flags either form of the slope outside the
* implementation. Comments are stripped first; the planted cases prove the
* matcher fires.
*/
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import {
readSource,
readSourceText,
walkSourceFiles,
} from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
const ALLOWED: Record<string, string> = {
"lib/stats/linear-fit.ts": "The implementation.",
};
const HAND_ROLLED_SLOPE =
/\bn\s*\*\s*sum_?xy\s*-\s*sum_?x\s*\*\s*sum_?y\b|\bsxy\s*\/\s*sxx\b|\bsumXy\s*-\s*\(\s*sumX\s*\*\s*sumY\s*\)\s*\/\s*n\b/i;
function stripComments(text: string): string {
return text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
}
describe("one least-squares line", () => {
it("the matcher fires on a hand-rolled slope", () => {
expect(
HAND_ROLLED_SLOPE.test(
"const slope = (n * sumXY - sumX * sumY) / denominator;",
),
).toBe(true);
expect(HAND_ROLLED_SLOPE.test("const slope = sxy / sxx;")).toBe(true);
expect(
HAND_ROLLED_SLOPE.test("const sxy = sumXy - (sumX * sumY) / n;"),
).toBe(true);
expect(HAND_ROLLED_SLOPE.test("const fit = linearFit(xs, ys);")).toBe(
false,
);
});
it("no file outside the implementation fits its own", () => {
const files = walkSourceFiles(SRC, { floor: 3000 }).filter(
(file) =>
!file.startsWith("generated/") &&
!file.includes("__tests__/") &&
!/\.test\.tsx?$/.test(file),
);
const offenders = files.filter(
(file) =>
!(file in ALLOWED) &&
HAND_ROLLED_SLOPE.test(stripComments(readSourceText(join(SRC, file)))),
);
expect(offenders).toEqual([]);
// The six readers now call the implementation: a sweep that finds fewer
// call sites read the wrong tree or lost one.
const callers = files.filter((file) =>
/\b(?:linearFit|fitFromSums|trendLineByTime)\s*\(/.test(
stripComments(readSourceText(join(SRC, file))),
),
);
expect(callers.length).toBeGreaterThanOrEqual(7);
// The implementation itself matches, so the sweep read real files.
expect(
HAND_ROLLED_SLOPE.test(
stripComments(
readSource(
"src/lib/stats/linear-fit.ts",
"export function linearFit",
),
),
),
).toBe(true);
});
});