Skip to content

Commit 6a61856

Browse files
seancafferystamblerre
authored andcommitted
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 microsoft/vscode-go#3199 Change-Id: Ibb2d1267bd44aa370e2cf9ff6554c18f0d67815c GitHub-Last-Rev: b8e33c0 GitHub-Pull-Request: #87 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/235447 Reviewed-by: Rebecca Stambler <[email protected]> Reviewed-by: Hyang-Ah Hana Kim <[email protected]>
1 parent 3d575bd commit 6a61856

File tree

7 files changed

+178
-3
lines changed

7 files changed

+178
-3
lines changed

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@
161161
"title": "Go: Test Function At Cursor",
162162
"description": "Runs a unit test at the cursor."
163163
},
164+
{
165+
"command": "go.subtest.cursor",
166+
"title": "Go: Subtest At Cursor",
167+
"description": "Runs a sub test at the cursor."
168+
},
164169
{
165170
"command": "go.benchmark.cursor",
166171
"title": "Go: Benchmark Function At Cursor",

src/goMain.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import { playgroundCommand } from './goPlayground';
3838
import { GoReferencesCodeLensProvider } from './goReferencesCodelens';
3939
import { GoRunTestCodeLensProvider } from './goRunTestCodelens';
4040
import { outputChannel, showHideStatus } from './goStatus';
41-
import { testAtCursor, testCurrentFile, testCurrentPackage, testPrevious, testWorkspace } from './goTest';
41+
import { subTestAtCursor, testAtCursor, testCurrentFile, testCurrentPackage, testPrevious, testWorkspace } from './goTest';
4242
import { getConfiguredTools } from './goTools';
4343
import { vetCode } from './goVet';
4444
import {
@@ -273,6 +273,13 @@ export function activate(ctx: vscode.ExtensionContext): void {
273273
})
274274
);
275275

