Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ function now(): string {
return new Date().toISOString();
}

const FIXTURE_NOW_MS = Date.now();

function daysAgoIso(days: number): string {
return new Date(Date.now() - days * 86_400_000).toISOString();
return new Date(FIXTURE_NOW_MS - days * 86_400_000).toISOString();
}

function registryConfig(overrides: Json = {}): Json {
Expand Down
12 changes: 10 additions & 2 deletions scripts/check-fixture-clock-races.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ import { fileURLToPath, URL } from "node:url";

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

/** Every directory whose fixtures the checker walks. `packages/loopover-engine/test` is a second,
* independently-run required suite (`npm run test --workspace @loopover/engine`) with its own convention
* for this bug shape, and is scanned alongside the root suite so a violation there fails the same way. */
export const FIXTURE_TEST_ROOTS = ["test", "packages/loopover-engine/test"] as const;

/** Helper declarations of the racy shape: takes an offset parameter AND computes from a fresh `Date.now()`. */
const OFFSET_HELPER_RE = /(?:^|\n)\s*(?:export\s+)?(?:function\s+(\w+)\s*\(([^)]*)\)|const\s+(\w+)\s*=\s*\(([^)]*)\)\s*(?::[^=]+)?=>)/g;

Expand Down Expand Up @@ -80,7 +85,10 @@ export function findFixtureClockRaces(file: string, source: string): FixtureCloc
return races;
}

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

const races = files.flatMap((file) => findFixtureClockRaces(file.slice(root.length + 1), readFileSync(file, "utf8")));
if (races.length > 0) {
Expand Down
54 changes: 53 additions & 1 deletion test/unit/check-fixture-clock-races-script.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
// here matter as much as the positive one. Reading the clock live is CORRECT wherever the passage of time is
// itself under test; the distinguishing property is whether the helper projects a fixture timestamp from an
// offset its caller varies.
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

import { describe, expect, it } from "vitest";

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

describe("findFixtureClockRaces (#9955)", () => {
it("REGRESSION: catches the exact queue-trends shape that reached CI", () => {
Expand Down Expand Up @@ -105,4 +109,52 @@ seed(dayAgo(3));
`;
expect(findFixtureClockRaces("f.test.ts", source)).toHaveLength(1);
});

it("REGRESSION (#10043): reports the daysAgoIso shape that lived in packages/loopover-engine/test unscanned", () => {
// Verbatim the pre-fix helper from packages/loopover-engine/test/issue-quality-report.test.ts, plus its
// two call sites -- the exact shape the checker never saw because main() only walked test/.
const source = `
function daysAgoIso(days: number): string {
return new Date(Date.now() - days * 86_400_000).toISOString();
}
const stale = build(daysAgoIso(60));
const ancient = build(daysAgoIso(100));
`;
expect(findFixtureClockRaces("packages/loopover-engine/test/x.test.ts", source)).toEqual([
{ file: "packages/loopover-engine/test/x.test.ts", helper: "daysAgoIso", calls: 2 },
]);
});
});

describe("FIXTURE_TEST_ROOTS and walk (#10043)", () => {
it("scans both the root suite and the engine suite", () => {
expect(FIXTURE_TEST_ROOTS).toContain("test");
expect(FIXTURE_TEST_ROOTS).toContain("packages/loopover-engine/test");
});

it("walk tolerates a missing directory instead of throwing", () => {
const out: string[] = [];
expect(() => walk(join(tmpdir(), "check-fixture-clock-races-missing-dir-fixture"), out)).not.toThrow();
expect(out).toEqual([]);
});

it("walk reaches a fixture under EITHER root the same way, mirroring main()'s per-root loop", () => {
const workspace = mkdtempSync(join(tmpdir(), "fixture-clock-races-"));
try {
const rootTestDir = join(workspace, "test", "unit");
const engineTestDir = join(workspace, "packages", "loopover-engine", "test");
mkdirSync(rootTestDir, { recursive: true });
mkdirSync(engineTestDir, { recursive: true });
writeFileSync(join(rootTestDir, "root-fixture.test.ts"), "export const rootFixture = 1;\n");
writeFileSync(join(engineTestDir, "engine-fixture.test.ts"), "export const engineFixture = 1;\n");

const found: string[] = [];
for (const testRoot of FIXTURE_TEST_ROOTS) walk(join(workspace, testRoot), found);

expect(found).toContain(join(rootTestDir, "root-fixture.test.ts"));
expect(found).toContain(join(engineTestDir, "engine-fixture.test.ts"));
} finally {
rmSync(workspace, { recursive: true, force: true });
}
});
});