Skip to content

Commit

Permalink
src/goTest: add single-test debugging
Browse files Browse the repository at this point in the history
Change-Id: I2dac932cfe0854d57157353d5de6eae89158d87d
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/348571
Trust: Hyang-Ah Hana Kim <[email protected]>
Trust: Suzy Mueller <[email protected]>
Run-TryBot: Hyang-Ah Hana Kim <[email protected]>
Reviewed-by: Suzy Mueller <[email protected]>
  • Loading branch information
firelizzard18 authored and hyangah committed Oct 11, 2021
1 parent afc7d07 commit 1f8bf32
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 8 deletions.
24 changes: 17 additions & 7 deletions src/goTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,14 +207,23 @@ export async function subTestAtCursor(goConfig: vscode.WorkspaceConfiguration, a

/**
* Debugs the test at cursor.
* @param editorOrDocument The text document (or editor) that defines the test.
* @param testFunctionName The name of the test function.
* @param testFunctions All test function symbols defined by the document.
* @param goConfig Go configuration, i.e. flags, tags, environment, etc.
* @param sessionID If specified, `sessionID` is added to the debug
* configuration and can be used to identify the debug session.
* @returns Whether the debug session was successfully started.
*/
async function debugTestAtCursor(
editor: vscode.TextEditor,
export async function debugTestAtCursor(
editorOrDocument: vscode.TextEditor | vscode.TextDocument,
testFunctionName: string,
testFunctions: vscode.DocumentSymbol[],
goConfig: vscode.WorkspaceConfiguration
goConfig: vscode.WorkspaceConfiguration,
sessionID?: string
) {
const args = getTestFunctionDebugArgs(editor.document, testFunctionName, testFunctions);
const doc = 'document' in editorOrDocument ? editorOrDocument.document : editorOrDocument;
const args = getTestFunctionDebugArgs(doc, testFunctionName, testFunctions);
const tags = getTestTags(goConfig);
const buildFlags = tags ? ['-tags', tags] : [];
const flagsFromConfig = getTestFlags(goConfig);
Expand All @@ -230,17 +239,18 @@ async function debugTestAtCursor(
}
buildFlags.push(x);
});
const workspaceFolder = vscode.workspace.getWorkspaceFolder(editor.document.uri);
const workspaceFolder = vscode.workspace.getWorkspaceFolder(doc.uri);
const debugConfig: vscode.DebugConfiguration = {
name: 'Debug Test',
type: 'go',
request: 'launch',
mode: 'test',
program: path.dirname(editor.document.fileName),
program: path.dirname(doc.fileName),
env: goConfig.get('testEnvVars', {}),
envFile: goConfig.get('testEnvFile'),
args,
buildFlags: buildFlags.join(' ')
buildFlags: buildFlags.join(' '),
sessionID
};
lastDebugConfig = debugConfig;
lastDebugWorkspaceFolder = workspaceFolder;
Expand Down
106 changes: 105 additions & 1 deletion src/goTest/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------*/
import {
CancellationToken,
DebugSession,
Location,
OutputChannel,
Position,
Expand All @@ -20,10 +21,13 @@ import vscode = require('vscode');
import { outputChannel } from '../goStatus';
import { isModSupported } from '../goModules';
import { getGoConfig } from '../config';
import { getTestFlags, goTest, GoTestOutput } from '../testUtils';
import { getBenchmarkFunctions, getTestFlags, getTestFunctions, goTest, GoTestOutput } from '../testUtils';
import { GoTestResolver } from './resolve';
import { dispose, forEachAsync, GoTest, Workspace } from './utils';
import { GoTestProfiler, ProfilingOptions } from './profile';
import { debugTestAtCursor } from '../goTest';

let debugSessionID = 0;

type CollectedTest = { item: TestItem; explicitlyIncluded?: boolean };

Expand Down Expand Up @@ -90,6 +94,21 @@ export class GoTestRunner {
true
);

ctrl.createRunProfile(
'Go (Debug)',
TestRunProfileKind.Debug,
async (request, token) => {
try {
await this.debug(request, token);
} catch (error) {
const m = 'Failed to debug tests';
outputChannel.appendLine(`${m}: ${error}`);
await vscode.window.showErrorMessage(m);
}
},
true
);

const pprof = ctrl.createRunProfile(
'Go (Profile)',
TestRunProfileKind.Run,
Expand All @@ -112,6 +131,91 @@ export class GoTestRunner {
};
}

async debug(request: TestRunRequest, token?: CancellationToken) {
if (!request.include) {
await vscode.window.showErrorMessage('The Go test explorer does not support debugging multiple tests');
return;
}

const collected = new Map<TestItem, CollectedTest[]>();
const files = new Set<TestItem>();
for (const item of request.include) {
await this.collectTests(item, true, request.exclude || [], collected, files);
}

const tests = Array.from(collected.values()).reduce((a, b) => a.concat(b), []);
if (tests.length > 1) {
await vscode.window.showErrorMessage('The Go test explorer does not support debugging multiple tests');
return;
}

const test = tests[0].item;
const { kind, name } = GoTest.parseId(test.id);
const doc = await vscode.workspace.openTextDocument(test.uri);
await doc.save();

const goConfig = getGoConfig(test.uri);
const getFunctions = kind === 'benchmark' ? getBenchmarkFunctions : getTestFunctions;
const testFunctions = await getFunctions(doc, token);

// TODO Can we get output from the debug session, in order to check for
// run/pass/fail events?

const id = `debug #${debugSessionID++} ${name}`;
const subs: vscode.Disposable[] = [];
const sessionPromise = new Promise<DebugSession>((resolve) => {
subs.push(
vscode.debug.onDidStartDebugSession((s) => {
if (s.configuration.sessionID === id) {
resolve(s);
subs.forEach((s) => s.dispose());
}
})
);

if (token) {
subs.push(
token.onCancellationRequested(() => {
resolve(null);
subs.forEach((s) => s.dispose());
})
);
}
});

const run = this.ctrl.createTestRun(request, `Debug ${name}`);
const started = await debugTestAtCursor(doc, name, testFunctions, goConfig, id);
if (!started) {
subs.forEach((s) => s.dispose());
run.end();
return;
}

const session = await sessionPromise;
if (!session) {
run.end();
return;
}

token.onCancellationRequested(() => vscode.debug.stopDebugging(session));

await new Promise<void>((resolve) => {
const sub = vscode.debug.onDidTerminateDebugSession(didTerminateSession);

token?.onCancellationRequested(() => {
resolve();
sub.dispose();
});

function didTerminateSession(s: DebugSession) {
if (s.id !== session.id) return;
resolve();
sub.dispose();
}
});
run.end();
}

// Execute tests - TestController.runTest callback
async run(request: TestRunRequest, token?: CancellationToken, options: ProfilingOptions = {}): Promise<boolean> {
const collected = new Map<TestItem, CollectedTest[]>();
Expand Down

0 comments on commit 1f8bf32

Please sign in to comment.