mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
test(guards): scan once in the threshold and timezone guards
Both guards timed out at five seconds under a loaded unit suite. The timezone formatter guard grepped the whole of src, the generated client included, once per scan; it now reads the tree once without the generated client and keeps a file only when a plain substring says a pattern could match. The threshold literal guard parsed every client file once per rule and twice per run; each file is parsed once, each rule's hits are found once, and a file is parsed for a rule only when it carries one of the rule's markers. A sample that the markers would skip fails the matcher case, and a planted literal still fails the allowlist case. Measured alone: the timezone guard 5.5 s to 0.45 s (its floor case 1.9 s to 2 ms), the threshold guard 4.6 s to 0.54 s (its non-empty case 1.1 s to 0 ms, reusing the hits it already found).
This commit is contained in:
1 parent
8fc4941200
commit
98a083c86e
2 files changed
+112
-71
No files matched your search
@@ -75,14 +75,23 @@ function clientFiles(): ClientFile[] {
|
||||
return out;
|
||||
}
|
||||
|
||||
const parsed = new Map<string, ts.SourceFile>();
|
||||
|
||||
/** One parse per file and source, however many rules read it. */
|
||||
function parse(name: string, source: string): ts.SourceFile {
|
||||
return ts.createSourceFile(
|
||||
name,
|
||||
source,
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
name.endsWith("x") ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
|
||||
);
|
||||
const key = `${name}\0${source}`;
|
||||
let file = parsed.get(key);
|
||||
if (file === undefined) {
|
||||
file = ts.createSourceFile(
|
||||
name,
|
||||
source,
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
name.endsWith("x") ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
|
||||
);
|
||||
parsed.set(key, file);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
/** The literal text a JSX attribute initialiser carries, if it is one. */
|
||||
@@ -218,6 +227,11 @@ function resolverImports(name: string, source: string): string[] {
|
||||
interface Rule {
|
||||
name: string;
|
||||
find: (name: string, source: string) => string[];
|
||||
/**
|
||||
* Text a file must contain for `find` to have anything to report, tested
|
||||
* before any parse. Every shape the rule matches carries one of these.
|
||||
*/
|
||||
markers: readonly string[];
|
||||
/** Sources that must produce at least one hit, proving the matcher. */
|
||||
samples: Array<{ name: string; source: string }>;
|
||||
/** File → exact hit count it may have, with the reason. */
|
||||
@@ -228,6 +242,7 @@ const RULES: Rule[] = [
|
||||
{
|
||||
name: "a literal directionSentiment",
|
||||
find: literalDirections,
|
||||
markers: ["directionSentiment"],
|
||||
samples: [
|
||||
{
|
||||
name: "sample.tsx",
|
||||
@@ -249,6 +264,7 @@ const RULES: Rule[] = [
|
||||
{
|
||||
name: "a reference line at a literal",
|
||||
find: literalLines,
|
||||
markers: [...LINE_TAGS],
|
||||
samples: [
|
||||
{
|
||||
name: "sample.tsx",
|
||||
@@ -280,12 +296,14 @@ const RULES: Rule[] = [
|
||||
{
|
||||
name: "a polarity table",
|
||||
find: polarityTables,
|
||||
markers: ["up-good", "up-bad", "UP_GOOD_", "UP_BAD_", "Sentiment"],
|
||||
samples: POLARITY_SAMPLES.map((source) => ({ name: "sample.ts", source })),
|
||||
allow: {},
|
||||
},
|
||||
{
|
||||
name: "a server resolver imported into the browser",
|
||||
find: resolverImports,
|
||||
markers: SERVER_RESOLVERS,
|
||||
samples: [
|
||||
{
|
||||
name: "sample.ts",
|
||||
@@ -299,12 +317,19 @@ const RULES: Rule[] = [
|
||||
|
||||
const FILES = clientFiles();
|
||||
|
||||
const ruleHits = new Map<Rule, Map<string, string[]>>();
|
||||
|
||||
/** Each rule's hits across the client tree, found once per run. */
|
||||
function hitsFor(rule: Rule): Map<string, string[]> {
|
||||
const out = new Map<string, string[]>();
|
||||
let out = ruleHits.get(rule);
|
||||
if (out !== undefined) return out;
|
||||
out = new Map<string, string[]>();
|
||||
for (const { rel, source } of FILES) {
|
||||
if (!rule.markers.some((marker) => source.includes(marker))) continue;
|
||||
const hits = rule.find(rel, source);
|
||||
if (hits.length > 0) out.set(rel, hits);
|
||||
}
|
||||
ruleHits.set(rule, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -321,6 +346,12 @@ describe("client threshold literal guard", () => {
|
||||
rule.find(sample.name, sample.source).length,
|
||||
`no hit on: ${sample.source}`,
|
||||
).toBeGreaterThan(0);
|
||||
// The pre-filter has to let every sample through, or it would hide
|
||||
// the shape before the matcher saw it.
|
||||
expect(
|
||||
rule.markers.some((marker) => sample.source.includes(marker)),
|
||||
`the markers skip: ${sample.source}`,
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { walkSourceFiles } from "./helpers/source-files";
|
||||
|
||||
/**
|
||||
* A formatter that renders an instant must say which calendar it renders in.
|
||||
*
|
||||
@@ -29,6 +30,54 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
const REPO_ROOT = resolve(__dirname, "../..");
|
||||
|
||||
/** A source file under `src/`, by its repo-relative path, and its text. */
|
||||
interface SourceFile {
|
||||
file: string;
|
||||
source: string;
|
||||
}
|
||||
|
||||
/** Whatever any scan below looks for, as plain substrings. */
|
||||
const MARKERS = [
|
||||
"Intl.DateTimeFormat",
|
||||
".toLocaleDateString",
|
||||
".toLocaleTimeString",
|
||||
"getDateTimeFormat",
|
||||
"fmt.date",
|
||||
];
|
||||
|
||||
let candidates: SourceFile[] | null = null;
|
||||
|
||||
/**
|
||||
* Every source file that mentions one of the markers, read once per run.
|
||||
*
|
||||
* Each scan used to start its own `grep -r` over the whole of `src/`,
|
||||
* generated client included, five times per run; under a loaded unit suite
|
||||
* that alone ran past the 5 s test timeout. The tree is walked once now, the
|
||||
* generated Prisma client left out (it renders no dates, and no scan has ever
|
||||
* matched in it), and each file is kept only when a plain substring test says
|
||||
* a pattern could match, before any regular expression runs.
|
||||
*/
|
||||
function candidateFiles(): SourceFile[] {
|
||||
candidates ??= walkSourceFiles(resolve(REPO_ROOT, "src"), { floor: 3000 })
|
||||
.filter((rel) => !rel.startsWith("generated/"))
|
||||
.map((rel) => ({
|
||||
file: `src/${rel}`,
|
||||
source: readFileSync(resolve(REPO_ROOT, "src", rel), "utf8"),
|
||||
}))
|
||||
.filter(({ source }) => MARKERS.some((marker) => source.includes(marker)));
|
||||
return candidates;
|
||||
}
|
||||
|
||||
/** The candidate files a pattern matches, test files left in. */
|
||||
function filesMatching(pattern: RegExp, under = "src/"): SourceFile[] {
|
||||
return candidateFiles().filter(
|
||||
({ file, source }) => file.startsWith(under) && pattern.test(source),
|
||||
);
|
||||
}
|
||||
|
||||
const ANY_FORMATTER =
|
||||
/new Intl\.DateTimeFormat|\.toLocale(Date|Time)String\(|getDateTimeFormat\(/;
|
||||
|
||||
/**
|
||||
* Bare formatter sites that are correct as they stand. Keep this short: it is
|
||||
* an inventory of exceptions, not a parking space. Every entry says why, and
|
||||
@@ -97,26 +146,12 @@ function topLevelArgCount(args: string): number {
|
||||
}
|
||||
|
||||
function bareFormatterSites(): Array<{ file: string; line: number }> {
|
||||
const listed = execFileSync(
|
||||
"grep",
|
||||
[
|
||||
"-rlE",
|
||||
"new Intl\\.DateTimeFormat|\\.toLocale(Date|Time)String\\(|getDateTimeFormat\\(",
|
||||
"src",
|
||||
"--include=*.ts",
|
||||
"--include=*.tsx",
|
||||
],
|
||||
{ cwd: REPO_ROOT, encoding: "utf8" },
|
||||
)
|
||||
.trim()
|
||||
.split("\n")
|
||||
.filter(Boolean)
|
||||
const listed = filesMatching(ANY_FORMATTER)
|
||||
// Tests pin instants on purpose and are not user-facing surfaces.
|
||||
.filter((f) => !f.includes("__tests__"));
|
||||
.filter(({ file }) => !file.includes("__tests__"));
|
||||
|
||||
const hits: Array<{ file: string; line: number }> = [];
|
||||
for (const file of listed) {
|
||||
const source = readFileSync(resolve(REPO_ROOT, file), "utf8");
|
||||
for (const { file, source } of listed) {
|
||||
for (const pattern of FORMATTER_PATTERNS) {
|
||||
pattern.lastIndex = 0;
|
||||
for (let m = pattern.exec(source); m !== null; m = pattern.exec(source)) {
|
||||
@@ -158,21 +193,7 @@ describe("date formatters name the calendar they render in", () => {
|
||||
it("finds formatter sites at all (the guard must not pass vacuously)", () => {
|
||||
// If this drops to zero the grep has stopped matching and every assertion
|
||||
// below became free.
|
||||
const all = execFileSync(
|
||||
"grep",
|
||||
[
|
||||
"-rlE",
|
||||
"new Intl\\.DateTimeFormat|\\.toLocale(Date|Time)String\\(|getDateTimeFormat\\(",
|
||||
"src",
|
||||
"--include=*.ts",
|
||||
"--include=*.tsx",
|
||||
],
|
||||
{ cwd: REPO_ROOT, encoding: "utf8" },
|
||||
)
|
||||
.trim()
|
||||
.split("\n")
|
||||
.filter(Boolean);
|
||||
expect(all.length).toBeGreaterThan(5);
|
||||
expect(filesMatching(ANY_FORMATTER).length).toBeGreaterThan(5);
|
||||
});
|
||||
|
||||
it("has no unexplained formatter without an explicit timeZone", () => {
|
||||
@@ -241,26 +262,17 @@ describe("date formatters name the calendar they render in", () => {
|
||||
describe("components format dates through useFormatters", () => {
|
||||
const CALL = /\bgetDateTimeFormat\s*\(/g;
|
||||
|
||||
let scanned: { offenders: string[]; calls: number } | null = null;
|
||||
|
||||
function scan() {
|
||||
const files = execFileSync(
|
||||
"grep",
|
||||
[
|
||||
"-rlE",
|
||||
"getDateTimeFormat\\s*\\(",
|
||||
"src/components",
|
||||
"--include=*.ts",
|
||||
"--include=*.tsx",
|
||||
],
|
||||
{ cwd: REPO_ROOT, encoding: "utf8" },
|
||||
)
|
||||
.trim()
|
||||
.split("\n")
|
||||
.filter(Boolean)
|
||||
.filter((f) => !f.includes("__tests__"));
|
||||
if (scanned) return scanned;
|
||||
const files = filesMatching(
|
||||
/getDateTimeFormat\s*\(/,
|
||||
"src/components/",
|
||||
).filter(({ file }) => !file.includes("__tests__"));
|
||||
const offenders: string[] = [];
|
||||
let calls = 0;
|
||||
for (const file of files) {
|
||||
const source = readFileSync(resolve(REPO_ROOT, file), "utf8");
|
||||
for (const { file, source } of files) {
|
||||
CALL.lastIndex = 0;
|
||||
for (let m = CALL.exec(source); m; m = CALL.exec(source)) {
|
||||
const lineStart = source.lastIndexOf("\n", m.index) + 1;
|
||||
@@ -275,7 +287,8 @@ describe("components format dates through useFormatters", () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
return { offenders, calls };
|
||||
scanned = { offenders, calls };
|
||||
return scanned;
|
||||
}
|
||||
|
||||
it("still sees the cache's calls in components (the matcher matches)", () => {
|
||||
@@ -317,20 +330,16 @@ describe("a stated date is formatted on its calendar date", () => {
|
||||
const CALENDAR_CALL = /\bfmt\.dateShortSmartCalendar\s*\(/g;
|
||||
const NOON_ANCHOR = /T12:00:00/;
|
||||
|
||||
let scanned: { offenders: string[]; calendarCalls: number } | null = null;
|
||||
|
||||
function scan() {
|
||||
const files = execFileSync(
|
||||
"grep",
|
||||
["-rlE", "fmt\\.date", "src", "--include=*.ts", "--include=*.tsx"],
|
||||
{ cwd: REPO_ROOT, encoding: "utf8" },
|
||||
)
|
||||
.trim()
|
||||
.split("\n")
|
||||
.filter(Boolean)
|
||||
.filter((f) => !f.includes("__tests__"));
|
||||
if (scanned) return scanned;
|
||||
const files = filesMatching(/fmt\.date/).filter(
|
||||
({ file }) => !file.includes("__tests__"),
|
||||
);
|
||||
const offenders: string[] = [];
|
||||
let calendarCalls = 0;
|
||||
for (const file of files) {
|
||||
const source = readFileSync(resolve(REPO_ROOT, file), "utf8");
|
||||
for (const { file, source } of files) {
|
||||
calendarCalls += (source.match(CALENDAR_CALL) ?? []).length;
|
||||
if (/const fmt = makeBucketLabelFormatters\(/.test(source)) continue;
|
||||
ZONED_CALL.lastIndex = 0;
|
||||
@@ -343,7 +352,8 @@ describe("a stated date is formatted on its calendar date", () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
return { offenders, calendarCalls };
|
||||
scanned = { offenders, calendarCalls };
|
||||
return scanned;
|
||||
}
|
||||
|
||||
it("finds the calendar formatter in use (the matcher still matches)", () => {
|
||||
|
||||
Reference in new issue
Block a user