Skip to content

Commit

Permalink
src: support running an individual subtest
Browse files Browse the repository at this point in the history
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: golang#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]>
  • Loading branch information
seancaffery authored and stamblerre committed Jun 3, 2020
1 parent 3d575bd commit 6a61856
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 3 deletions.
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@
"title": "Go: Test Function At Cursor",
"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",
Expand Down
9 changes: 8 additions & 1 deletion src/goMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { playgroundCommand } from './goPlayground';
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 {
Expand Down Expand Up @@ -273,6 +273,13 @@ export function activate(ctx: vscode.ExtensionContext): void {
})
);

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();
Expand Down
60 changes: 60 additions & 0 deletions src/goTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,66 @@ async function runTestAtCursor(
return goTest(testConfig);
}

/**
* 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.
*/
Expand Down
6 changes: 5 additions & 1 deletion src/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,11 @@ function targetArgs(testconfig: TestConfig): Array<string> {
// 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('|'))]);
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/subtests/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/microsoft/vscode-go/gofixtures/subtests

go 1.14
20 changes: 20 additions & 0 deletions test/fixtures/subtests/subtests_test.go
Original file line number Diff line number Diff line change
@@ -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()
})
}
78 changes: 77 additions & 1 deletion test/integration/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { goPlay } from '../../src/goPlayground';
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,
Expand Down Expand Up @@ -113,6 +113,10 @@ suite('Go Extension Tests', function () {
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(() => {
Expand Down Expand Up @@ -1538,4 +1542,76 @@ encountered.
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);
});
});

0 comments on commit 6a61856

Please sign in to comment.