1+ import type { ChildProcess } from 'node:child_process' ;
2+ import { EventEmitter } from 'node:events' ;
3+ import { PassThrough } from 'node:stream' ;
4+ import type { InteractiveProcess , InteractiveSpawner } from '../../../execution/index.ts' ;
15import { describe , expect , it } from 'vitest' ;
26
37import { DapTransport } from '../transport.ts' ;
48import type { DapEvent , DapResponse } from '../types.ts' ;
5- import {
6- createMockInteractiveSpawner ,
7- type MockInteractiveSession ,
8- } from '../../../../test-utils/mock-executors.ts' ;
9+ type TestSession = {
10+ stdout : PassThrough ;
11+ stderr : PassThrough ;
12+ stdin : PassThrough ;
13+ emitExit : ( code ?: number | null , signal ?: NodeJS . Signals | null ) => void ;
14+ emitError : ( error : Error ) => void ;
15+ } ;
916
1017function encodeMessage ( message : Record < string , unknown > ) : string {
1118 const payload = JSON . stringify ( message ) ;
@@ -27,14 +34,73 @@ function buildResponse(
2734 } ;
2835}
2936
37+ function createTestSpawner ( ) : { spawner : InteractiveSpawner ; session : TestSession } {
38+ const stdout = new PassThrough ( ) ;
39+ const stderr = new PassThrough ( ) ;
40+ const stdin = new PassThrough ( ) ;
41+ const emitter = new EventEmitter ( ) ;
42+ const mockProcess = emitter as unknown as ChildProcess ;
43+ const mutableProcess = mockProcess as unknown as {
44+ stdout : PassThrough | null ;
45+ stderr : PassThrough | null ;
46+ stdin : PassThrough | null ;
47+ killed : boolean ;
48+ exitCode : number | null ;
49+ signalCode : NodeJS . Signals | null ;
50+ spawnargs : string [ ] ;
51+ spawnfile : string ;
52+ pid : number ;
53+ } ;
54+
55+ mutableProcess . stdout = stdout ;
56+ mutableProcess . stderr = stderr ;
57+ mutableProcess . stdin = stdin ;
58+ mutableProcess . killed = false ;
59+ mutableProcess . exitCode = null ;
60+ mutableProcess . signalCode = null ;
61+ mutableProcess . spawnargs = [ ] ;
62+ mutableProcess . spawnfile = 'mock' ;
63+ mutableProcess . pid = 12345 ;
64+ mockProcess . kill = ( ( signal ?: NodeJS . Signals ) : boolean => {
65+ mutableProcess . killed = true ;
66+ emitter . emit ( 'exit' , 0 , signal ?? null ) ;
67+ return true ;
68+ } ) as ChildProcess [ 'kill' ] ;
69+
70+ const session : TestSession = {
71+ stdout,
72+ stderr,
73+ stdin,
74+ emitExit : ( code = 0 , signal = null ) => {
75+ emitter . emit ( 'exit' , code , signal ) ;
76+ } ,
77+ emitError : ( error ) => {
78+ emitter . emit ( 'error' , error ) ;
79+ } ,
80+ } ;
81+
82+ const spawner : InteractiveSpawner = ( ) : InteractiveProcess => ( {
83+ process : mockProcess ,
84+ write ( data : string ) : void {
85+ stdin . write ( data ) ;
86+ } ,
87+ kill ( signal ?: NodeJS . Signals ) : void {
88+ mockProcess . kill ?.( signal ) ;
89+ } ,
90+ dispose ( ) : void {
91+ stdout . end ( ) ;
92+ stderr . end ( ) ;
93+ stdin . end ( ) ;
94+ emitter . removeAllListeners ( ) ;
95+ } ,
96+ } ) ;
97+
98+ return { spawner, session } ;
99+ }
100+
30101describe ( 'DapTransport framing' , ( ) => {
31102 it ( 'parses responses across chunk boundaries' , async ( ) => {
32- let session : MockInteractiveSession | null = null ;
33- const spawner = createMockInteractiveSpawner ( {
34- onSpawn : ( spawned ) => {
35- session = spawned ;
36- } ,
37- } ) ;
103+ const { spawner, session } = createTestSpawner ( ) ;
38104
39105 const transport = new DapTransport ( { spawner, adapterCommand : [ 'lldb-dap' ] } ) ;
40106
@@ -45,20 +111,15 @@ describe('DapTransport framing', () => {
45111 ) ;
46112
47113 const response = encodeMessage ( buildResponse ( 1 , 'initialize' , { ok : true } ) ) ;
48- session ? .stdout . write ( response . slice ( 0 , 12 ) ) ;
49- session ? .stdout . write ( response . slice ( 12 ) ) ;
114+ session . stdout . write ( response . slice ( 0 , 12 ) ) ;
115+ session . stdout . write ( response . slice ( 12 ) ) ;
50116
51117 await expect ( responsePromise ) . resolves . toEqual ( { ok : true } ) ;
52118 transport . dispose ( ) ;
53119 } ) ;
54120
55121 it ( 'handles multiple messages in a single chunk' , async ( ) => {
56- let session : MockInteractiveSession | null = null ;
57- const spawner = createMockInteractiveSpawner ( {
58- onSpawn : ( spawned ) => {
59- session = spawned ;
60- } ,
61- } ) ;
122+ const { spawner, session } = createTestSpawner ( ) ;
62123
63124 const transport = new DapTransport ( { spawner, adapterCommand : [ 'lldb-dap' ] } ) ;
64125 const events : DapEvent [ ] = [ ] ;
@@ -78,7 +139,7 @@ describe('DapTransport framing', () => {
78139 } ) ;
79140 const responseMessage = encodeMessage ( buildResponse ( 1 , 'threads' , { ok : true } ) ) ;
80141
81- session ? .stdout . write ( `${ eventMessage } ${ responseMessage } ` ) ;
142+ session . stdout . write ( `${ eventMessage } ${ responseMessage } ` ) ;
82143
83144 await expect ( responsePromise ) . resolves . toEqual ( { ok : true } ) ;
84145 expect ( events ) . toHaveLength ( 1 ) ;
@@ -87,12 +148,7 @@ describe('DapTransport framing', () => {
87148 } ) ;
88149
89150 it ( 'continues after invalid headers' , async ( ) => {
90- let session : MockInteractiveSession | null = null ;
91- const spawner = createMockInteractiveSpawner ( {
92- onSpawn : ( spawned ) => {
93- session = spawned ;
94- } ,
95- } ) ;
151+ const { spawner, session } = createTestSpawner ( ) ;
96152
97153 const transport = new DapTransport ( { spawner, adapterCommand : [ 'lldb-dap' ] } ) ;
98154
@@ -102,9 +158,9 @@ describe('DapTransport framing', () => {
102158 { timeoutMs : 1_000 } ,
103159 ) ;
104160
105- session ? .stdout . write ( 'Content-Length: nope\r\n\r\n' ) ;
161+ session . stdout . write ( 'Content-Length: nope\r\n\r\n' ) ;
106162 const responseMessage = encodeMessage ( buildResponse ( 1 , 'stackTrace' , { ok : true } ) ) ;
107- session ? .stdout . write ( responseMessage ) ;
163+ session . stdout . write ( responseMessage ) ;
108164
109165 await expect ( responsePromise ) . resolves . toEqual ( { ok : true } ) ;
110166 transport . dispose ( ) ;
0 commit comments