1
1
import * as net from "node:net" ;
2
+ import * as os from "node:os" ;
2
3
3
4
import * as vscode from "vscode" ;
4
5
@@ -48,6 +49,11 @@ type MochaEvent =
48
49
| [ "pending" , MochaTestEvent ]
49
50
| [ "test-start" , MochaTestEvent ] ;
50
51
52
+ enum DebugMode {
53
+ JS ,
54
+ NATIVE_AND_JS ,
55
+ }
56
+
51
57
export function createTestController (
52
58
context : vscode . ExtensionContext ,
53
59
electronRoot : vscode . Uri ,
@@ -82,7 +88,7 @@ export function createTestController(
82
88
async function runTests (
83
89
request : vscode . TestRunRequest ,
84
90
token : vscode . CancellationToken ,
85
- debug : boolean = false ,
91
+ debug ?: DebugMode ,
86
92
) {
87
93
const extraArgs = request . profile
88
94
? runProfileData . get ( request . profile )
@@ -129,6 +135,18 @@ export function createTestController(
129
135
}
130
136
} ) ;
131
137
138
+ const processIdFilename = vscode . Uri . joinPath (
139
+ context . storageUri ! ,
140
+ ".native-test-debugging-process-id" ,
141
+ ) ;
142
+
143
+ const env : Record < string , string > = {
144
+ MOCHA_REPORTER : "mocha-multi-reporters" ,
145
+ MOCHA_MULTI_REPORTERS : `${ context . asAbsolutePath (
146
+ "out/electron/mocha-reporter.js" ,
147
+ ) } , spec`,
148
+ ELECTRON_ROOT : electronRoot . fsPath ,
149
+ } ;
132
150
let command = `${ buildToolsExecutable } test --runners=main` ;
133
151
134
152
if ( testRegexes . length ) {
@@ -139,10 +157,15 @@ export function createTestController(
139
157
command += ` ${ extraArgs } ` ;
140
158
}
141
159
142
- if ( debug ) {
160
+ if ( debug !== undefined ) {
143
161
command += ` --inspect-brk` ;
144
162
}
145
163
164
+ if ( debug === DebugMode . NATIVE_AND_JS ) {
165
+ command += ` --wait-for-debugger` ;
166
+ env . ELECTRON_TEST_PID_DUMP_PATH = processIdFilename . fsPath ;
167
+ }
168
+
146
169
// Mark all tests we're about to run as enqueued
147
170
for ( const test of testsById . values ( ) ) {
148
171
run . enqueued ( test ) ;
@@ -158,13 +181,7 @@ export function createTestController(
158
181
cancellationToken : token ,
159
182
shellOptions : {
160
183
cwd : electronRoot . fsPath ,
161
- env : {
162
- MOCHA_REPORTER : "mocha-multi-reporters" ,
163
- MOCHA_MULTI_REPORTERS : `${ context . asAbsolutePath (
164
- "out/electron/mocha-reporter.js" ,
165
- ) } , spec`,
166
- ELECTRON_ROOT : electronRoot . fsPath ,
167
- } ,
184
+ env,
168
185
} ,
169
186
// Ignore non-zero exit codes, there's no way to
170
187
// distinguish from normal test failures
@@ -238,7 +255,73 @@ export function createTestController(
238
255
}
239
256
} ) ;
240
257
241
- if ( debug ) {
258
+ if ( debug === DebugMode . NATIVE_AND_JS ) {
259
+ // Directory may not exist so create it first
260
+ await vscode . workspace . fs . createDirectory ( context . storageUri ! ) ;
261
+
262
+ await vscode . workspace . fs . writeFile (
263
+ processIdFilename ,
264
+ new TextEncoder ( ) . encode ( "0" ) ,
265
+ ) ;
266
+
267
+ // Watch for changes to the PID dump file so we know when
268
+ // the Electron process is started and we can attach to it
269
+ const watcher = vscode . workspace . createFileSystemWatcher (
270
+ new vscode . RelativePattern (
271
+ context . storageUri ! ,
272
+ ".native-test-debugging-process-id" ,
273
+ ) ,
274
+ ) ;
275
+ const processId = await new Promise < number > ( ( resolve , reject ) => {
276
+ const timeoutId = setTimeout ( reject , 10_000 ) ;
277
+
278
+ watcher . onDidChange ( async ( uri ) => {
279
+ clearTimeout ( timeoutId ) ;
280
+ watcher . dispose ( ) ;
281
+
282
+ resolve (
283
+ parseInt ( ( await vscode . workspace . fs . readFile ( uri ) ) . toString ( ) ) ,
284
+ ) ;
285
+ } ) ;
286
+ } ) ;
287
+
288
+ const nativeDebuggingConfigurationType =
289
+ os . platform ( ) === "win32"
290
+ ? "electron.cpp.windows"
291
+ : os . platform ( ) === "darwin"
292
+ ? "electron.cpp.lldb"
293
+ : "electron.cpp.gdb" ;
294
+ const nativeDebuggingConfiguration =
295
+ await context . extension . packageJSON . contributes . debuggers
296
+ . find ( ( { type } ) => type === nativeDebuggingConfigurationType )
297
+ ?. initialConfigurations . find ( ( { request } ) => request === "attach" ) ;
298
+
299
+ if ( ! nativeDebuggingConfiguration ) {
300
+ testRunError = new vscode . TestMessage (
301
+ "Couldn't find native debugging configuration" ,
302
+ ) ;
303
+ task . terminate ( ) ;
304
+ return ;
305
+ }
306
+
307
+ const nativeDebuggingSession = await vscode . debug . startDebugging (
308
+ undefined ,
309
+ {
310
+ ...nativeDebuggingConfiguration ,
311
+ processId,
312
+ } ,
313
+ { testRun : run } ,
314
+ ) ;
315
+
316
+ if ( ! nativeDebuggingSession ) {
317
+ testRunError = new vscode . TestMessage (
318
+ "Couldn't start native debugging session" ,
319
+ ) ;
320
+ task . terminate ( ) ;
321
+ }
322
+ }
323
+
324
+ if ( testRunError === undefined && debug !== undefined ) {
242
325
const debuggingSession = await vscode . debug . startDebugging (
243
326
undefined ,
244
327
{
@@ -248,7 +331,7 @@ export function createTestController(
248
331
port : 9229 ,
249
332
continueOnAttach : true ,
250
333
} ,
251
- { testRun : run } ,
334
+ { testRun : run , parentSession : vscode . debug . activeDebugSession } ,
252
335
) ;
253
336
254
337
if ( ! debuggingSession ) {
@@ -281,33 +364,66 @@ export function createTestController(
281
364
}
282
365
}
283
366
284
- const runProfile = testController . createRunProfile (
285
- "Run" ,
286
- vscode . TestRunProfileKind . Run ,
287
- async ( request , token ) => {
288
- return ExtensionState . runOperation (
289
- ExtensionOperation . RUN_TESTS ,
290
- ( ) => runTests ( request , token ) ,
291
- ExtensionState . isOperationRunning ( ExtensionOperation . RUN_TESTS ) ,
292
- ) ;
293
- } ,
294
- true ,
295
- ) ;
367
+ const profiles = [
368
+ testController . createRunProfile (
369
+ "Run" ,
370
+ vscode . TestRunProfileKind . Run ,
371
+ async ( request , token ) => {
372
+ return ExtensionState . runOperation (
373
+ ExtensionOperation . RUN_TESTS ,
374
+ ( ) => runTests ( request , token ) ,
375
+ ExtensionState . isOperationRunning ( ExtensionOperation . RUN_TESTS ) ,
376
+ ) ;
377
+ } ,
378
+ true ,
379
+ ) ,
380
+ testController . createRunProfile (
381
+ "Debug" ,
382
+ vscode . TestRunProfileKind . Debug ,
383
+ async ( request , token ) => {
384
+ return ExtensionState . runOperation (
385
+ ExtensionOperation . RUN_TESTS ,
386
+ ( ) => runTests ( request , token , DebugMode . JS ) ,
387
+ ExtensionState . isOperationRunning ( ExtensionOperation . RUN_TESTS ) ,
388
+ ) ;
389
+ } ,
390
+ false ,
391
+ ) ,
392
+ testController . createRunProfile (
393
+ "Debug (C++ and JS)" ,
394
+ vscode . TestRunProfileKind . Debug ,
395
+ async ( request , token ) => {
396
+ if ( ! vscode . extensions . getExtension ( "ms-vscode.cpptools" ) ) {
397
+ vscode . window . showErrorMessage (
398
+ "Please install the 'ms-vscode.cpptools' extension to enable native debugging" ,
399
+ ) ;
400
+ return ;
401
+ }
296
402
297
- const debugProfile = testController . createRunProfile (
298
- "Debug" ,
299
- vscode . TestRunProfileKind . Debug ,
300
- async ( request , token ) => {
301
- return ExtensionState . runOperation (
302
- ExtensionOperation . RUN_TESTS ,
303
- ( ) => runTests ( request , token , true ) ,
304
- ExtensionState . isOperationRunning ( ExtensionOperation . RUN_TESTS ) ,
305
- ) ;
306
- } ,
307
- false ,
308
- ) ;
403
+ const specRunnerContents = await vscode . workspace . fs . readFile (
404
+ vscode . Uri . joinPath ( electronRoot , "script" , "spec-runner.js" ) ,
405
+ ) ;
406
+
407
+ if (
408
+ ! specRunnerContents . toString ( ) . includes ( "ELECTRON_TEST_PID_DUMP_PATH" )
409
+ ) {
410
+ vscode . window . showErrorMessage (
411
+ "This Electron checkout does not support native debugging - see https://github.com/electron/electron/pull/45481" ,
412
+ ) ;
413
+ return ;
414
+ }
415
+
416
+ return ExtensionState . runOperation (
417
+ ExtensionOperation . RUN_TESTS ,
418
+ ( ) => runTests ( request , token , DebugMode . NATIVE_AND_JS ) ,
419
+ ExtensionState . isOperationRunning ( ExtensionOperation . RUN_TESTS ) ,
420
+ ) ;
421
+ } ,
422
+ false ,
423
+ ) ,
424
+ ] ;
309
425
310
- for ( const profile of [ runProfile , debugProfile ] ) {
426
+ for ( const profile of profiles ) {
311
427
profile . configureHandler = async ( ) => {
312
428
const extraArgs = await vscode . window . showInputBox ( {
313
429
title : "Electron Test Runner" ,
0 commit comments