mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-07 08:26:11 +00:00
* feat(cpp): parse CUDA source extensions * test(cpp): characterize CUDA parser limitations --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
230 lines
8.3 KiB
TypeScript
230 lines
8.3 KiB
TypeScript
/**
|
|
* Integration test: IncludeExtractor output → group matching → bridge DB.
|
|
*
|
|
* Covers PR #1156 review finding #7: verifies that the full runtime path
|
|
* (IncludeExtractor → StoredContract → runExactMatch → CrossLinks → writeBridge)
|
|
* stays wired up. A regression in either normalizeContractId or the include
|
|
* branch of ManifestExtractor.resolveSymbol would produce 0 cross-links and
|
|
* fail this test.
|
|
*/
|
|
import { describe, it, expect } from 'vitest';
|
|
import * as fs from 'node:fs';
|
|
import * as os from 'node:os';
|
|
import * as path from 'node:path';
|
|
import { parseGroupConfig } from '../../../src/core/group/config-parser.js';
|
|
import { syncGroup } from '../../../src/core/group/sync.js';
|
|
import type { StoredContract } from '../../../src/core/group/types.js';
|
|
import { IncludeExtractor } from '../../../src/core/group/extractors/include-extractor.js';
|
|
import { normalizeContractId } from '../../../src/core/group/matching.js';
|
|
|
|
const GROUP_YAML = [
|
|
'version: 1',
|
|
'name: include-test-group',
|
|
'description: "IncludeExtractor integration test"',
|
|
'',
|
|
'repos:',
|
|
' app/provider: include-provider',
|
|
' app/consumer: include-consumer',
|
|
'',
|
|
'links: []',
|
|
'packages: {}',
|
|
'',
|
|
'detect:',
|
|
' http: false',
|
|
' grpc: false',
|
|
' topics: false',
|
|
' shared_libs: false',
|
|
' includes: true',
|
|
' embedding_fallback: false',
|
|
'',
|
|
'matching:',
|
|
' bm25_threshold: 0.7',
|
|
' embedding_threshold: 0.65',
|
|
' max_candidates_per_step: 3',
|
|
].join('\n');
|
|
|
|
describe('IncludeExtractor → syncGroup integration (finding #7)', () => {
|
|
it('produces a CrossLink when provider and consumer emit the same include contract-id', async () => {
|
|
const config = parseGroupConfig(GROUP_YAML);
|
|
|
|
// Mock the IncludeExtractor output directly — a header provider in one
|
|
// repo and a quoted #include consumer in the other, both normalized to
|
|
// the same include::map/base/view.h contract-id.
|
|
const mockContracts: StoredContract[] = [
|
|
{
|
|
contractId: 'include::map/base/view.h',
|
|
type: 'include',
|
|
role: 'provider',
|
|
symbolUid: 'File:map/base/view.h',
|
|
symbolRef: { filePath: 'map/base/view.h', name: 'view.h' },
|
|
symbolName: 'view.h',
|
|
confidence: 0.95,
|
|
meta: { source: 'filesystem' },
|
|
repo: 'app/provider',
|
|
},
|
|
{
|
|
contractId: 'include::map/base/view.h',
|
|
type: 'include',
|
|
role: 'consumer',
|
|
symbolUid: 'File:src/controller.cpp',
|
|
symbolRef: { filePath: 'src/controller.cpp', name: 'map/base/view.h' },
|
|
symbolName: 'map/base/view.h',
|
|
confidence: 0.85,
|
|
meta: { source: 'tree_sitter', includePath: 'map/base/view.h' },
|
|
repo: 'app/consumer',
|
|
},
|
|
];
|
|
|
|
const result = await syncGroup(config, {
|
|
extractorOverride: async () => mockContracts,
|
|
skipWrite: true,
|
|
});
|
|
|
|
const includeLinks = result.crossLinks.filter((l) => l.type === 'include');
|
|
expect(includeLinks.length).toBeGreaterThanOrEqual(1);
|
|
|
|
const link = includeLinks[0];
|
|
expect(link.contractId).toBe('include::map/base/view.h');
|
|
expect(link.matchType).toBe('exact');
|
|
expect(link.from.repo).toBe('app/consumer');
|
|
expect(link.to.repo).toBe('app/provider');
|
|
});
|
|
|
|
it('normalizes mixed-case / backslash include paths to the same contract-id end-to-end', async () => {
|
|
const config = parseGroupConfig(GROUP_YAML);
|
|
|
|
// Provider writes the canonical form; consumer's include has mixed case
|
|
// and a backslash. After normalizeContractId they must still match.
|
|
const providerId = 'include::map/base/view.h';
|
|
const rawConsumerId = 'include::Map\\Base\\View.h';
|
|
|
|
// Sanity — normalizeContractId must collapse them.
|
|
expect(normalizeContractId(rawConsumerId)).toBe(providerId);
|
|
|
|
const mockContracts: StoredContract[] = [
|
|
{
|
|
contractId: providerId,
|
|
type: 'include',
|
|
role: 'provider',
|
|
symbolUid: 'File:map/base/view.h',
|
|
symbolRef: { filePath: 'map/base/view.h', name: 'view.h' },
|
|
symbolName: 'view.h',
|
|
confidence: 0.95,
|
|
meta: { source: 'filesystem' },
|
|
repo: 'app/provider',
|
|
},
|
|
{
|
|
contractId: rawConsumerId,
|
|
type: 'include',
|
|
role: 'consumer',
|
|
symbolUid: 'File:src/controller.cpp',
|
|
symbolRef: { filePath: 'src/controller.cpp', name: 'Map/Base/View.h' },
|
|
symbolName: 'Map/Base/View.h',
|
|
confidence: 0.85,
|
|
meta: { source: 'tree_sitter', includePath: 'Map\\Base\\View.h' },
|
|
repo: 'app/consumer',
|
|
},
|
|
];
|
|
|
|
const result = await syncGroup(config, {
|
|
extractorOverride: async () => mockContracts,
|
|
skipWrite: true,
|
|
});
|
|
|
|
const includeLinks = result.crossLinks.filter((l) => l.type === 'include');
|
|
expect(includeLinks.length).toBeGreaterThanOrEqual(1);
|
|
});
|
|
|
|
it('round-trip: extractor output from two real temp repos produces matching contract-ids', async () => {
|
|
// Drives the extractor directly (no `syncGroup`) against two on-disk
|
|
// fixture repos, then hands the StoredContract-shaped output to
|
|
// syncGroup via extractorOverride. This exercises the real extraction
|
|
// code + the matching pipeline together.
|
|
const providerDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-include-int-provider-'));
|
|
const consumerDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-include-int-consumer-'));
|
|
try {
|
|
fs.mkdirSync(path.join(providerDir, 'shared/api'), { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(providerDir, 'shared/api/client.h'),
|
|
'#pragma once\nstruct Client {};',
|
|
);
|
|
fs.mkdirSync(path.join(consumerDir, 'src'), { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(consumerDir, 'src/main.cpp'),
|
|
'#include "shared/api/client.h"\nint main(){return 0;}',
|
|
);
|
|
|
|
const extractor = new IncludeExtractor();
|
|
const providerOutput = await extractor.extract(null, providerDir, {
|
|
id: 'provider',
|
|
path: 'app/provider',
|
|
repoPath: providerDir,
|
|
storagePath: path.join(providerDir, '.gitnexus'),
|
|
});
|
|
const consumerOutput = await extractor.extract(null, consumerDir, {
|
|
id: 'consumer',
|
|
path: 'app/consumer',
|
|
repoPath: consumerDir,
|
|
storagePath: path.join(consumerDir, '.gitnexus'),
|
|
});
|
|
|
|
const stored: StoredContract[] = [
|
|
...providerOutput
|
|
.filter((c) => c.role === 'provider')
|
|
.map((c) => ({ ...c, repo: 'app/provider' })),
|
|
...consumerOutput
|
|
.filter((c) => c.role === 'consumer')
|
|
.map((c) => ({ ...c, repo: 'app/consumer' })),
|
|
];
|
|
|
|
const config = parseGroupConfig(GROUP_YAML);
|
|
const result = await syncGroup(config, {
|
|
extractorOverride: async () => stored,
|
|
skipWrite: true,
|
|
});
|
|
|
|
const includeLinks = result.crossLinks.filter((l) => l.type === 'include');
|
|
expect(includeLinks.length).toBeGreaterThanOrEqual(1);
|
|
expect(includeLinks[0].contractId).toBe('include::shared/api/client.h');
|
|
expect(includeLinks[0].matchType).toBe('exact');
|
|
} finally {
|
|
fs.rmSync(providerDir, { recursive: true, force: true });
|
|
fs.rmSync(consumerDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it('suppresses extensionless local includes that resolve to .cuh headers', async () => {
|
|
const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-include-cuh-'));
|
|
try {
|
|
fs.mkdirSync(path.join(repoDir, 'include'), { recursive: true });
|
|
fs.mkdirSync(path.join(repoDir, 'src'), { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(repoDir, 'include/kernel.cuh'),
|
|
'#pragma once\n__global__ void kernel();',
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(repoDir, 'src/main.cu'),
|
|
'#include "include/kernel"\nvoid host() {}',
|
|
);
|
|
|
|
const extractor = new IncludeExtractor();
|
|
const contracts = await extractor.extract(null, repoDir, {
|
|
id: 'cuda-repo',
|
|
path: 'app/cuda-repo',
|
|
repoPath: repoDir,
|
|
storagePath: path.join(repoDir, '.gitnexus'),
|
|
});
|
|
|
|
expect(
|
|
contracts.some(
|
|
(c) => c.role === 'provider' && c.contractId === 'include::include/kernel.cuh',
|
|
),
|
|
).toBe(true);
|
|
expect(
|
|
contracts.some((c) => c.role === 'consumer' && c.contractId === 'include::include/kernel'),
|
|
).toBe(false);
|
|
} finally {
|
|
fs.rmSync(repoDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|