src: support running an individual subtest
Similar to 'Test function at cursor', this allows running a test by placing
the cursor inside the test function or on the t.Run call. It will search
back from the current cursor position within the current outer test
function for a line that contains t.Run. If a subtest is found, an
appropriate call to runTestAtCursor is constructed and the test is
executed by existing go test functionality.
This is a port of https://github.com/microsoft/vscode-go/pull/3199
Change-Id: Ibb2d1267bd44aa370e2cf9ff6554c18f0d67815c
GitHub-Last-Rev: b8e33c04f24242727fa9b6fda1a06761cde88207
GitHub-Pull-Request: golang/vscode-go#87
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/235447
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/package.json b/package.json
index 49846aa..8fb9b57 100644
--- a/package.json
+++ b/package.json
@@ -162,6 +162,11 @@
"description": "Runs a unit test at the cursor."
},
{
+ "command": "go.subtest.cursor",
+ "title": "Go: Subtest At Cursor",
+ "description": "Runs a sub test at the cursor."
+ },
+ {
"command": "go.benchmark.cursor",
"title": "Go: Benchmark Function At Cursor",
"description": "Runs a benchmark at the cursor."
diff --git a/src/goMain.ts b/src/goMain.ts
index 3451bd5..732f424 100644
--- a/src/goMain.ts
+++ b/src/goMain.ts
@@ -38,7 +38,7 @@
import { GoReferencesCodeLensProvider } from './goReferencesCodelens';
import { GoRunTestCodeLensProvider } from './goRunTestCodelens';
import { outputChannel, showHideStatus } from './goStatus';
-import { testAtCursor, testCurrentFile, testCurrentPackage, testPrevious, testWorkspace } from './goTest';
+import { subTestAtCursor, testAtCursor, testCurrentFile, testCurrentPackage, testPrevious, testWorkspace } from './goTest';
import { getConfiguredTools } from './goTools';
import { vetCode } from './goVet';
import {
@@ -274,6 +274,13 @@
);
ctx.subscriptions.push(
+ vscode.commands.registerCommand('go.subtest.cursor', (args) => {
+ const goConfig = getGoConfig();
+ subTestAtCursor(goConfig, args);
+ })
+ );
+
+ ctx.subscriptions.push(
vscode.commands.registerCommand('go.debug.cursor', (args) => {
const goConfig = getGoConfig();
testAtCursor(goConfig, 'debug', args);
diff --git a/src/goTest.ts b/src/goTest.ts
index cc76650..a523a6e 100644
--- a/src/goTest.ts
+++ b/src/goTest.ts
@@ -108,6 +108,66 @@
}
/**
+ * Executes the sub unit test at the primary cursor using `go test`. Output
+ * is sent to the 'Go' channel.
+ *
+ * @param goConfig Configuration for the Go extension.
+ */
+export async function subTestAtCursor(goConfig: vscode.WorkspaceConfiguration, args: any) {
+ const editor = vscode.window.activeTextEditor;
+ if (!editor) {
+ vscode.window.showInformationMessage('No editor is active.');
+ return;
+ }
+ if (!editor.document.fileName.endsWith('_test.go')) {
+ vscode.window.showInformationMessage('No tests found. Current file is not a test file.');
+ return;
+ }
+
+ await editor.document.save();
+ try {
+ const testFunctions = await getTestFunctions(editor.document, null);
+ // We use functionName if it was provided as argument
+ // Otherwise find any test function containing the cursor.
+ const currentTestFunctions = testFunctions.filter((func) => func.range.contains(editor.selection.start));
+ const testFunctionName =
+ args && args.functionName ? args.functionName : currentTestFunctions.map((el) => el.name)[0];
+
+ if (!testFunctionName || currentTestFunctions.length === 0) {
+ vscode.window.showInformationMessage('No test function found at cursor.');
+ return;
+ }
+
+ const testFunction = currentTestFunctions[0];
+ const simpleRunRegex = /t.Run\("([^"]+)",/;
+ const runRegex = /t.Run\(/;
+ let lineText: string;
+ let runMatch: RegExpMatchArray | null;
+ let simpleMatch: RegExpMatchArray | null;
+ for (let i = editor.selection.start.line; i >= testFunction.range.start.line; i--) {
+ lineText = editor.document.lineAt(i).text;
+ simpleMatch = lineText.match(simpleRunRegex);
+ runMatch = lineText.match(runRegex);
+ if (simpleMatch || (runMatch && !simpleMatch)) {
+ break;
+ }
+ }
+
+ if (!simpleMatch) {
+ vscode.window.showInformationMessage('No subtest function with a simple subtest name found at cursor.');
+ return;
+ }
+
+ const subTestName = testFunctionName + '/' + simpleMatch[1];
+
+ return await runTestAtCursor(editor, subTestName, testFunctions, goConfig, 'test', args);
+ } catch (err) {
+ vscode.window.showInformationMessage('Unable to run subtest: ' + err.toString());
+ console.error(err);
+ }
+}
+
+/**
* Debugs the test at cursor.
*/
async function debugTestAtCursor(
diff --git a/src/testUtils.ts b/src/testUtils.ts
index c318b0f..d1a97f4 100644
--- a/src/testUtils.ts
+++ b/src/testUtils.ts
@@ -463,7 +463,11 @@
// in running all the test methods, but one of them should call testify's `suite.Run(...)`
// which will result in the correct thing to happen
if (testFunctions.length > 0) {
- params = params.concat(['-run', util.format('^(%s)$', testFunctions.join('|'))]);
+ if (testFunctions.length === 1) {
+ params = params.concat(['-run', util.format('^%s$', testFunctions.pop())]);
+ } else {
+ params = params.concat(['-run', util.format('^(%s)$', testFunctions.join('|'))]);
+ }
}
if (testifyMethods.length > 0) {
params = params.concat(['-testify.m', util.format('^(%s)$', testifyMethods.join('|'))]);
diff --git a/test/fixtures/subtests/go.mod b/test/fixtures/subtests/go.mod
new file mode 100644
index 0000000..1455032
--- /dev/null
+++ b/test/fixtures/subtests/go.mod
@@ -0,0 +1,3 @@
+module github.com/microsoft/vscode-go/gofixtures/subtests
+
+go 1.14
diff --git a/test/fixtures/subtests/subtests_test.go b/test/fixtures/subtests/subtests_test.go
new file mode 100644
index 0000000..3481139
--- /dev/null
+++ b/test/fixtures/subtests/subtests_test.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+ "testing"
+)
+
+func TestSample(t *testing.T) {
+ t.Run("sample test passing", func(t *testing.T) {
+
+ })
+
+ t.Run("sample test failing", func(t *testing.T) {
+ t.FailNow()
+ })
+
+ testName := "dynamic test name"
+ t.Run(testName, func(t *testing.T) {
+ t.FailNow()
+ })
+}
diff --git a/test/integration/extension.test.ts b/test/integration/extension.test.ts
index f0ed8a7..1299a92 100644
--- a/test/integration/extension.test.ts
+++ b/test/integration/extension.test.ts
@@ -28,7 +28,7 @@
import { GoSignatureHelpProvider } from '../../src/goSignature';
import { GoCompletionItemProvider } from '../../src/goSuggest';
import { getWorkspaceSymbols } from '../../src/goSymbol';
-import { testCurrentFile } from '../../src/goTest';
+import { subTestAtCursor, testCurrentFile } from '../../src/goTest';
import {
getBinPath,
getCurrentGoPath,
@@ -113,6 +113,10 @@
path.join(fixtureSourcePath, 'diffTestData', 'file2.go'),
path.join(fixturePath, 'diffTest2Data', 'file2.go')
);
+ fs.copySync(
+ path.join(fixtureSourcePath, 'subtests', 'subtests_test.go'),
+ path.join(fixturePath, 'subtests', 'subtests_test.go')
+ );
});
suiteTeardown(() => {
@@ -1538,4 +1542,76 @@
await runFillStruct(editor);
assert.equal(vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.getText(), golden);
});
+
+ test('Subtests - runs a test with cursor on t.Run line', async () => {
+ const config = vscode.workspace.getConfiguration('go');
+ const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
+ const document = await vscode.workspace.openTextDocument(uri);
+ const editor = await vscode.window.showTextDocument(document);
+ const selection = new vscode.Selection(7, 4, 7, 4);
+ editor.selection = selection;
+
+ const result = await subTestAtCursor(config, []);
+ assert.equal(result, true);
+ });
+
+ test('Subtests - runs a test with cursor within t.Run function', async () => {
+ const config = vscode.workspace.getConfiguration('go');
+ const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
+ const document = await vscode.workspace.openTextDocument(uri);
+ const editor = await vscode.window.showTextDocument(document);
+ const selection = new vscode.Selection(8, 4, 8, 4);
+ editor.selection = selection;
+
+ const result = await subTestAtCursor(config, []);
+ assert.equal(result, true);
+ });
+
+ test('Subtests - returns false for a failing test', async () => {
+ const config = vscode.workspace.getConfiguration('go');
+ const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
+ const document = await vscode.workspace.openTextDocument(uri);
+ const editor = await vscode.window.showTextDocument(document);
+ const selection = new vscode.Selection(11, 4, 11, 4);
+ editor.selection = selection;
+
+ const result = await subTestAtCursor(config, []);
+ assert.equal(result, false);
+ });
+
+ test('Subtests - does nothing for a dynamically defined subtest', async () => {
+ const config = vscode.workspace.getConfiguration('go');
+ const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
+ const document = await vscode.workspace.openTextDocument(uri);
+ const editor = await vscode.window.showTextDocument(document);
+ const selection = new vscode.Selection(17, 4, 17, 4);
+ editor.selection = selection;
+
+ const result = await subTestAtCursor(config, []);
+ assert.equal(result, undefined);
+ });
+
+ test('Subtests - does nothing when cursor outside of a test function', async () => {
+ const config = vscode.workspace.getConfiguration('go');
+ const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
+ const document = await vscode.workspace.openTextDocument(uri);
+ const editor = await vscode.window.showTextDocument(document);
+ const selection = new vscode.Selection(5, 0, 5, 0);
+ editor.selection = selection;
+
+ const result = await subTestAtCursor(config, []);
+ assert.equal(result, undefined);
+ });
+
+ test('Subtests - does nothing when no test function covers the cursor and a function name is passed in', async () => {
+ const config = vscode.workspace.getConfiguration('go');
+ const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
+ const document = await vscode.workspace.openTextDocument(uri);
+ const editor = await vscode.window.showTextDocument(document);
+ const selection = new vscode.Selection(5, 0, 5, 0);
+ editor.selection = selection;
+
+ const result = await subTestAtCursor(config, {functionName: 'TestMyFunction'});
+ assert.equal(result, undefined);
+ });
});