Skip to content

Commit aeb0ada

Browse files
authored
feat: combined debugging test run profile (#77)
1 parent aad026e commit aeb0ada

File tree

2 files changed

+217
-36
lines changed

2 files changed

+217
-36
lines changed

package.json

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@
267267
{
268268
"type": "electron.cpp.windows",
269269
"label": "Electron: C++ (Windows)",
270+
"languages": ["c", "cpp"],
271+
"when": "electron-build-tools:ready && isWindows",
270272
"initialConfigurations": [
271273
{
272274
"name": "Electron: C++ (Windows)",
@@ -296,12 +298,26 @@
296298
"sourceFileMap": {
297299
"o:\\": "${command:electron-build-tools.show.root}\\src"
298300
}
301+
},
302+
{
303+
"name": "Attach to Electron: C++ (Windows)",
304+
"type": "cppvsdbg",
305+
"request": "attach",
306+
"program": "${command:electron-build-tools.show.exec}",
307+
"MIMode": "lldb",
308+
"processId": "${command:pickProcess}",
309+
"sourceFileMap": {
310+
"o:\\": "${command:electron-build-tools.show.root}\\src"
311+
}
299312
}
300313
]
301314
},
302315
{
303316
"type": "electron.cpp.lldb",
304317
"label": "Electron: C++ (lldb)",
318+
"languages": ["c", "cpp", "objective-c", "objective-cpp"],
319+
"when": "electron-build-tools:ready && !isWindows",
320+
"hiddenWhen": "isLinux",
305321
"initialConfigurations": [
306322
{
307323
"name": "Electron: C++ (lldb)",
@@ -340,6 +356,29 @@
340356
}
341357
],
342358
"sourceFileMap": {
359+
"../../": "${command:electron-build-tools.show.root}/src/",
360+
"gen/": "${command:electron-build-tools.show.out.path}/gen/"
361+
}
362+
},
363+
{
364+
"name": "Attach to Electron: C++ (lldb)",
365+
"type": "cppdbg",
366+
"request": "attach",
367+
"program": "${command:electron-build-tools.show.exec}",
368+
"MIMode": "lldb",
369+
"processId": "${command:pickProcess}",
370+
"setupCommands": [
371+
{
372+
"description": "Run Chromium lldbinit.py",
373+
"text": "command script import ${command:electron-build-tools.show.root}/src/tools/lldb/lldbinit.py"
374+
},
375+
{
376+
"description": "Load Blink lldb configuration",
377+
"text": "command script import ${command:electron-build-tools.show.root}/src/third_party/blink/tools/lldb/lldb_blink.py"
378+
}
379+
],
380+
"sourceFileMap": {
381+
"../../": "${command:electron-build-tools.show.root}/src/",
343382
"gen/": "${command:electron-build-tools.show.out.path}/gen/"
344383
}
345384
}
@@ -348,6 +387,9 @@
348387
{
349388
"type": "electron.cpp.gdb",
350389
"label": "Electron: C++ (gdb)",
390+
"languages": ["c", "cpp"],
391+
"when": "electron-build-tools:ready && !isWindows",
392+
"hiddenWhen": "isMac",
351393
"initialConfigurations": [
352394
{
353395
"name": "Electron: C++ (gdb)",
@@ -386,6 +428,29 @@
386428
}
387429
],
388430
"sourceFileMap": {
431+
"../../": "${command:electron-build-tools.show.root}/src/",
432+
"gen/": "${command:electron-build-tools.show.out.path}/gen/"
433+
}
434+
},
435+
{
436+
"name": "Attach to Electron: C++ (gdb)",
437+
"type": "cppdbg",
438+
"request": "attach",
439+
"program": "${command:electron-build-tools.show.exec}",
440+
"MIMode": "gdb",
441+
"processId": "${command:pickProcess}",
442+
"setupCommands": [
443+
{
444+
"description": "Load Chromium gdb configuration",
445+
"text": "-interpreter-exec console \"source -v ${command:electron-build-tools.show.root}/src/tools/gdb/gdbinit\""
446+
},
447+
{
448+
"description": "Load Blink gdb configuration",
449+
"text": "-interpreter-exec console \"python import sys; sys.path.insert(0, '${command:electron-build-tools.show.root}/src/third_party/blink/tools/gdb'); import blink\""
450+
}
451+
],
452+
"sourceFileMap": {
453+
"../../": "${command:electron-build-tools.show.root}/src/",
389454
"gen/": "${command:electron-build-tools.show.out.path}/gen/"
390455
}
391456
}

src/tests.ts

Lines changed: 152 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as net from "node:net";
2+
import * as os from "node:os";
23

34
import * as vscode from "vscode";
45

@@ -48,6 +49,11 @@ type MochaEvent =
4849
| ["pending", MochaTestEvent]
4950
| ["test-start", MochaTestEvent];
5051

52+
enum DebugMode {
53+
JS,
54+
NATIVE_AND_JS,
55+
}
56+
5157
export function createTestController(
5258
context: vscode.ExtensionContext,
5359
electronRoot: vscode.Uri,
@@ -82,7 +88,7 @@ export function createTestController(
8288
async function runTests(
8389
request: vscode.TestRunRequest,
8490
token: vscode.CancellationToken,
85-
debug: boolean = false,
91+
debug?: DebugMode,
8692
) {
8793
const extraArgs = request.profile
8894
? runProfileData.get(request.profile)
@@ -129,6 +135,18 @@ export function createTestController(
129135
}
130136
});
131137

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+
};
132150
let command = `${buildToolsExecutable} test --runners=main`;
133151

134152
if (testRegexes.length) {
@@ -139,10 +157,15 @@ export function createTestController(
139157
command += ` ${extraArgs}`;
140158
}
141159

142-
if (debug) {
160+
if (debug !== undefined) {
143161
command += ` --inspect-brk`;
144162
}
145163

164+
if (debug === DebugMode.NATIVE_AND_JS) {
165+
command += ` --wait-for-debugger`;
166+
env.ELECTRON_TEST_PID_DUMP_PATH = processIdFilename.fsPath;
167+
}
168+
146169
// Mark all tests we're about to run as enqueued
147170
for (const test of testsById.values()) {
148171
run.enqueued(test);
@@ -158,13 +181,7 @@ export function createTestController(
158181
cancellationToken: token,
159182
shellOptions: {
160183
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,
168185
},
169186
// Ignore non-zero exit codes, there's no way to
170187
// distinguish from normal test failures
@@ -238,7 +255,73 @@ export function createTestController(
238255
}
239256
});
240257

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) {
242325
const debuggingSession = await vscode.debug.startDebugging(
243326
undefined,
244327
{
@@ -248,7 +331,7 @@ export function createTestController(
248331
port: 9229,
249332
continueOnAttach: true,
250333
},
251-
{ testRun: run },
334+
{ testRun: run, parentSession: vscode.debug.activeDebugSession },
252335
);
253336

254337
if (!debuggingSession) {
@@ -281,33 +364,66 @@ export function createTestController(
281364
}
282365
}
283366

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+
}
296402

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+
];
309425

310-
for (const profile of [runProfile, debugProfile]) {
426+
for (const profile of profiles) {
311427
profile.configureHandler = async () => {
312428
const extraArgs = await vscode.window.showInputBox({
313429
title: "Electron Test Runner",

0 commit comments

Comments
 (0)