@@ -279,4 +279,235 @@ describe('eval-analysis', () => {
279279 'Could not statically resolve eval case object for evalTest call.' ,
280280 ] ) ;
281281 } ) ;
282+
283+ describe ( 'tool reference extraction' , ( ) => {
284+ it ( 'extracts tool from waitForToolCall string literal' , ( ) => {
285+ const analysis = analyzeEvalSource ( `
286+ import { evalTest } from './test-helper.js';
287+ evalTest('USUALLY_PASSES', {
288+ name: 'grep test',
289+ prompt: 'find something',
290+ assert: async (rig) => {
291+ await rig.waitForToolCall('grep_search');
292+ },
293+ });
294+ ` ) ;
295+
296+ expect ( analysis . cases [ 0 ] . toolReferences ) . toEqual ( [ 'grep_search' ] ) ;
297+ } ) ;
298+
299+ it ( 'extracts tool from toolRequest.name comparison' , ( ) => {
300+ const analysis = analyzeEvalSource ( `
301+ import { evalTest } from './test-helper.js';
302+ evalTest('USUALLY_PASSES', {
303+ name: 'shell test',
304+ prompt: 'run a command',
305+ assert: async (rig) => {
306+ const logs = rig.readToolLogs();
307+ const calls = logs.filter(
308+ (log) => log.toolRequest.name === 'run_shell_command',
309+ );
310+ },
311+ });
312+ ` ) ;
313+
314+ expect ( analysis . cases [ 0 ] . toolReferences ) . toEqual ( [ 'run_shell_command' ] ) ;
315+ } ) ;
316+
317+ it ( 'extracts multiple tools from array includes' , ( ) => {
318+ const analysis = analyzeEvalSource ( `
319+ import { evalTest } from './test-helper.js';
320+ evalTest('USUALLY_PASSES', {
321+ name: 'edit test',
322+ prompt: 'edit a file',
323+ assert: async (rig) => {
324+ const logs = rig.readToolLogs();
325+ const editCalls = logs.filter(
326+ (log) => ['write_file', 'replace'].includes(log.toolRequest.name),
327+ );
328+ },
329+ });
330+ ` ) ;
331+
332+ expect ( analysis . cases [ 0 ] . toolReferences ) . toEqual ( [
333+ 'replace' ,
334+ 'write_file' ,
335+ ] ) ;
336+ } ) ;
337+
338+ it ( 'extracts tool from imported constant' , ( ) => {
339+ const analysis = analyzeEvalSource ( `
340+ import { TRACKER_CREATE_TASK_TOOL_NAME } from '@google/gemini-cli-core';
341+ import { evalTest } from './test-helper.js';
342+ evalTest('USUALLY_PASSES', {
343+ name: 'tracker test',
344+ prompt: 'create a task',
345+ assert: async (rig) => {
346+ await rig.waitForToolCall(TRACKER_CREATE_TASK_TOOL_NAME);
347+ },
348+ });
349+ ` ) ;
350+
351+ expect ( analysis . cases [ 0 ] . toolReferences ) . toEqual ( [ 'tracker_create_task' ] ) ;
352+ } ) ;
353+
354+ it ( 'deduplicates references within a case' , ( ) => {
355+ const analysis = analyzeEvalSource ( `
356+ import { evalTest } from './test-helper.js';
357+ evalTest('USUALLY_PASSES', {
358+ name: 'dedup test',
359+ prompt: 'search twice',
360+ assert: async (rig) => {
361+ await rig.waitForToolCall('grep_search');
362+ const logs = rig.readToolLogs();
363+ const calls = logs.filter(
364+ (log) => log.toolRequest.name === 'grep_search',
365+ );
366+ },
367+ });
368+ ` ) ;
369+
370+ expect ( analysis . cases [ 0 ] . toolReferences ) . toEqual ( [ 'grep_search' ] ) ;
371+ } ) ;
372+
373+ it ( 'sorts references alphabetically' , ( ) => {
374+ const analysis = analyzeEvalSource ( `
375+ import { evalTest } from './test-helper.js';
376+ evalTest('USUALLY_PASSES', {
377+ name: 'sorted test',
378+ prompt: 'do things',
379+ assert: async (rig) => {
380+ await rig.waitForToolCall('write_file');
381+ await rig.waitForToolCall('grep_search');
382+ await rig.waitForToolCall('glob');
383+ },
384+ });
385+ ` ) ;
386+
387+ expect ( analysis . cases [ 0 ] . toolReferences ) . toEqual ( [
388+ 'glob' ,
389+ 'grep_search' ,
390+ 'write_file' ,
391+ ] ) ;
392+ } ) ;
393+
394+ it ( 'returns empty array when no tool refs found' , ( ) => {
395+ const analysis = analyzeEvalSource ( `
396+ import { evalTest } from './test-helper.js';
397+ evalTest('USUALLY_PASSES', {
398+ name: 'no tools',
399+ prompt: 'just answer',
400+ assert: async (rig, result) => {
401+ expect(result).toContain('hello');
402+ },
403+ });
404+ ` ) ;
405+
406+ expect ( analysis . cases [ 0 ] . toolReferences ) . toEqual ( [ ] ) ;
407+ } ) ;
408+
409+ it ( 'aggregates file-level toolReferences across cases' , ( ) => {
410+ const analysis = analyzeEvalSource ( `
411+ import { evalTest } from './test-helper.js';
412+ evalTest('USUALLY_PASSES', {
413+ name: 'case 1',
414+ prompt: 'first',
415+ assert: async (rig) => {
416+ await rig.waitForToolCall('grep_search');
417+ },
418+ });
419+ evalTest('USUALLY_PASSES', {
420+ name: 'case 2',
421+ prompt: 'second',
422+ assert: async (rig) => {
423+ await rig.waitForToolCall('write_file');
424+ },
425+ });
426+ ` ) ;
427+
428+ expect ( analysis . toolReferences ) . toEqual ( [ 'grep_search' , 'write_file' ] ) ;
429+ } ) ;
430+
431+ it ( 'deduplicates file-level toolReferences' , ( ) => {
432+ const analysis = analyzeEvalSource ( `
433+ import { evalTest } from './test-helper.js';
434+ evalTest('USUALLY_PASSES', {
435+ name: 'case 1',
436+ prompt: 'first',
437+ assert: async (rig) => {
438+ await rig.waitForToolCall('grep_search');
439+ },
440+ });
441+ evalTest('USUALLY_PASSES', {
442+ name: 'case 2',
443+ prompt: 'second',
444+ assert: async (rig) => {
445+ await rig.waitForToolCall('grep_search');
446+ },
447+ });
448+ ` ) ;
449+
450+ expect ( analysis . toolReferences ) . toEqual ( [ 'grep_search' ] ) ;
451+ } ) ;
452+
453+ it ( 'handles aliased constant imports' , ( ) => {
454+ const analysis = analyzeEvalSource ( `
455+ import { TRACKER_CREATE_TASK_TOOL_NAME as CREATE_TOOL } from '@google/gemini-cli-core';
456+ import { evalTest } from './test-helper.js';
457+ evalTest('USUALLY_PASSES', {
458+ name: 'alias test',
459+ prompt: 'create task',
460+ assert: async (rig) => {
461+ await rig.waitForToolCall(CREATE_TOOL);
462+ },
463+ });
464+ ` ) ;
465+
466+ expect ( analysis . cases [ 0 ] . toolReferences ) . toEqual ( [ 'tracker_create_task' ] ) ;
467+ } ) ;
468+
469+ it ( 'handles reversed toolRequest.name comparison' , ( ) => {
470+ const analysis = analyzeEvalSource ( `
471+ import { evalTest } from './test-helper.js';
472+ evalTest('USUALLY_PASSES', {
473+ name: 'reversed compare',
474+ prompt: 'do something',
475+ assert: async (rig) => {
476+ const logs = rig.readToolLogs();
477+ const calls = logs.filter(
478+ (log) => 'replace' === log.toolRequest.name,
479+ );
480+ },
481+ });
482+ ` ) ;
483+
484+ expect ( analysis . cases [ 0 ] . toolReferences ) . toEqual ( [ 'replace' ] ) ;
485+ } ) ;
486+
487+ it ( 'extracts tools from real grep_search eval pattern' , ( ) => {
488+ const analysis = analyzeEvalSource (
489+ `
490+ import { describe, expect } from 'vitest';
491+ import { evalTest, TestRig } from './test-helper.js';
492+
493+ describe('grep_search_functionality', () => {
494+ evalTest('USUALLY_PASSES', {
495+ suiteName: 'default',
496+ suiteType: 'behavioral',
497+ name: 'should find a simple string in a file',
498+ files: { 'test.txt': 'hello world' },
499+ prompt: 'Find "world" in test.txt',
500+ assert: async (rig: TestRig, result: string) => {
501+ await rig.waitForToolCall('grep_search');
502+ },
503+ });
504+ });
505+ ` ,
506+ { filePath : '/repo/evals/grep_search.eval.ts' , repoRoot : '/repo' } ,
507+ ) ;
508+
509+ expect ( analysis . cases [ 0 ] . toolReferences ) . toEqual ( [ 'grep_search' ] ) ;
510+ expect ( analysis . toolReferences ) . toEqual ( [ 'grep_search' ] ) ;
511+ } ) ;
512+ } ) ;
282513} ) ;
0 commit comments