From 6a61856c0bb2d9cfb9b7c584a1e220518ee07411 Mon Sep 17 00:00:00 2001 From: Sean Caffery Date: Wed, 3 Jun 2020 01:07:31 +0000 Subject: [PATCH] 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 Reviewed-by: Hyang-Ah Hana Kim --- package.json | 5 ++ src/goMain.ts | 9 ++- src/goTest.ts | 60 +++++++++++++++++++ src/testUtils.ts | 6 +- test/fixtures/subtests/go.mod | 3 + test/fixtures/subtests/subtests_test.go | 20 +++++++ test/integration/extension.test.ts | 78 ++++++++++++++++++++++++- 7 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/subtests/go.mod create mode 100644 test/fixtures/subtests/subtests_test.go diff --git a/package.json b/package.json index 49846aa648..8fb9b57b75 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/goMain.ts b/src/goMain.ts index 3451bd5b77..732f42446b 100644 --- a/src/goMain.ts +++ b/src/goMain.ts @@ -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 { @@ -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(); diff --git a/src/goTest.ts b/src/goTest.ts index cc7665043b..a523a6ea76 100644 --- a/src/goTest.ts +++ b/src/goTest.ts @@ -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. */ diff --git a/src/testUtils.ts b/src/testUtils.ts index c318b0f3cc..d1a97f4ec5 100644 --- a/src/testUtils.ts +++ b/src/testUtils.ts @@ -463,7 +463,11 @@ function targetArgs(testconfig: TestConfig): Array { // 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 0000000000..14550324d6 --- /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 0000000000..348113999f --- /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 f0ed8a78ce..1299a9243b 100644 --- a/test/integration/extension.test.ts +++ b/test/integration/extension.test.ts @@ -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, @@ -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(() => { @@ -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); + }); });