@@ -55,6 +55,14 @@ interface DynamicEdge {
5555 target_file: string ;
5656}
5757
58+ interface TracerArtifact {
59+ // `skipped` means the tracer's toolchain was unavailable (no edges captured,
60+ // not a recall failure). `ok` means the tracer ran — the edges array may
61+ // still be empty if the language's tracer legitimately produced no calls.
62+ status: 'ok' | 'skipped' ;
63+ edges: DynamicEdge [ ] ;
64+ }
65+
5866interface LangResult {
5967 precision: number ;
6068 recall: number ;
@@ -68,6 +76,10 @@ interface LangResult {
6876 // instead of rebuilding fixtures from scratch (see issue #1052).
6977 falsePositiveEdges: string [ ] ;
7078 falseNegativeEdges: string [ ] ;
79+ // Raw tracer output so tests/benchmarks/resolution/tracer/tracer-validation.test.ts
80+ // can reuse it instead of re-running the per-language tracer subprocess
81+ // (see issue #1166).
82+ tracer: TracerArtifact ;
7183 dynamicEdges ? : number ;
7284 dynamicConfirmed ? : number ;
7385}
@@ -99,7 +111,10 @@ function edgeKey(sourceName: string, sourceFile: string, targetName: string, tar
99111 return `${ sourceName } @${ normalizeFile ( sourceFile ) } -> ${ targetName } @${ normalizeFile ( targetFile ) } ` ;
100112}
101113
102- function computeMetrics ( resolvedEdges : ResolvedEdge [ ] , expectedEdges : ExpectedEdge [ ] ) : LangResult {
114+ function computeMetrics (
115+ resolvedEdges : ResolvedEdge [ ] ,
116+ expectedEdges : ExpectedEdge [ ] ,
117+ ) : Omit < LangResult , 'tracer' | 'dynamicEdges' | 'dynamicConfirmed' > {
103118 const resolvedSet = new Set (
104119 resolvedEdges . map ( ( e ) => edgeKey ( e . source_name , e . source_file , e . target_name , e . target_file ) ) ,
105120 ) ;
@@ -162,10 +177,15 @@ const TRACER_SCRIPT = path.join(root, 'tests', 'benchmarks', 'resolution', 'trac
162177
163178/**
164179 * Attempt to run the dynamic call tracer for a language fixture.
165- * Returns captured edges on success, empty array on failure or unavailability.
180+ *
181+ * Returns `{ status: 'ok', edges }` on a successful run (edges may be empty if
182+ * the tracer produced no calls) and `{ status: 'skipped', edges: [] }` when
183+ * the tracer subprocess failed or its toolchain wasn't available. The status
184+ * distinction lets tests reusing this artifact (see issue #1166) preserve the
185+ * tracer-validation suite's skip-on-toolchain-missing behavior.
166186 */
167- function runDynamicTracer ( lang : string ) : DynamicEdge [ ] {
168- if ( ! fs . existsSync ( TRACER_SCRIPT ) ) return [ ] ;
187+ function runDynamicTracer ( lang : string ) : TracerArtifact {
188+ if ( ! fs . existsSync ( TRACER_SCRIPT ) ) return { status : 'skipped' , edges : [ ] } ;
169189
170190 const fixtureDir = path . join ( FIXTURES_DIR , lang ) ;
171191 try {
@@ -176,12 +196,17 @@ function runDynamicTracer(lang: string): DynamicEdge[] {
176196 stdio : [ 'pipe' , 'pipe' , 'pipe' ] ,
177197 } ) ;
178198 const parsed = JSON . parse ( result ) ;
179- if ( parsed . error ) {
199+ const edges : DynamicEdge [ ] = Array . isArray ( parsed . edges ) ? parsed . edges : [ ] ;
200+ // Mirror tracer-validation.test.ts: when run-tracer.mjs reports an error
201+ // and produced no edges, treat the run as a toolchain-missing skip
202+ // rather than a recall failure.
203+ if ( parsed . error && edges . length === 0 ) {
180204 console . error ( ` Dynamic tracer for ${ lang } : ${ parsed . error } ` ) ;
205+ return { status : 'skipped' , edges : [ ] } ;
181206 }
182- return Array . isArray ( parsed . edges ) ? parsed . edges : [ ] ;
207+ return { status : 'ok' , edges } ;
183208 } catch {
184- return [ ] ;
209+ return { status : 'skipped' , edges : [ ] } ;
185210 }
186211}
187212
@@ -285,20 +310,23 @@ try {
285310 const expectedEdges : ExpectedEdge [ ] = manifest . edges ;
286311
287312 // Run dynamic tracer if available
288- const dynamicEdges = runDynamicTracer ( lang ) ;
289- const { dynamicConfirmed } = mergeWithDynamic ( expectedEdges , dynamicEdges ) ;
313+ const tracer = runDynamicTracer ( lang ) ;
314+ const { dynamicConfirmed } = mergeWithDynamic ( expectedEdges , tracer . edges ) ;
290315
291316 // Use only expected edges for metrics (dynamic edges are supplemental)
292- const metrics = computeMetrics ( resolvedEdges , expectedEdges ) ;
293- if ( dynamicEdges . length > 0 ) {
294- metrics . dynamicEdges = dynamicEdges . length ;
317+ const metrics : LangResult = {
318+ ...computeMetrics ( resolvedEdges , expectedEdges ) ,
319+ tracer,
320+ } ;
321+ if ( tracer . edges . length > 0 ) {
322+ metrics . dynamicEdges = tracer . edges . length ;
295323 metrics . dynamicConfirmed = dynamicConfirmed ;
296324 }
297325 results [ lang ] = metrics ;
298326
299327 const dynamicInfo =
300- dynamicEdges . length > 0
301- ? ` dynamic=${ dynamicEdges . length } confirmed=${ dynamicConfirmed } `
328+ tracer . edges . length > 0
329+ ? ` dynamic=${ tracer . edges . length } confirmed=${ dynamicConfirmed } `
302330 : '' ;
303331 console . error (
304332 ` ${ lang } : precision=${ ( metrics . precision * 100 ) . toFixed ( 1 ) } % recall=${ ( metrics . recall * 100 ) . toFixed ( 1 ) } %${ dynamicInfo } ` ,
0 commit comments