GitNexus/gitnexus/test/integration/instance-ownership-pipeline-benchmark.test.ts
Copilot d3d4fa31bb
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
fix(scope-resolution): gate C#/Kotlin free calls by instance ownership (#2563) (#2654)
* Initial plan

* fix(scope-resolution): gate C# and Kotlin free calls

* fix(scope-resolution): keep Kotlin ownership gate safe

* Apply remaining changes

* perf(scope-resolution): benchmark and cache ownership gates

* test(scope-resolution): simplify benchmark scaling loop

* refactor(scope-resolution): encapsulate ownership cache

* test(scope-resolution): enforce subquadratic ownership scaling

* fix(scope-resolution): address ownership review findings

* test(csharp): regenerate capture golden for #2563 fixtures

The committed expected-captures.json was missing the new
NamespaceOwnerCollision.cs entry and carried a stale SameFileCases.cs
digest/count (56 → 67), so csharp-captures-golden.test.ts was the sole
red check on the PR. Regenerate with UPDATE_GOLDEN=1 to match the
fixtures the bench fingerprint already reflects.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 13:31:56 +01:00

129 lines
3.8 KiB
TypeScript

/**
* Instance-ownership free-call gate benchmark.
*
* Generates C# and Kotlin projects where every file contains a caller and an
* unrelated class method with the same receiver-less call name. Repeated calls
* stress the ownership gate that prevents the same-file fallback from linking
* those unrelated methods.
*
* Run:
* cd gitnexus && GITNEXUS_BENCH=1 npx vitest run test/integration/instance-ownership-pipeline-benchmark.test.ts
*/
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
import { runPipelineFromRepo } from '../../src/core/ingestion/pipeline.js';
const BENCH_ENABLED = process.env.GITNEXUS_BENCH === '1';
const CALLS_PER_FILE = 24;
// time growth divided by file growth: quadratic work reaches 2 on a doubling.
const NORMALIZED_SCALING_LIMIT = 2;
interface BenchResult {
fileCount: number;
callCount: number;
elapsedMs: number;
peakHeapMB: number;
}
interface LanguageCase {
readonly label: string;
readonly extension: string;
source(fileIndex: number): string;
}
const LANGUAGES: readonly LanguageCase[] = [
{
label: 'C#',
extension: 'cs',
source: (fileIndex) => `namespace Bench${fileIndex};
public class Caller${fileIndex}
{
${Array.from(
{ length: CALLS_PER_FILE },
(_, callIndex) => ` public void Run${callIndex}() { Foreign(); }`,
).join('\n')}
}
public class Unrelated${fileIndex}
{
public void Foreign() {}
}
`,
},
{
label: 'Kotlin',
extension: 'kt',
source: (fileIndex) => `package bench${fileIndex}
class Caller${fileIndex} {
${Array.from(
{ length: CALLS_PER_FILE },
(_, callIndex) => ` fun run${callIndex}() { foreign() }`,
).join('\n')}
}
class Unrelated${fileIndex} {
fun foreign() {}
}
`,
},
];
function generateFixture(language: LanguageCase, fileCount: number): string {
const dir = fs.mkdtempSync(
path.join(os.tmpdir(), `instance-ownership-${language.extension}-${fileCount}-`),
);
for (let i = 0; i < fileCount; i++) {
fs.writeFileSync(path.join(dir, `Case${i}.${language.extension}`), language.source(i));
}
return dir;
}
async function runBenchmark(language: LanguageCase, fileCount: number): Promise<BenchResult> {
const dir = generateFixture(language, fileCount);
let peakHeapMB = 0;
const heapSampler = setInterval(() => {
peakHeapMB = Math.max(peakHeapMB, process.memoryUsage().heapUsed / 1024 / 1024);
}, 25);
try {
const start = performance.now();
await runPipelineFromRepo(dir, () => {}, { skipGraphPhases: true });
return {
fileCount,
callCount: fileCount * CALLS_PER_FILE,
elapsedMs: Math.round(performance.now() - start),
peakHeapMB: Math.round(peakHeapMB),
};
} finally {
clearInterval(heapSampler);
fs.rmSync(dir, { recursive: true, force: true });
}
}
describe.skipIf(!BENCH_ENABLED)('instance-ownership free-call gate benchmark', () => {
for (const language of LANGUAGES) {
it(`${language.label} scales sub-quadratically with ownership-gated calls`, async () => {
// Wide enough steps to expose quadratic growth without making this
// opt-in benchmark impractical on contributor machines.
let previous: BenchResult | undefined;
for (const fileCount of [100, 250, 500]) {
const result = await runBenchmark(language, fileCount);
console.log(
`${language.label}: ${result.fileCount} files / ${result.callCount} calls: ` +
`${result.elapsedMs}ms, ${result.peakHeapMB}MB heap`,
);
if (previous !== undefined) {
const fileRatio = result.fileCount / previous.fileCount;
const timeRatio = result.elapsedMs / previous.elapsedMs;
expect(timeRatio / fileRatio).toBeLessThan(NORMALIZED_SCALING_LIMIT);
}
previous = result;
}
}, 600_000);
}
});