Skip to content

Commit 4e9cdbe

Browse files
authored
ci(fixture-clock-races): scan packages/loopover-engine/test too (#10130)
The checker's main() only ever walked test/, so the same helper-re-reads- the-clock shape it exists to catch could still land in the engine package's independently-run suite and auto-close a correct PR on timing alone. Widen the scan to a named FIXTURE_TEST_ROOTS list (mirroring check-dead-exports.ts's REFERENCE_ROOTS pattern) and fix the one violation that was already sitting there. Co-authored-by: bitfathers94 <237535319+bitfathers94@users.noreply.github.com>
1 parent 1d2b142 commit 4e9cdbe

3 files changed

Lines changed: 66 additions & 4 deletions

File tree

packages/loopover-engine/test/issue-quality-report.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ function now(): string {
1616
return new Date().toISOString();
1717
}
1818

19+
const FIXTURE_NOW_MS = Date.now();
20+
1921
function daysAgoIso(days: number): string {
20-
return new Date(Date.now() - days * 86_400_000).toISOString();
22+
return new Date(FIXTURE_NOW_MS - days * 86_400_000).toISOString();
2123
}
2224

2325
function registryConfig(overrides: Json = {}): Json {

scripts/check-fixture-clock-races.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ import { fileURLToPath, URL } from "node:url";
3232

3333
export type FixtureClockRace = { file: string; helper: string; calls: number };
3434

35+
/** Every directory whose fixtures the checker walks. `packages/loopover-engine/test` is a second,
36+
* independently-run required suite (`npm run test --workspace @loopover/engine`) with its own convention
37+
* for this bug shape, and is scanned alongside the root suite so a violation there fails the same way. */
38+
export const FIXTURE_TEST_ROOTS = ["test", "packages/loopover-engine/test"] as const;
39+
3540
/** Helper declarations of the racy shape: takes an offset parameter AND computes from a fresh `Date.now()`. */
3641
const OFFSET_HELPER_RE = /(?:^|\n)\s*(?:export\s+)?(?:function\s+(\w+)\s*\(([^)]*)\)|const\s+(\w+)\s*=\s*\(([^)]*)\)\s*(?::[^=]+)?=>)/g;
3742

@@ -80,7 +85,10 @@ export function findFixtureClockRaces(file: string, source: string): FixtureCloc
8085
return races;
8186
}
8287

83-
function walk(dir: string, out: string[]): void {
88+
/** Recursively collects `*.test.ts` files under `dir` into `out`, tolerating a missing directory (a checkout
89+
* without the engine package must still run rather than crash). Exported so the roots loop's tolerance and
90+
* reach can be asserted directly rather than only through `main()`'s unmockable filesystem paths. */
91+
export function walk(dir: string, out: string[]): void {
8492
let entries: ReadonlyArray<{ name: string; isDirectory(): boolean }>;
8593
try {
8694
entries = readdirSync(dir, { withFileTypes: true });
@@ -101,7 +109,7 @@ function walk(dir: string, out: string[]): void {
101109
function main(): void {
102110
const root = join(fileURLToPath(new URL(".", import.meta.url)), "..");
103111
const files: string[] = [];
104-
walk(join(root, "test"), files);
112+
for (const testRoot of FIXTURE_TEST_ROOTS) walk(join(root, testRoot), files);
105113

106114
const races = files.flatMap((file) => findFixtureClockRaces(file.slice(root.length + 1), readFileSync(file, "utf8")));
107115
if (races.length > 0) {

test/unit/check-fixture-clock-races-script.test.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
// here matter as much as the positive one. Reading the clock live is CORRECT wherever the passage of time is
55
// itself under test; the distinguishing property is whether the helper projects a fixture timestamp from an
66
// offset its caller varies.
7+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
8+
import { tmpdir } from "node:os";
9+
import { join } from "node:path";
10+
711
import { describe, expect, it } from "vitest";
812

9-
import { findFixtureClockRaces } from "../../scripts/check-fixture-clock-races";
13+
import { FIXTURE_TEST_ROOTS, findFixtureClockRaces, walk } from "../../scripts/check-fixture-clock-races";
1014

1115
describe("findFixtureClockRaces (#9955)", () => {
1216
it("REGRESSION: catches the exact queue-trends shape that reached CI", () => {
@@ -105,4 +109,52 @@ seed(dayAgo(3));
105109
`;
106110
expect(findFixtureClockRaces("f.test.ts", source)).toHaveLength(1);
107111
});
112+
113+
it("REGRESSION (#10043): reports the daysAgoIso shape that lived in packages/loopover-engine/test unscanned", () => {
114+
// Verbatim the pre-fix helper from packages/loopover-engine/test/issue-quality-report.test.ts, plus its
115+
// two call sites -- the exact shape the checker never saw because main() only walked test/.
116+
const source = `
117+
function daysAgoIso(days: number): string {
118+
return new Date(Date.now() - days * 86_400_000).toISOString();
119+
}
120+
const stale = build(daysAgoIso(60));
121+
const ancient = build(daysAgoIso(100));
122+
`;
123+
expect(findFixtureClockRaces("packages/loopover-engine/test/x.test.ts", source)).toEqual([
124+
{ file: "packages/loopover-engine/test/x.test.ts", helper: "daysAgoIso", calls: 2 },
125+
]);
126+
});
127+
});
128+
129+
describe("FIXTURE_TEST_ROOTS and walk (#10043)", () => {
130+
it("scans both the root suite and the engine suite", () => {
131+
expect(FIXTURE_TEST_ROOTS).toContain("test");
132+
expect(FIXTURE_TEST_ROOTS).toContain("packages/loopover-engine/test");
133+
});
134+
135+
it("walk tolerates a missing directory instead of throwing", () => {
136+
const out: string[] = [];
137+
expect(() => walk(join(tmpdir(), "check-fixture-clock-races-missing-dir-fixture"), out)).not.toThrow();
138+
expect(out).toEqual([]);
139+
});
140+
141+
it("walk reaches a fixture under EITHER root the same way, mirroring main()'s per-root loop", () => {
142+
const workspace = mkdtempSync(join(tmpdir(), "fixture-clock-races-"));
143+
try {
144+
const rootTestDir = join(workspace, "test", "unit");
145+
const engineTestDir = join(workspace, "packages", "loopover-engine", "test");
146+
mkdirSync(rootTestDir, { recursive: true });
147+
mkdirSync(engineTestDir, { recursive: true });
148+
writeFileSync(join(rootTestDir, "root-fixture.test.ts"), "export const rootFixture = 1;\n");
149+
writeFileSync(join(engineTestDir, "engine-fixture.test.ts"), "export const engineFixture = 1;\n");
150+
151+
const found: string[] = [];
152+
for (const testRoot of FIXTURE_TEST_ROOTS) walk(join(workspace, testRoot), found);
153+
154+
expect(found).toContain(join(rootTestDir, "root-fixture.test.ts"));
155+
expect(found).toContain(join(engineTestDir, "engine-fixture.test.ts"));
156+
} finally {
157+
rmSync(workspace, { recursive: true, force: true });
158+
}
159+
});
108160
});

0 commit comments

Comments
 (0)