276+
ctx.subscriptions.push(
277+
vscode.commands.registerCommand('go.subtest.cursor', (args) => {
278+
const goConfig = getGoConfig();
279+
subTestAtCursor(goConfig, args);
280+
})
281+
);
282+
276283
ctx.subscriptions.push(
277284
vscode.commands.registerCommand('go.debug.cursor', (args) => {
278285
const goConfig = getGoConfig();

src/goTest.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,66 @@ async function runTestAtCursor(
107107
return goTest(testConfig);
108108
}
109109

110+
/**
111+
* Executes the sub unit test at the primary cursor using `go test`. Output
112+
* is sent to the 'Go' channel.
113+
*
114+
* @param goConfig Configuration for the Go extension.
115+
*/
116+
export async function subTestAtCursor(goConfig: vscode.WorkspaceConfiguration, args: any) {
117+
const editor = vscode.window.activeTextEditor;
118+
if (!editor) {
119+
vscode.window.showInformationMessage('No editor is active.');
120+
return;
121+
}
122+
if (!editor.document.fileName.endsWith('_test.go')) {
123+
vscode.window.showInformationMessage('No tests found. Current file is not a test file.');
124+
return;
125+
}
126+
127+
await editor.document.save();
128+
try {
129+
const testFunctions = await getTestFunctions(editor.document, null);
130+
// We use functionName if it was provided as argument
131+
// Otherwise find any test function containing the cursor.
132+
const currentTestFunctions = testFunctions.filter((func) => func.range.contains(editor.selection.start));
133+
const testFunctionName =
134+
args && args.functionName ? args.functionName : currentTestFunctions.map((el) => el.name)[0];
135+
136+
if (!testFunctionName || currentTestFunctions.length === 0) {
137+
vscode.window.showInformationMessage('No test function found at cursor.');
138+
return;
139+
}
140+
141+
const testFunction = currentTestFunctions[0];
142+
const simpleRunRegex = /t.Run\("([^"]+)",/;
143+
const runRegex = /t.Run\(/;
144+
let lineText: string;
145+
let runMatch: RegExpMatchArray | null;
146+
let simpleMatch: RegExpMatchArray | null;
147+
for (let i = editor.selection.start.line; i >= testFunction.range.start.line; i--) {
148+
lineText = editor.document.lineAt(i).text;
149+
simpleMatch = lineText.match(simpleRunRegex);
150+
runMatch = lineText.match(runRegex);
151+
if (simpleMatch || (runMatch && !simpleMatch)) {
152+
break;
153+
}
154+
}
155+
156+
if (!simpleMatch) {
157+
vscode.window.showInformationMessage('No subtest function with a simple subtest name found at cursor.');
158+
return;
159+
}
160+
161+
const subTestName = testFunctionName + '/' + simpleMatch[1];
162+
163+
return await runTestAtCursor(editor, subTestName, testFunctions, goConfig, 'test', args);
164+
} catch (err) {
165+
vscode.window.showInformationMessage('Unable to run subtest: ' + err.toString());
166+
console.error(err);
167+
}
168+
}
169+
110170
/**
111171
* Debugs the test at cursor.
112172
*/

src/testUtils.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,11 @@ function targetArgs(testconfig: TestConfig): Array<string> {
463463
// in running all the test methods, but one of them should call testify's `suite.Run(...)`
464464
// which will result in the correct thing to happen
465465
if (testFunctions.length > 0) {
466-
params = params.concat(['-run', util.format('^(%s)$', testFunctions.join('|'))]);
466+
if (testFunctions.length === 1) {
467+
params = params.concat(['-run', util.format('^%s$', testFunctions.pop())]);
468+
} else {
469+
params = params.concat(['-run', util.format('^(%s)$', testFunctions.join('|'))]);
470+
}
467471
}
468472
if (testifyMethods.length > 0) {
469473
params = params.concat(['-testify.m', util.format('^(%s)$', testifyMethods.join('|'))]);

test/fixtures/subtests/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/microsoft/vscode-go/gofixtures/subtests
2+
3+
go 1.14
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestSample(t *testing.T) {
8+
t.Run("sample test passing", func(t *testing.T) {
9+
10+
})
11+
12+
t.Run("sample test failing", func(t *testing.T) {
13+
t.FailNow()
14+
})
15+
16+
testName := "dynamic test name"
17+
t.Run(testName, func(t *testing.T) {
18+
t.FailNow()
19+
})
20+
}

test/integration/extension.test.ts

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { goPlay } from '../../src/goPlayground';
2828
import { GoSignatureHelpProvider } from '../../src/goSignature';
2929
import { GoCompletionItemProvider } from '../../src/goSuggest';
3030
import { getWorkspaceSymbols } from '../../src/goSymbol';
31-
import { testCurrentFile } from '../../src/goTest';
31+
import { subTestAtCursor, testCurrentFile } from '../../src/goTest';
3232
import {
3333
getBinPath,
3434
getCurrentGoPath,
@@ -113,6 +113,10 @@ suite('Go Extension Tests', function () {
113113
path.join(fixtureSourcePath, 'diffTestData', 'file2.go'),
114114
path.join(fixturePath, 'diffTest2Data', 'file2.go')
115115
);
116+
fs.copySync(
117+
path.join(fixtureSourcePath, 'subtests', 'subtests_test.go'),
118+
path.join(fixturePath, 'subtests', 'subtests_test.go')
119+
);
116120
});
117121

118122
suiteTeardown(() => {
@@ -1538,4 +1542,76 @@ encountered.
15381542
await runFillStruct(editor);
15391543
assert.equal(vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.getText(), golden);
15401544
});
1545+
1546+
test('Subtests - runs a test with cursor on t.Run line', async () => {
1547+
const config = vscode.workspace.getConfiguration('go');
1548+
const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
1549+
const document = await vscode.workspace.openTextDocument(uri);
1550+
const editor = await vscode.window.showTextDocument(document);
1551+
const selection = new vscode.Selection(7, 4, 7, 4);
1552+
editor.selection = selection;
1553+
1554+
const result = await subTestAtCursor(config, []);
1555+
assert.equal(result, true);
1556+
});
1557+
1558+
test('Subtests - runs a test with cursor within t.Run function', async () => {
1559+
const config = vscode.workspace.getConfiguration('go');
1560+
const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
1561+
const document = await vscode.workspace.openTextDocument(uri);
1562+
const editor = await vscode.window.showTextDocument(document);
1563+
const selection = new vscode.Selection(8, 4, 8, 4);
1564+
editor.selection = selection;
1565+
1566+
const result = await subTestAtCursor(config, []);
1567+
assert.equal(result, true);
1568+
});
1569+
1570+
test('Subtests - returns false for a failing test', async () => {
1571+
const config = vscode.workspace.getConfiguration('go');
1572+
const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
1573+
const document = await vscode.workspace.openTextDocument(uri);
1574+
const editor = await vscode.window.showTextDocument(document);
1575+
const selection = new vscode.Selection(11, 4, 11, 4);
1576+
editor.selection = selection;
1577+
1578+
const result = await subTestAtCursor(config, []);
1579+
assert.equal(result, false);
1580+
});
1581+
1582+
test('Subtests - does nothing for a dynamically defined subtest', async () => {
1583+
const config = vscode.workspace.getConfiguration('go');
1584+
const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
1585+
const document = await vscode.workspace.openTextDocument(uri);
1586+
const editor = await vscode.window.showTextDocument(document);
1587+
const selection = new vscode.Selection(17, 4, 17, 4);
1588+
editor.selection = selection;
1589+
1590+
const result = await subTestAtCursor(config, []);
1591+
assert.equal(result, undefined);
1592+
});
1593+
1594+
test('Subtests - does nothing when cursor outside of a test function', async () => {
1595+
const config = vscode.workspace.getConfiguration('go');
1596+
const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
1597+
const document = await vscode.workspace.openTextDocument(uri);
1598+
const editor = await vscode.window.showTextDocument(document);
1599+
const selection = new vscode.Selection(5, 0, 5, 0);
1600+
editor.selection = selection;
1601+
1602+
const result = await subTestAtCursor(config, []);
1603+
assert.equal(result, undefined);
1604+
});
1605+
1606+
test('Subtests - does nothing when no test function covers the cursor and a function name is passed in', async () => {
1607+
const config = vscode.workspace.getConfiguration('go');
1608+
const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
1609+
const document = await vscode.workspace.openTextDocument(uri);
1610+
const editor = await vscode.window.showTextDocument(document);
1611+
const selection = new vscode.Selection(5, 0, 5, 0);
1612+
editor.selection = selection;
1613+
1614+
const result = await subTestAtCursor(config, {functionName: 'TestMyFunction'});
1615+
assert.equal(result, undefined);
1616+
});
15411617
});

0 commit comments

Comments
 (0)