mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-02 09:45:39 +08:00
The orphan sweep discarded resolver statistics, so unchanged files caused the CLI to report a no-op after recovery. Carry processed, resolved, and unresolved sweep counts into SyncResult and report recovery without changing quiet mode. Cover successful recovery, unresolved attempts, and genuine no-ops with real SQLite regression tests. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
afbaf6f61d
commit
61846105c4
@@ -7,12 +7,12 @@ import CodeGraph from '../src/index';
|
||||
|
||||
const BIN = path.resolve(__dirname, '../dist/bin/codegraph.js');
|
||||
|
||||
describe('codegraph sync lock contention (#1361)', () => {
|
||||
describe('codegraph sync reporting', () => {
|
||||
let testDir: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-cli-sync-'));
|
||||
fs.writeFileSync(path.join(testDir, 'index.ts'), 'export function original() { return 1; }');
|
||||
fs.writeFileSync(path.join(testDir, 'index.ts'), 'export function original() { return target(); }\nexport function target() { return 1; }');
|
||||
const cg = CodeGraph.initSync(testDir);
|
||||
try {
|
||||
await cg.indexAll();
|
||||
@@ -38,6 +38,38 @@ describe('codegraph sync lock contention (#1361)', () => {
|
||||
});
|
||||
}
|
||||
|
||||
it.each([false, true])('reports pending-reference recovery (quiet=%s)', (quiet) => {
|
||||
for (const resolvable of [true, false]) {
|
||||
const cg = CodeGraph.openSync(testDir);
|
||||
try {
|
||||
const queries = (cg as unknown as { queries: import('../src/db/queries').QueryBuilder }).queries;
|
||||
const caller = cg.searchNodes('original').find(r => r.node.name === 'original')!.node;
|
||||
const target = cg.searchNodes('target').find(r => r.node.name === 'target')!.node;
|
||||
queries.db.prepare("DELETE FROM edges WHERE source = ? AND target = ? AND kind = 'calls'")
|
||||
.run(caller.id, target.id);
|
||||
queries.insertUnresolvedRef({
|
||||
fromNodeId: caller.id, referenceName: resolvable ? 'target' : 'missingTarget',
|
||||
referenceKind: 'calls', line: 1, column: 37, filePath: 'index.ts', language: 'typescript',
|
||||
});
|
||||
} finally {
|
||||
cg.destroy();
|
||||
}
|
||||
const recovered = sync(quiet);
|
||||
expect(recovered.error).toBeUndefined();
|
||||
expect(recovered.status).toBe(0);
|
||||
if (quiet) expect(recovered.stdout + recovered.stderr).toBe('');
|
||||
else {
|
||||
expect(recovered.stdout).not.toContain('Already up to date');
|
||||
expect(recovered.stdout).toContain(`Resolved ${resolvable ? 1 : 0} pending references`);
|
||||
if (!resolvable) expect(recovered.stdout).toContain('1 unresolved');
|
||||
}
|
||||
const unchanged = sync(quiet);
|
||||
expect(unchanged.status).toBe(0);
|
||||
if (quiet) expect(unchanged.stdout + unchanged.stderr).toBe('');
|
||||
else expect(unchanged.stdout).toContain('Already up to date');
|
||||
}
|
||||
});
|
||||
|
||||
it.each([false, true])('reports contention and recovers after release (quiet=%s)', (quiet) => {
|
||||
fs.writeFileSync(path.join(testDir, 'index.ts'), 'export function changedUnderLock() { return 2; }');
|
||||
const lockPath = path.join(testDir, '.codegraph', 'codegraph.lock');
|
||||
|
||||
@@ -1096,3 +1096,47 @@ describe('committed-but-unindexed changes (#1829)', () => {
|
||||
expect(cg.getChangedFiles().added).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('sync pending-reference recovery reporting (#1360)', () => {
|
||||
let testDir: string;
|
||||
let cg: CodeGraph;
|
||||
|
||||
beforeEach(async () => {
|
||||
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-sync-recovery-'));
|
||||
fs.writeFileSync(path.join(testDir, 'index.ts'),
|
||||
'export function caller() { return target(); }\nexport function target() { return 1; }\n');
|
||||
cg = CodeGraph.initSync(testDir);
|
||||
await cg.indexAll();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cg?.destroy();
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it.each([true, false])('reports sweep outcomes without file changes (resolvable=%s)', async (resolvable) => {
|
||||
const queries = (cg as unknown as { queries: import('../src/db/queries').QueryBuilder }).queries;
|
||||
const caller = cg.searchNodes('caller').find(r => r.node.name === 'caller')!.node;
|
||||
const target = cg.searchNodes('target').find(r => r.node.name === 'target')!.node;
|
||||
queries.db.prepare("DELETE FROM edges WHERE source = ? AND target = ? AND kind = 'calls'")
|
||||
.run(caller.id, target.id);
|
||||
queries.insertUnresolvedRef({
|
||||
fromNodeId: caller.id, referenceName: resolvable ? 'target' : 'missingTarget',
|
||||
referenceKind: 'calls', line: 1, column: 35, filePath: 'index.ts', language: 'typescript',
|
||||
});
|
||||
expect(cg.getPendingReferenceCount()).toBe(1);
|
||||
|
||||
const result = await cg.sync();
|
||||
expect(result).toMatchObject({
|
||||
filesAdded: 0, filesModified: 0, filesRemoved: 0,
|
||||
pendingRefsProcessed: 1, pendingRefsResolved: resolvable ? 1 : 0,
|
||||
pendingRefsUnresolved: resolvable ? 0 : 1,
|
||||
});
|
||||
expect(cg.getPendingReferenceCount()).toBe(0);
|
||||
expect(cg.getCallees(caller.id).some(r => r.node.id === target.id)).toBe(resolvable);
|
||||
expect(await cg.sync()).toMatchObject({
|
||||
pendingRefsProcessed: 0, pendingRefsResolved: 0, pendingRefsUnresolved: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -973,9 +973,9 @@ program
|
||||
|
||||
const totalChanges = result.filesAdded + result.filesModified + result.filesRemoved;
|
||||
|
||||
if (totalChanges === 0) {
|
||||
if (totalChanges === 0 && !result.pendingRefsProcessed) {
|
||||
clack.log.info('Already up to date');
|
||||
} else {
|
||||
} else if (totalChanges > 0) {
|
||||
clack.log.success(`Synced ${formatNumber(totalChanges)} changed files`);
|
||||
const details: string[] = [];
|
||||
if (result.filesAdded > 0) details.push(`Added: ${result.filesAdded}`);
|
||||
@@ -984,6 +984,12 @@ program
|
||||
clack.log.info(`${details.join(', ')} ${getGlyphs().dash} ${formatNumber(result.nodesUpdated)} nodes in ${formatDuration(result.durationMs)}`);
|
||||
}
|
||||
|
||||
if (result.pendingRefsProcessed) {
|
||||
const unresolved = result.pendingRefsUnresolved
|
||||
? ` (${formatNumber(result.pendingRefsUnresolved)} unresolved)` : '';
|
||||
clack.log.info(`Resolved ${formatNumber(result.pendingRefsResolved ?? 0)} pending references${unresolved}`);
|
||||
}
|
||||
|
||||
clack.outro('Done');
|
||||
} finally {
|
||||
cg.destroy();
|
||||
|
||||
@@ -120,6 +120,12 @@ export interface IndexResult {
|
||||
* Result of a sync operation
|
||||
*/
|
||||
export interface SyncResult {
|
||||
/** References attempted by the pending-reference recovery sweep, if run. */
|
||||
pendingRefsProcessed?: number;
|
||||
/** Pending references successfully resolved by the recovery sweep. */
|
||||
pendingRefsResolved?: number;
|
||||
/** Pending references the recovery sweep could not resolve. */
|
||||
pendingRefsUnresolved?: number;
|
||||
filesChecked: number;
|
||||
filesAdded: number;
|
||||
filesModified: number;
|
||||
|
||||
+7
-1
@@ -982,6 +982,9 @@ export class CodeGraph {
|
||||
// Grind them down with the batched resolver; this also makes a bare
|
||||
// `codegraph sync` the recovery command for a wedged index. On a
|
||||
// healthy index this is one COUNT query.
|
||||
result.pendingRefsProcessed = 0;
|
||||
result.pendingRefsResolved = 0;
|
||||
result.pendingRefsUnresolved = 0;
|
||||
const orphanCount = this.queries.getUnresolvedReferencesCount();
|
||||
if (orphanCount > 0) {
|
||||
options.onProgress?.({
|
||||
@@ -990,7 +993,7 @@ export class CodeGraph {
|
||||
total: orphanCount,
|
||||
});
|
||||
|
||||
await this.resolveReferencesBatched(
|
||||
const recovery = await this.resolveReferencesBatched(
|
||||
(current, total) => {
|
||||
options.onProgress?.({
|
||||
phase: 'resolving',
|
||||
@@ -1007,6 +1010,9 @@ export class CodeGraph {
|
||||
},
|
||||
backpressure
|
||||
);
|
||||
result.pendingRefsProcessed = recovery.stats.total;
|
||||
result.pendingRefsResolved = recovery.stats.resolved;
|
||||
result.pendingRefsUnresolved = recovery.stats.unresolved;
|
||||
}
|
||||
|
||||
if (filesChanged || orphanCount > 0) {
|
||||
|
||||
Reference in New Issue
Block a user