GitNexus/gitnexus/test/unit/cfg/reaching-def-reason-codec.test.ts
Gergő Magyar 78b4077d8a
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
feat(impact): opt-in PDG-backed impact mode - statement + inter-procedural slicing, resolved-callee-id soundness, mutation-oracle validated (#2227)
2026-06-20 12:04:32 +01:00

127 lines
5 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
encodeReachingDefReason,
encodeReachingDefReasonPairs,
decodeReachingDefReason,
REACHING_DEF_REASON_CODEC_VERSION,
} from '../../../src/core/ingestion/cfg/reaching-def-reason-codec.js';
import { sanitizeUTF8, escapeCSVField } from '../../../src/core/lbug/csv-generator.js';
// FU-B-2: the REACHING_DEF `reason` codec carries the binding NAME (first,
// verbatim — `pdg_query` flows keys on it) plus a compact versioned annotation
// holding the ORDERED LIST of def/use-line pairs for the (block-pair, binding)
// group. The statement-granular intra-block projection (pdg-impact.ts) walks the
// annotation's def→use pairs to recover a coalesced block's interior dependents,
// so the round-trip + CSV-survival contract is load-bearing.
describe('FU-B-2 reaching-def reason codec', () => {
it('round-trips name + a single def/use pair', () => {
const wire = encodeReachingDefReason('acc', 7, 9);
expect(wire).toBe(`acc|${REACHING_DEF_REASON_CODEC_VERSION}:7:9`);
expect(decodeReachingDefReason(wire)).toEqual({
name: 'acc',
pairs: [{ defLine: 7, useLine: 9 }],
defLine: 7,
useLine: 9,
});
});
it('round-trips a MULTI-pair list (same-binding reassignment chain)', () => {
// `acc = f(acc); acc = g(acc); acc = h(acc)` — one (self-block, accIdx) group,
// three def→use steps. The full list must survive so the projection walk can
// chain 24->25->26->27 to fixpoint (a first-pair-only encoding would stop at 25).
const wire = encodeReachingDefReasonPairs('acc', [
{ defLine: 24, useLine: 25 },
{ defLine: 25, useLine: 26 },
{ defLine: 26, useLine: 27 },
]);
expect(wire).toBe(`acc|${REACHING_DEF_REASON_CODEC_VERSION}:24:25;25:26;26:27`);
expect(decodeReachingDefReason(wire)).toEqual({
name: 'acc',
pairs: [
{ defLine: 24, useLine: 25 },
{ defLine: 25, useLine: 26 },
{ defLine: 26, useLine: 27 },
],
// `defLine`/`useLine` mirror the FIRST pair for back-compat consumers.
defLine: 24,
useLine: 25,
});
});
it('decodes a self-edge (defLine === useLine) — a same-line read-then-write', () => {
const wire = encodeReachingDefReason('x', 12, 12);
expect(decodeReachingDefReason(wire)).toEqual({
name: 'x',
pairs: [{ defLine: 12, useLine: 12 }],
defLine: 12,
useLine: 12,
});
});
it('preserves a synthetic-binding name (`name@module`) verbatim — `@` is not the separator', () => {
const wire = encodeReachingDefReason('config@module', 3, 40);
expect(decodeReachingDefReason(wire)).toEqual({
name: 'config@module',
pairs: [{ defLine: 3, useLine: 40 }],
defLine: 3,
useLine: 40,
});
});
it('a legacy bare-name reason (no annotation) decodes to the name with no pairs', () => {
expect(decodeReachingDefReason('total')).toEqual({ name: 'total', pairs: [] });
});
it('drops a malformed/negative line to the bare name (sound default — never a bad annotation)', () => {
expect(encodeReachingDefReason('y', -1, 9)).toBe('y');
expect(encodeReachingDefReason('y', 7, Number.NaN)).toBe('y');
expect(decodeReachingDefReason(encodeReachingDefReason('y', -1, 9))).toEqual({
name: 'y',
pairs: [],
});
});
it('drops a single malformed pair but keeps the well-formed pairs of the list', () => {
// `<name>|1:1:2;bad;5:6` — the middle chunk is not a `def:use` pair; it is
// dropped, the rest kept (the decoder never throws and never loses good pairs).
expect(decodeReachingDefReason('w|1:1:2;bad;5:6')).toEqual({
name: 'w',
pairs: [
{ defLine: 1, useLine: 2 },
{ defLine: 5, useLine: 6 },
],
defLine: 1,
useLine: 2,
});
});
it('never throws on garbage — yields the best-effort name, no pairs', () => {
expect(decodeReachingDefReason(undefined)).toEqual({ name: '', pairs: [] });
expect(decodeReachingDefReason(123)).toEqual({ name: '', pairs: [] });
// wrong version / shape → name kept, annotation discarded
expect(decodeReachingDefReason('z|9:1:2')).toEqual({ name: 'z', pairs: [] });
expect(decodeReachingDefReason('z|1:abc:2')).toEqual({ name: 'z', pairs: [] });
expect(decodeReachingDefReason('z|1:2')).toEqual({ name: 'z', pairs: [] });
});
it('survives escapeCSVField ∘ sanitizeUTF8 byte-exact (printable ASCII only)', () => {
const wire = encodeReachingDefReasonPairs('count', [
{ defLine: 100, useLine: 250 },
{ defLine: 250, useLine: 400 },
]);
expect(sanitizeUTF8(wire)).toBe(wire);
// round-trips out of the escaped CSV cell (strip the surrounding quotes)
const escaped = escapeCSVField(wire);
const unquoted = escaped.slice(1, -1).replace(/""/g, '"');
expect(decodeReachingDefReason(unquoted)).toEqual({
name: 'count',
pairs: [
{ defLine: 100, useLine: 250 },
{ defLine: 250, useLine: 400 },
],
defLine: 100,
useLine: 250,
});
});
});