Files
codegraph/__tests__/global-setup-dist.test.ts
0f44fdf8af fix(tests): build missing or stale test prerequisites (#1879) (#2021)
* test: build dist/ before the suite when it is missing or stale (#1879)

About thirty suites spawn dist/bin/codegraph.js, the parallel resolver loads
its worker from dist/, and the `codegraph ui` suites serve dist/viewer/. On a
fresh checkout `npm ci && npm test` failed them on spawn timeouts and a
missing-viewer error that never mentioned the build; after an edit without a
rebuild they quietly exercised the old code.

A Vitest globalSetup now rebuilds each half only when it is missing or older
than its sources: `tsc` + copy-assets when anything under src/ is newer than
dist/bin/codegraph.js, `build:ui` when ui/src/ is newer than
dist/viewer/index.html. A current dist/ costs one mtime walk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(tests): build missing or stale test prerequisites (#1879)

Dist-dependent tests launched missing or stale compiled outputs without a build prerequisite.
Retain the contributor's shared setup and track build inputs and individual outputs for reliable invalidation.
Resolve TypeScript locally, preserve asset copying and engine/UI separation, and report build failures directly.
Add regression coverage for cold builds, warm runs, configuration changes, and missing assets.

Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:12:55 +00:00

99 lines
3.8 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { ensureTestDist } from './global-setup-dist';
let root: string;
let builds: string[];
function write(file: string, content = file): void {
fs.mkdirSync(path.dirname(path.join(root, file)), { recursive: true });
fs.writeFileSync(path.join(root, file), content);
}
const engineOutputs = ['dist/bin/codegraph.js', 'dist/db/schema.sql',
'dist/extraction/wasm/typescript.wasm', 'dist/resolution/resolve-worker.js'];
const viewerOutputs = ['dist/viewer/index.html', 'dist/viewer/assets/app.js', 'dist/viewer/assets/app.css'];
function build(part: 'engine' | 'viewer'): void {
builds.push(part);
for (const file of part === 'engine' ? engineOutputs : viewerOutputs) write(file);
}
function setup(): void { ensureTestDist(root, build); }
beforeEach(() => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-test-build-'));
builds = [];
for (const file of ['src/bin/codegraph.ts', 'src/db/schema.sql',
'src/extraction/wasm/typescript.wasm', 'tsconfig.json', 'package.json',
'package-lock.json', 'scripts/check-ui-build.mjs', 'ui/src/App.svelte',
'ui/vite.config.ts', 'ui/svelte.config.js', 'ui/tsconfig.json', 'ui/package.json', 'ui/index.html']) write(file);
});
afterEach(() => fs.rmSync(root, { recursive: true, force: true }));
describe('test dist prerequisites', () => {
it('builds a cold checkout in dependency order and leaves a warm checkout untouched', () => {
setup();
expect(builds).toEqual(['engine', 'viewer']);
builds = [];
setup();
expect(builds).toEqual([]);
});
it.each([...engineOutputs, ...viewerOutputs])('repairs missing output %s even with current sentinels', file => {
setup();
builds = [];
fs.unlinkSync(path.join(root, file));
setup();
expect(builds).toEqual([file.startsWith('dist/viewer/') ? 'viewer' : 'engine']);
expect(fs.existsSync(path.join(root, file))).toBe(true);
});
it.each([
['src/bin/codegraph.ts', ['engine']],
['src/db/schema.sql', ['engine']],
['src/extraction/wasm/typescript.wasm', ['engine']],
['tsconfig.json', ['engine', 'viewer']],
['package.json', ['engine', 'viewer']],
['package-lock.json', ['engine', 'viewer']],
['scripts/check-ui-build.mjs', ['engine', 'viewer']],
['ui/src/App.svelte', ['viewer']],
['ui/vite.config.ts', ['viewer']],
['ui/svelte.config.js', ['viewer']],
['ui/tsconfig.json', ['viewer']],
['ui/package.json', ['viewer']],
['ui/index.html', ['viewer']],
])('invalidates changed input %s even with a preserved mtime', (file, expected) => {
setup();
builds = [];
const stat = fs.statSync(path.join(root, file));
write(file, 'changed');
fs.utimesSync(path.join(root, file), stat.atime, stat.mtime);
setup();
expect(builds).toEqual(expected);
});
it('invalidates added and removed source files', () => {
setup();
builds = [];
write('src/new-worker.ts');
setup();
fs.unlinkSync(path.join(root, 'src/new-worker.ts'));
setup();
expect(builds).toEqual(['engine', 'engine']);
});
it('fails directly when the checkout has no installed compiler', () => {
expect(() => ensureTestDist(root)).toThrow(/engine build failed: Cannot find module/);
expect(fs.existsSync(path.join(root, 'dist/.test-build-engine.json'))).toBe(false);
});
it('reports build errors directly and retries instead of recording partial success', () => {
expect(() => ensureTestDist(root, () => {
write('dist/bin/codegraph.js');
throw new Error('compiler diagnostic');
})).toThrow('[test setup] engine build failed: compiler diagnostic');
expect(fs.existsSync(path.join(root, 'dist/.test-build-engine.json'))).toBe(false);
setup();
expect(builds).toEqual(['engine', 'viewer']);
});
});