diff --git a/src/goRunTestCodelens.ts b/src/goRunTestCodelens.ts index ab09db3d6..80616142d 100644 --- a/src/goRunTestCodelens.ts +++ b/src/goRunTestCodelens.ts @@ -8,7 +8,7 @@ import vscode = require('vscode'); import path = require('path'); import { TextDocument, CancellationToken, CodeLens, Command } from 'vscode'; -import { getTestFunctions, getBenchmarkFunctions, getTestFlags } from './testUtils'; +import { getTestFunctions, getBenchmarkFunctions, getTestFlags, extractInstanceTestName, findAllTestSuiteRuns } from './testUtils'; import { GoDocumentSymbolProvider } from './goOutline'; import { getCurrentGoPath } from './util'; import { GoBaseCodeLensProvider } from './goBaseCodelens'; @@ -94,10 +94,22 @@ export class GoRunTestCodeLensProvider extends GoBaseCodeLensProvider { codelens.push(new CodeLens(func.location.range, runTestCmd)); + let args: string[] = []; + let instanceMethod = extractInstanceTestName(func.name); + if (instanceMethod) { + const testFns = findAllTestSuiteRuns(document, testFunctions); + if (testFns && testFns.length > 0) { + args = args.concat('-test.run', `^${testFns.map(t => t.name).join('|')}$`); + } + args = args.concat('-testify.m', `^${instanceMethod}$`); + } else { + args = args.concat('-test.run', `^${func.name}$`); + } + let debugTestCmd: Command = { title: 'debug test', command: 'go.debug.startSession', - arguments: [Object.assign({}, currentDebugConfig, { args: ['-test.run', '^' + func.name + '$'] })] + arguments: [Object.assign({}, currentDebugConfig, { args: args })] }; codelens.push(new CodeLens(func.location.range, debugTestCmd)); diff --git a/src/goTest.ts b/src/goTest.ts index 2f18a5c2f..eeb4b77f7 100644 --- a/src/goTest.ts +++ b/src/goTest.ts @@ -8,7 +8,7 @@ import path = require('path'); import vscode = require('vscode'); import os = require('os'); -import { goTest, TestConfig, getTestFlags, getTestFunctions, getBenchmarkFunctions } from './testUtils'; +import { goTest, TestConfig, getTestFlags, getTestFunctions, getBenchmarkFunctions, extractInstanceTestName, findAllTestSuiteRuns } from './testUtils'; import { getCoverage } from './goCover'; // lastTestConfig holds a reference to the last executed TestConfig which allows @@ -34,7 +34,7 @@ export function testAtCursor(goConfig: vscode.WorkspaceConfiguration, isBenchmar const getFunctions = isBenchmark ? getBenchmarkFunctions : getTestFunctions; - const {tmpCoverPath, testFlags } = makeCoverData(goConfig, 'coverOnSingleTest', args); + const { tmpCoverPath, testFlags } = makeCoverData(goConfig, 'coverOnSingleTest', args); editor.document.save().then(() => { return getFunctions(editor.document, null).then(testFunctions => { @@ -59,13 +59,22 @@ export function testAtCursor(goConfig: vscode.WorkspaceConfiguration, isBenchmar return; } - const testConfig = { + let testConfigFns = [testFunctionName]; + + if (!isBenchmark && extractInstanceTestName(testFunctionName)) { + // find test function with corresponding suite.Run + const testFns = findAllTestSuiteRuns(editor.document, testFunctions); + if (testFns) { + testConfigFns = testConfigFns.concat(testFns.map(t => t.name)); + } + } + + const testConfig: TestConfig = { goConfig: goConfig, dir: path.dirname(editor.document.fileName), flags: testFlags, - functions: [testFunctionName], + functions: testConfigFns, isBenchmark: isBenchmark, - showTestCoverage: true }; // Remember this config as the last executed test. @@ -94,13 +103,12 @@ export function testCurrentPackage(goConfig: vscode.WorkspaceConfiguration, args return; } - const {tmpCoverPath, testFlags } = makeCoverData(goConfig, 'coverOnTestPackage', args); + const { tmpCoverPath, testFlags } = makeCoverData(goConfig, 'coverOnTestPackage', args); - const testConfig = { + const testConfig: TestConfig = { goConfig: goConfig, dir: path.dirname(editor.document.fileName), flags: testFlags, - showTestCoverage: true }; // Remember this config as the last executed test. lastTestConfig = testConfig; @@ -160,11 +168,11 @@ export function testCurrentFile(goConfig: vscode.WorkspaceConfiguration, args: s return editor.document.save().then(() => { return getTestFunctions(editor.document, null).then(testFunctions => { - const testConfig = { + const testConfig: TestConfig = { goConfig: goConfig, dir: path.dirname(editor.document.fileName), flags: getTestFlags(goConfig, args), - functions: testFunctions.map(func => { return func.name; }) + functions: testFunctions.map(sym => sym.name), }; // Remember this config as the last executed test. lastTestConfig = testConfig; @@ -203,5 +211,5 @@ function makeCoverData(goConfig: vscode.WorkspaceConfiguration, confFlag: string testFlags.push('-coverprofile=' + tmpCoverPath); } - return {tmpCoverPath, testFlags}; + return { tmpCoverPath, testFlags }; } diff --git a/src/testUtils.ts b/src/testUtils.ts index 22255d48b..e7d745dad 100644 --- a/src/testUtils.ts +++ b/src/testUtils.ts @@ -23,6 +23,7 @@ statusBarItem.text = '$(x) Cancel Running Tests'; */ const runningTestProcesses: cp.ChildProcess[] = []; +const testSuiteMethodRegex = /^\(([^)]+)\)\.(Test.*)$/; /** * Input to goTest. @@ -98,10 +99,38 @@ export function getTestFunctions(doc: vscode.TextDocument, token: vscode.Cancell .then(symbols => symbols.filter(sym => sym.kind === vscode.SymbolKind.Function - && (sym.name.startsWith('Test') || sym.name.startsWith('Example'))) + && (sym.name.startsWith('Test') || sym.name.startsWith('Example') || testSuiteMethodRegex.test(sym.name)) + ) ); } +/** + * Extracts test method name of a suite test function. + * For example a symbol with name "(*testSuite).TestMethod" will return "TestMethod". + * + * @param symbolName Symbol Name to extract method name from. + */ +export function extractInstanceTestName(symbolName: string): string { + const match = symbolName.match(testSuiteMethodRegex); + if (!match || match.length !== 3) { + return null; + } + return match[2]; +} + +/** + * Finds test methods containing "suite.Run()" call. + * + * @param doc Editor document + * @param allTests All test functions + */ +export function findAllTestSuiteRuns(doc: vscode.TextDocument, allTests: vscode.SymbolInformation[]): vscode.SymbolInformation[] { + // get non-instance test functions + const testFunctions = allTests.filter(t => !testSuiteMethodRegex.test(t.name)); + // filter further to ones containing suite.Run() + return testFunctions.filter(t => doc.getText(t.location.range).includes('suite.Run(')); +} + /** * Returns all Benchmark functions in the given source file. * @@ -166,7 +195,7 @@ export function goTest(testconfig: TestConfig): Thenable { targetArgs(testconfig).then(targets => { let outTargets = args.slice(0); - if (targets.length > 2) { + if (targets.length > 4) { outTargets.push(''); } else { outTargets.push(...targets); @@ -287,7 +316,29 @@ function expandFilePathInOutput(output: string, cwd: string): string { */ function targetArgs(testconfig: TestConfig): Thenable> { if (testconfig.functions) { - return Promise.resolve([testconfig.isBenchmark ? '-bench' : '-run', util.format('^%s$', testconfig.functions.join('|'))]); + let params: string[] = []; + if (testconfig.isBenchmark) { + params = ['-bench', util.format('^%s$', testconfig.functions.join('|'))]; + } else { + let testFunctions = testconfig.functions; + let testifyMethods = testFunctions.filter(fn => testSuiteMethodRegex.test(fn)); + if (testifyMethods.length > 0) { + // filter out testify methods + testFunctions = testFunctions.filter(fn => !testSuiteMethodRegex.test(fn)); + testifyMethods = testifyMethods.map(extractInstanceTestName); + } + + // we might skip the '-run' param when running only testify methods, which will result + // 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 (testifyMethods.length > 0) { + params = params.concat(['-testify.m', util.format('^%s$', testifyMethods.join('|'))]); + } + } + return Promise.resolve(params); } else if (testconfig.includeSubDirectories && !testconfig.isBenchmark) { return getGoVersion().then((ver: SemVersion) => { if (ver && (ver.major > 1 || (ver.major === 1 && ver.minor >= 9))) {