GitNexus/gitnexus/test/unit/node-workspace-scan-determinism.test.ts
Abhinav Pandey 79f210c5b5
fix(go, workspace): resolve test siblings and tighten package discovery (#3191)
* fix(go): resolve test helpers through package sibling tables

* fix(workspace): discover source entries from scoped static configuration

* Address PR review feedback (#3191)

- Align sibling comments with the no-bare-name partition and drop the stale same-dir fallback claim.
- Pin `_test.go` dot-import wildcard augmentation so a revert to nonTestFiles cannot stay green.

Note: pre-existing failure in worker-pool startup crashes in the full vitest suite not addressed by this PR.
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: tighten workspace discovery and index Go sibling bindings

Skip leftover test/ workspace roots and extra Vite configs. Publish
same-package Go names from per-package indexes instead of pairing every file.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-10 12:22:12 +01:00

171 lines
6.7 KiB
TypeScript

/**
* M7 — `findWorkspaceRoots`'s workspace-ROOT scan (nested `pnpm-workspace.yaml`
* / `lerna.json` / `package.json#workspaces` discovery inside
* `node-workspace-packages.ts`): sorted `readdir` (deterministic) and a
* 50,000-directory cap that warns instead of silently truncating.
*
* Real disk I/O can't cheaply exercise a 50,000-directory tree, so `readdir` /
* `readFile` are intercepted for one virtual root and everything else falls
* through to the real `fs/promises` (same Proxy-over-`importOriginal` shape as
* `node-workspace-repo-root-race.test.ts`), so unrelated code (the logger's
* own init, etc.) is unaffected.
*
* Two things are proven, deliberately kept SEPARATE because they can't both be
* observed through the same signal: a package admitted through a NESTED
* root's declaration sits one directory level BELOW the declaring directory,
* and the wide level needed to trip the 50k root-scan cap already exceeds the
* SEPARATE 20k-directory package-scan cap — so a package nested under the
* wide, capped subtree is not a reachable signal for either scan.
*
* 1. The cap trips and warns (`logger.warn`, captured via `_captureLogger`)
* rather than hanging or throwing, however the synthetic `readdir` order
* is shuffled.
* 2. A package admitted through a path that does NOT depend on the wide,
* capped subtree (`members/only`, admitted directly by the repo root's
* OWN declared `workspaces: ['members/*']`) still resolves correctly and
* IDENTICALLY no matter how the wide subtree's `readdir` order is
* shuffled — the capped/warned branch does not corrupt or drop unrelated,
* already-resolvable results.
*/
import { describe, it, expect, vi } from 'vitest';
import { _captureLogger } from '../../src/core/logger.js';
const ROOT = '/virtual-gn-m7-repo';
const FILLER_COUNT = 50_010; // > WORKSPACE_ROOT_SCAN_MAX_DIRS (50_000)
// The wide subtree is named to sort AFTER `members`: both scans now read directories in
// sorted order (deterministic), so a capped scan drops whatever sorts last — the
// fixture must not rely on unsorted readdir order to reach `members/only` first.
interface FakeDirent {
readonly name: string;
isDirectory(): boolean;
isFile(): boolean;
}
const dirEnt = (name: string): FakeDirent => ({
name,
isDirectory: () => true,
isFile: () => false,
});
const fileEnt = (name: string): FakeDirent => ({
name,
isDirectory: () => false,
isFile: () => true,
});
/** Fisher-Yates, seeded by a simple LCG so each "shuffle" is reproducible. */
function shuffled<T>(arr: readonly T[], seed: number): T[] {
const out = [...arr];
let s = seed;
const rand = (): number => {
s = (s * 1103515245 + 12345) & 0x7fffffff;
return s / 0x7fffffff;
};
for (let i = out.length - 1; i > 0; i--) {
const j = Math.floor(rand() * (i + 1));
[out[i], out[j]] = [out[j]!, out[i]!];
}
return out;
}
const fillerNames = Array.from(
{ length: FILLER_COUNT },
(_, i) => `d${String(i).padStart(6, '0')}`,
);
// A mutable box the mock factory closes over — flipped per shuffle from
// inside the test, so `vi.mock` (hoisted, registered once) can still serve a
// different `filler` order on each call without `vi.resetModules()`.
const box = vi.hoisted(() => ({ fillerOrder: [] as string[] }));
vi.mock('fs/promises', async (importOriginal) => {
const actual = await importOriginal<typeof import('fs/promises')>();
const d = (actual as unknown as { default: typeof actual }).default ?? actual;
const relOf = (p: string): string | null =>
p === ROOT ? '' : p.startsWith(`${ROOT}/`) ? p.slice(ROOT.length + 1) : null;
const fakeReaddir = async (dir: string) => {
const rel = relOf(dir);
if (rel === null)
return (d as typeof import('fs/promises')).readdir(
dir as never,
{
withFileTypes: true,
} as never,
);
if (rel === '') return [dirEnt('members'), dirEnt('zzz-filler')];
if (rel === 'members') return [dirEnt('only')];
if (rel === 'members/only') return [fileEnt('package.json')];
if (rel === 'zzz-filler') return box.fillerOrder.map(dirEnt);
// Every filler child (and anything deeper, unreached in practice) is empty.
return [];
};
const fakeReadFile = async (file: string) => {
const rel = relOf(file);
if (rel === null) return (d as typeof import('fs/promises')).readFile(file as never, 'utf-8');
if (rel === 'package.json') {
return JSON.stringify({ name: 'root', private: true, workspaces: ['members/*'] });
}
if (rel === 'members/only/package.json') {
return JSON.stringify({ name: '@repo/only', exports: { '.': './index.ts' } });
}
const err = Object.assign(new Error(`ENOENT: ${file}`), { code: 'ENOENT' });
throw err;
};
return {
default: new Proxy(d, {
get(target, prop) {
if (prop === 'readdir') return fakeReaddir;
if (prop === 'readFile') return fakeReadFile;
const v = Reflect.get(target, prop, target) as unknown;
return typeof v === 'function' ? (v as (...args: unknown[]) => unknown).bind(target) : v;
},
}),
};
});
describe('M7 — workspace-root scan: sorted readdir + a cap that warns (50,010-directory synthetic tree)', () => {
it('trips the cap and warns, and the run still completes deterministically for a result outside the capped subtree', async () => {
const { loadNodeWorkspacePackages, invalidateNodeWorkspacePackages } =
await import('../../src/core/ingestion/import-resolvers/node-workspace-packages.js');
const results: {
warned: boolean;
onlyDir: string | undefined;
onlyEntries: readonly string[] | undefined;
}[] = [];
for (const seed of [1, 2]) {
box.fillerOrder = shuffled(fillerNames, seed);
invalidateNodeWorkspacePackages(ROOT);
const cap = _captureLogger();
let pkgs;
try {
pkgs = await loadNodeWorkspacePackages(ROOT);
} finally {
cap.restore();
}
const text = cap.text();
const warned =
text.includes('workspace-root scan') &&
text.includes('50000-directory cap') &&
text.includes(ROOT);
const only = pkgs?.byName.get('@repo/only');
results.push({ warned, onlyDir: only?.dir, onlyEntries: only?.entries });
}
// Both shuffles hit the cap and warned about it.
expect(results[0]!.warned).toBe(true);
expect(results[1]!.warned).toBe(true);
// Both shuffles still admit the package reachable independently of the
// wide/capped `filler` subtree, identically.
expect(results[0]!.onlyDir).toBe('members/only');
expect(results[1]!.onlyDir).toBe('members/only');
expect(results[0]!.onlyEntries).toEqual(['members/only/index']);
expect(results[1]!.onlyEntries).toEqual(results[0]!.onlyEntries);
}, 60_000);
});