GitNexus/gitnexus/test/integration/resolvers/cpp-local-using-headers.test.ts

154 lines
6.6 KiB
TypeScript

import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import {
getRelationships,
runPipelineFromRepo,
writeFixtureRepo,
type PipelineResult,
} from './helpers.js';
describe('C++ namespace using visibility through literal includes', () => {
let repoDir: string;
let result: PipelineResult;
beforeAll(async () => {
repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-cpp-using-headers-'));
writeFixtureRepo(repoDir, {
'definitions.hpp': `namespace helpers {
int work() { return 1; }
struct Worker { int run() { return 1; } };
}
`,
'named.hpp': '#include "definitions.hpp"\nusing helpers::work;\nusing helpers::Worker;\n',
'forwarded.hpp': '#include "named.hpp"\n',
'directive.hpp': '#include "definitions.hpp"\nusing namespace helpers;\n',
'named.cpp':
'#include "named.hpp"\nint from_named() { return work(); }\nint from_type() { Worker w; return w.run(); }\n',
'forwarded.cpp': '#include "forwarded.hpp"\nint from_forwarded() { return work(); }\n',
'directive.cpp': '#include "directive.hpp"\nint from_directive() { return work(); }\n',
'before.cpp': 'int before_include() { return work(); }\n#include "named.hpp"\n',
});
result = await runPipelineFromRepo(repoDir, () => {});
}, 60_000);
afterAll(() => {
if (repoDir !== undefined) fs.rmSync(repoDir, { recursive: true, force: true });
});
it.each(['from_named', 'from_forwarded', 'from_directive'])(
'resolves %s through the included namespace using',
(caller) => {
const calls = getRelationships(result, 'CALLS').filter((edge) => edge.source === caller);
expect(calls.map((edge) => [edge.target, edge.targetFilePath])).toEqual([
['work', 'definitions.hpp'],
]);
expect(calls.every((edge) => edge.rel.reason !== 'global-name-fallback')).toBe(true);
},
);
it('retains the imported class identity', () => {
const calls = getRelationships(result, 'CALLS').filter(
(edge) => edge.source === 'from_type' && edge.target === 'run',
);
expect(calls.map((edge) => edge.targetFilePath)).toEqual(['definitions.hpp']);
expect(calls.every((edge) => edge.rel.reason !== 'global-name-fallback')).toBe(true);
});
it('does not make a later include visible in an earlier function definition', () => {
expect(
getRelationships(result, 'CALLS').filter((edge) => edge.source === 'before_include'),
).toEqual([]);
});
});
describe('C++ unresolved named using through a literal include', () => {
let repoDir: string;
let result: PipelineResult;
beforeAll(async () => {
repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-cpp-unresolved-using-header-'));
writeFixtureRepo(repoDir, {
'unresolved.hpp': 'using absent::work;\n',
'caller.cpp': '#include "unresolved.hpp"\nint from_unresolved_header() { return work(); }\n',
'forwarded.hpp': '#include "unresolved.hpp"\n',
'forwarded.cpp':
'#include "forwarded.hpp"\nint from_forwarded_unresolved_header() { return work(); }\n',
'missing-export.hpp': 'namespace present {}\nusing present::work;\n',
'missing-export.cpp':
'#include "missing-export.hpp"\nint from_missing_export_header() { return work(); }\n',
'before.cpp':
'int before_unresolved_include() { return work(); }\n#include "unresolved.hpp"\n',
'local-before.hpp': 'using absent::local_work;\n',
'local-before.cpp':
'int local_work() { return 1; }\nint before_local_unresolved_include() { return local_work(); }\n#include "local-before.hpp"\nint after_local_unresolved_include() { return local_work(); }\n',
'unrelated.cpp': 'int work() { return 9; }\n',
});
result = await runPipelineFromRepo(repoDir, () => {});
}, 60_000);
afterAll(() => {
if (repoDir !== undefined) fs.rmSync(repoDir, { recursive: true, force: true });
});
it('does not fall back to an unrelated workspace function after including an unresolved named using', () => {
expect(
getRelationships(result, 'CALLS').filter((edge) => edge.source === 'from_unresolved_header'),
).toEqual([]);
});
it.each([
'from_forwarded_unresolved_header',
'from_missing_export_header',
'after_local_unresolved_include',
])('preserves the unresolved header claim in %s', (caller) => {
expect(getRelationships(result, 'CALLS').filter((edge) => edge.source === caller)).toEqual([]);
});
it('keeps global fallback available before an unresolved header include', () => {
const calls = getRelationships(result, 'CALLS').filter(
(edge) => edge.source === 'before_unresolved_include',
);
expect(calls.map((edge) => [edge.target, edge.targetFilePath, edge.rel.reason])).toEqual([
['work', 'unrelated.cpp', 'global-name-fallback'],
]);
});
it('retains the local declaration before an unresolved header include', () => {
const calls = getRelationships(result, 'CALLS').filter(
(edge) => edge.source === 'before_local_unresolved_include',
);
expect(calls.map((edge) => [edge.target, edge.targetFilePath])).toEqual([
['local_work', 'local-before.cpp'],
]);
});
});
describe('C++ included and local overload identity', () => {
let repoDir: string;
let result: PipelineResult;
beforeAll(async () => {
repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-cpp-include-overloads-'));
writeFixtureRepo(repoDir, {
'overloads.hpp': 'int choose(int value);\n',
'main.cpp':
'#include "overloads.hpp"\nint choose(double value) { return 2; }\nint choose_integer() { return choose(1); }\nint choose_double() { return choose(1.5); }\n',
});
result = await runPipelineFromRepo(repoDir, () => {});
}, 60_000);
afterAll(() => {
if (repoDir !== undefined) fs.rmSync(repoDir, { recursive: true, force: true });
});
it('keeps a distinct included overload beside the local definition', () => {
const calls = getRelationships(result, 'CALLS').filter(
(edge) => edge.source === 'choose_integer',
);
expect(calls.map((edge) => [edge.target, edge.targetFilePath])).toEqual([
['choose', 'overloads.hpp'],
]);
expect(result.graph.getNode(calls[0].rel.targetId)?.properties.parameterTypes).toEqual(['int']);
});
it('selects the local overload for its matching argument', () => {
const calls = getRelationships(result, 'CALLS').filter(
(edge) => edge.source === 'choose_double',
);
expect(calls.map((edge) => [edge.target, edge.targetFilePath])).toEqual([
['choose', 'main.cpp'],
]);
expect(result.graph.getNode(calls[0].rel.targetId)?.properties.parameterTypes).toEqual([
'double',
]);
});
});