Skip to content
Open
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ leaves behind, and asserts the seam: nothing outside `lib/adapter.js` may reach
package is **prepared, not published** — `ui/package.json` carries `"private": true` deliberately,
and `scripts/pack-npm.sh` only packs a tarball when `CODEGRAPH_PACK_UI=1`.

Tests run as **two vitest projects** (`vitest.workspace.mts`): `engine` (node) and `ui` (jsdom, the
Tests run as **two Vitest projects** (`test.projects` in `vitest.config.mts`): `engine` (node) and `ui` (jsdom, the
Svelte plugin, `resolve.conditions: ['browser']`) for the single `__tests__/ui-package.test.ts`.
`npm test` still runs both. The split is not cosmetic — `browser` is a package-resolution
condition, and applied globally it hands the engine's suites the browser builds of
Expand Down
39 changes: 28 additions & 11 deletions __tests__/git-index-currency.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,28 @@ vi.mock('child_process', async importOriginal => {
});
vi.mock('fs', async importOriginal => {
const actual = await importOriginal<typeof import('fs')>();
return { ...actual, readFileSync: vi.fn(actual.readFileSync) };
// openSync too: a bounded source reader opens a descriptor instead of
// calling readFileSync, and the injected read failure must reach either.
return { ...actual, readFileSync: vi.fn(actual.readFileSync), openSync: vi.fn(actual.openSync) };
});
// The real entry points, for the injecting implementations to fall through to
// and for afterEach to reinstate. Under Vitest 4 `vi.spyOn` on a mocked
// module's export returns the same `vi.fn`, so reading `cp.execFileSync` inside
// a test and calling it from the injected implementation recurses into itself,
// and `vi.restoreAllMocks()` no longer resets a `vi.fn`'s implementation.
const actualCp = await vi.importActual<typeof import('child_process')>('child_process');
const actualFs = await vi.importActual<typeof import('fs')>('fs');
const injectExecFileSync = (impl: (real: typeof cp.execFileSync, ...args: any[]) => unknown) =>
vi.mocked(cp.execFileSync).mockImplementation(((...args: any[]) => impl(actualCp.execFileSync, ...args)) as typeof cp.execFileSync);
const injectReadFileSync = (impl: (real: typeof fs.readFileSync, ...args: any[]) => unknown) =>
vi.mocked(fs.readFileSync).mockImplementation(((...args: any[]) => impl(actualFs.readFileSync, ...args)) as typeof fs.readFileSync);
const injectOpenSync = (impl: (real: typeof fs.openSync, ...args: any[]) => unknown) =>
vi.mocked(fs.openSync).mockImplementation(((...args: any[]) => impl(actualFs.openSync, ...args)) as typeof fs.openSync);
const reinstateIo = () => {
vi.mocked(cp.execFileSync).mockImplementation(actualCp.execFileSync as typeof cp.execFileSync);
vi.mocked(fs.readFileSync).mockImplementation(actualFs.readFileSync as typeof fs.readFileSync);
vi.mocked(fs.openSync).mockImplementation(actualFs.openSync as typeof fs.openSync);
};

describe('git index currency across commits and restores (#1829)', () => {
let root: string;
Expand All @@ -34,7 +54,7 @@ describe('git index currency across commits and restores (#1829)', () => {
cg = CodeGraph.initSync(root);
expect((await cg.indexAll()).success).toBe(true);
});
afterEach(() => { vi.restoreAllMocks(); cg?.close(); fs.rmSync(root, { recursive: true, force: true }); });
afterEach(() => { reinstateIo(); vi.restoreAllMocks(); cg?.close(); fs.rmSync(root, { recursive: true, force: true }); });

it.each(['index', 'sync', 'scoped'] as const)('sees a restored indexed dirty edit after %s', async mode => {
write('source.ts', 'dirtyVersion');
Expand Down Expand Up @@ -80,12 +100,11 @@ describe('git index currency across commits and restores (#1829)', () => {

it('falls back when git diff fails instead of claiming a clean index', () => {
write('new.ts', 'newSymbol'); commit();
const real = cp.execFileSync;
let injected = 0;
vi.spyOn(cp, 'execFileSync').mockImplementation(((file: string, args: string[], options: any) => {
injectExecFileSync((real, file: string, args: string[], options: any) => {
if (file === 'git' && args[0] === 'diff') { injected++; throw new Error('Injected git diff timeout'); }
return real(file, args, options);
}) as typeof cp.execFileSync);
});
expect(cg.getChangedFiles().added).toEqual(['new.ts']);
expect(injected).toBeGreaterThan(0);
});
Expand Down Expand Up @@ -135,19 +154,17 @@ describe('git index currency across commits and restores (#1829)', () => {
// Sync reads a source file through a bounded reader that opens a
// descriptor (#1910), so the failure is injected at openSync as well as
// readFileSync: it must reach whichever one the read goes through.
const realRead = fs.readFileSync;
const realOpen = fs.openSync;
let injected = 0;
const failNewTs = (real: (...args: any[]) => unknown) => (file: any, ...args: any[]) => {
const failNewTs = (real: (...args: any[]) => unknown, file: any, ...args: any[]) => {
if (String(file) === path.join(root, 'new.ts')) { injected++; throw new Error('Injected transient read error'); }
return real(file, ...args);
};
vi.spyOn(fs, 'readFileSync').mockImplementation(failNewTs(realRead as any) as typeof fs.readFileSync);
vi.spyOn(fs, 'openSync').mockImplementation(failNewTs(realOpen as any) as typeof fs.openSync);
injectReadFileSync(failNewTs);
injectOpenSync(failNewTs);
await cg.sync();
expect(injected).toBeGreaterThan(0);
expect(symbols('newSymbol')).not.toContain('newSymbol');
vi.restoreAllMocks();
reinstateIo();
expect(cg.getChangedFiles().added).toEqual(['new.ts']);
await cg.sync(); clean();
});
Expand Down
Loading