@@ -4,7 +4,6 @@ import { EventEmitter } from "node:events";
44import { afterEach , beforeAll , describe , expect , it , vi } from "vitest" ;
55
66type MockSpawnChild = EventEmitter & {
7- stdout ?: EventEmitter & { setEncoding ?: ( enc : string ) => void } ;
87 stderr ?: EventEmitter & { setEncoding ?: ( enc : string ) => void } ;
98 send ?: ( message : unknown , callback ?: ( error ?: Error | null ) => void ) => boolean ;
109 connected ?: boolean ;
@@ -13,16 +12,13 @@ type MockSpawnChild = EventEmitter & {
1312
1413function createMockSpawnChild ( ) {
1514 const child = new EventEmitter ( ) as MockSpawnChild ;
16- const stdout = new EventEmitter ( ) as MockSpawnChild [ "stdout" ] ;
17- stdout ! . setEncoding = vi . fn ( ) ;
1815 const stderr = new EventEmitter ( ) as MockSpawnChild [ "stderr" ] ;
1916 stderr ! . setEncoding = vi . fn ( ) ;
20- child . stdout = stdout ;
2117 child . stderr = stderr ;
2218 child . connected = true ;
2319 child . kill = vi . fn ( ) ;
2420 child . send = vi . fn ( ( ) => true ) ;
25- return { child, stdout , stderr } ;
21+ return { child, stderr } ;
2622}
2723
2824vi . mock ( "node:child_process" , async ( ) => {
@@ -43,42 +39,52 @@ vi.mock("node:child_process", async () => {
4339
4440const spawnMock = vi . mocked ( spawn ) ;
4541
46- let testing : typeof import ( "./tool-search.js" ) . testing ;
42+ let toolSearch : typeof import ( "./tool-search.js" ) ;
4743
4844describe ( "tool-search code-mode stream errors" , ( ) => {
4945 beforeAll ( async ( ) => {
50- testing = ( await import ( "./tool-search.js" ) ) . testing ;
46+ toolSearch = await import ( "./tool-search.js" ) ;
5147 } ) ;
5248
5349 afterEach ( ( ) => {
54- testing . setToolSearchCodeModeSupportedForTest ( undefined ) ;
55- testing . setToolSearchMinCodeTimeoutMsForTest ( undefined ) ;
50+ toolSearch . testing . setToolSearchCodeModeSupportedForTest ( undefined ) ;
51+ toolSearch . testing . setToolSearchMinCodeTimeoutMsForTest ( undefined ) ;
5652 } ) ;
5753
58- it ( "rejects when the code-mode child stderr emits an error " , async ( ) => {
59- testing . setToolSearchCodeModeSupportedForTest ( true ) ;
60- testing . setToolSearchMinCodeTimeoutMsForTest ( 1000 ) ;
54+ it ( "rejects stderr errors and leaves the unused stdout unpiped " , async ( ) => {
55+ toolSearch . testing . setToolSearchCodeModeSupportedForTest ( true ) ;
56+ toolSearch . testing . setToolSearchMinCodeTimeoutMsForTest ( 1000 ) ;
6157
58+ let spawnedChild : MockSpawnChild | undefined ;
6259 spawnMock . mockImplementationOnce (
6360 ( _command : string , _args : readonly string [ ] , _options : SpawnOptions ) : ChildProcess => {
6461 const { child, stderr } = createMockSpawnChild ( ) ;
62+ spawnedChild = child ;
6563 process . nextTick ( ( ) => {
6664 stderr ?. emit ( "error" , new Error ( "stderr read failed" ) ) ;
6765 } ) ;
6866 return child as unknown as ChildProcess ;
6967 } ,
7068 ) ;
7169
72- const runtime = new testing . ToolSearchRuntime ( { } , testing . resolveToolSearchConfig ( { } ) ) ;
70+ const runtime = new toolSearch . ToolSearchRuntime (
71+ { } ,
72+ toolSearch . testing . resolveToolSearchConfig ( { } ) ,
73+ ) ;
7374
7475 await expect (
75- testing . runCodeModeChild ( {
76+ toolSearch . testing . runCodeModeChild ( {
7677 code : "return 1;" ,
77- config : testing . resolveToolSearchConfig ( { } ) ,
78+ config : toolSearch . testing . resolveToolSearchConfig ( { } ) ,
7879 logs : [ ] ,
7980 parentToolCallId : "call-stderr-error" ,
8081 runtime,
8182 } ) ,
8283 ) . rejects . toThrow ( "stderr read failed" ) ;
84+ expect ( spawnMock ) . toHaveBeenCalledOnce ( ) ;
85+ expect ( spawnMock . mock . calls [ 0 ] ?. [ 2 ] ) . toMatchObject ( {
86+ stdio : [ "ignore" , "ignore" , "pipe" , "ipc" ] ,
87+ } ) ;
88+ expect ( spawnedChild ?. kill ) . toHaveBeenCalledOnce ( ) ;
8389 } ) ;
8490} ) ;
0 commit comments