diff --git a/src/goTest.ts b/src/goTest.ts index e25c9d7b19..52d6958f6e 100644 --- a/src/goTest.ts +++ b/src/goTest.ts @@ -180,7 +180,7 @@ async function debugTestAtCursor( testFunctions: vscode.DocumentSymbol[], goConfig: vscode.WorkspaceConfiguration ) { - const args = getTestFunctionDebugArgs(editor.document, testFunctionName, testFunctions); + const args = await getTestFunctionDebugArgs(editor.document, testFunctionName, testFunctions); const tags = getTestTags(goConfig); const buildFlags = tags ? ['-tags', tags] : []; const flagsFromConfig = getTestFlags(goConfig); diff --git a/src/testUtils.ts b/src/testUtils.ts index 77e812ded7..de9cfdadf6 100644 --- a/src/testUtils.ts +++ b/src/testUtils.ts @@ -41,6 +41,10 @@ const testFuncRegex = /^Test\P{Ll}.*|^Example\P{Ll}.*/u; const testMethodRegex = /^\(([^)]+)\)\.(Test\P{Ll}.*)$/u; const benchmarkRegex = /^Benchmark\P{Ll}.*/u; +const checkPkg = '"gopkg.in/check.v1"'; +const testifyPkg = '"github.com/stretchr/testify/suite"'; +const allExternalPackages = [checkPkg, testifyPkg]; + /** * Input to goTest. */ @@ -144,13 +148,19 @@ export async function getTestFunctions( return; } const children = symbol.children; - const testify = children.some( - (sym) => sym.kind === vscode.SymbolKind.Namespace && sym.name === '"github.com/stretchr/testify/suite"' - ); + + // include test functions and methods from 3rd party testing packages + let containsExternal = false; + allExternalPackages.forEach((pkg) => { + const ext = children.some((sym) => sym.kind === vscode.SymbolKind.Namespace && sym.name === pkg); + if (ext) { + containsExternal = ext; + } + }); return children.filter( (sym) => sym.kind === vscode.SymbolKind.Function && - (testFuncRegex.test(sym.name) || (testify && testMethodRegex.test(sym.name))) + (testFuncRegex.test(sym.name) || (containsExternal && testMethodRegex.test(sym.name))) ); } @@ -174,16 +184,20 @@ export function extractInstanceTestName(symbolName: string): string { * @param testFunctionName The test function to get the debug args * @param testFunctions The test functions found in the document */ -export function getTestFunctionDebugArgs( +export async function getTestFunctionDebugArgs( document: vscode.TextDocument, testFunctionName: string, testFunctions: vscode.DocumentSymbol[] -): string[] { +): Promise { if (benchmarkRegex.test(testFunctionName)) { return ['-test.bench', '^' + testFunctionName + '$', '-test.run', 'a^']; } const instanceMethod = extractInstanceTestName(testFunctionName); if (instanceMethod) { + if (await containsThirdPartyTestPackages(document, null, [checkPkg])) { + return ['-check.f', `^${instanceMethod}$`]; + } + const testFns = findAllTestSuiteRuns(document, testFunctions); const testSuiteRuns = ['-test.run', `^${testFns.map((t) => t.name).join('|')}$`]; const testSuiteTests = ['-testify.m', `^${instanceMethod}$`]; @@ -281,7 +295,7 @@ export async function goTest(testconfig: TestConfig): Promise { const { targets, pkgMap, currentGoWorkspace } = await getTestTargetPackages(testconfig, outputChannel); // generate full test args. - const { args, outArgs, tmpCoverPath, addJSONFlag } = computeTestCommand(testconfig, targets); + const { args, outArgs, tmpCoverPath, addJSONFlag } = await computeTestCommand(testconfig, targets); outputChannel.appendLine(['Running tool:', goRuntimePath, ...outArgs].join(' ')); outputChannel.appendLine(''); @@ -388,15 +402,15 @@ async function getTestTargetPackages(testconfig: TestConfig, outputChannel: vsco // computeTestCommand returns the test command argument list and extra info necessary // to post process the test results. // Exported for testing. -export function computeTestCommand( +export async function computeTestCommand( testconfig: TestConfig, targets: string[] -): { +): Promise<{ args: Array; // test command args. outArgs: Array; // compact test command args to show to user. tmpCoverPath?: string; // coverage file path if coverage info is necessary. addJSONFlag: boolean; // true if we add extra -json flag for stream processing. -} { +}> { const args: Array = ['test']; // user-specified flags const argsFlagIdx = testconfig.flags?.indexOf('-args') ?? -1; @@ -438,7 +452,7 @@ export function computeTestCommand( } // all other test run/benchmark flags - args.push(...targetArgs(testconfig)); + args.push(...(await targetArgs(testconfig))); const outArgs = args.slice(0); // command to show @@ -561,24 +575,34 @@ export function cancelRunningTests(): Thenable { * * @param testconfig Configuration for the Go extension. */ -function targetArgs(testconfig: TestConfig): Array { +async function targetArgs(testconfig: TestConfig): Promise> { let params: string[] = []; if (testconfig.functions) { if (testconfig.isBenchmark) { params = ['-bench', util.format('^(%s)$', testconfig.functions.join('|'))]; } else { + 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; + } + let testFunctions = testconfig.functions; - let testifyMethods = testFunctions.filter((fn) => testMethodRegex.test(fn)); - if (testifyMethods.length > 0) { - // filter out testify methods + let testMethods = testFunctions.filter((fn) => testMethodRegex.test(fn)); + if (testMethods.length > 0) { + // filter out methods testFunctions = testFunctions.filter((fn) => !testMethodRegex.test(fn)); - testifyMethods = testifyMethods.map(extractInstanceTestName); + testMethods = testMethods.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 + // we might skip the '-run' param when running only external test package methods, which will + // result in running all the test methods, but in the case of testify, one of them should call + // testify's `suite.Run(...)`, which will cause the correct thing to happen if (testFunctions.length > 0) { if (testFunctions.length === 1) { params = params.concat(['-run', util.format('^%s$', testFunctions[0])]); @@ -586,8 +610,12 @@ function targetArgs(testconfig: TestConfig): Array { params = params.concat(['-run', util.format('^(%s)$', testFunctions.join('|'))]); } } - if (testifyMethods.length > 0) { - params = params.concat(['-testify.m', util.format('^(%s)$', testifyMethods.join('|'))]); + if (testMethods.length > 0) { + if (await containsThirdPartyTestPackages(editor.document, null, [checkPkg])) { + params = params.concat(['-check.f', util.format('^(%s)$', testMethods.join('|'))]); + } else if (await containsThirdPartyTestPackages(editor.document, null, [testifyPkg])) { + params = params.concat(['-testify.m', util.format('^(%s)$', testMethods.join('|'))]); + } } } return params; @@ -605,3 +633,24 @@ function removeRunFlag(flags: string[]): void { flags.splice(index, 2); } } + +export async function containsThirdPartyTestPackages( + doc: vscode.TextDocument, + token: vscode.CancellationToken, + pkgs: string[] +): Promise { + const documentSymbolProvider = new GoDocumentSymbolProvider(true); + const allPackages = await documentSymbolProvider + .provideDocumentSymbols(doc, token) + .then((symbols) => symbols[0].children) + .then((symbols) => { + return symbols.filter( + (sym) => + sym.kind === vscode.SymbolKind.Namespace && + pkgs.some((pkg) => { + return sym.name === pkg; + }) + ); + }); + return allPackages.length > 0; +} diff --git a/test/integration/test.test.ts b/test/integration/test.test.ts index 0896ea00ca..9f42873a1e 100644 --- a/test/integration/test.test.ts +++ b/test/integration/test.test.ts @@ -17,14 +17,14 @@ import sinon = require('sinon'); import vscode = require('vscode'); suite('Test Go Test Args', () => { - function runTest(param: { + async function runTest(param: { expectedArgs: string; expectedOutArgs: string; flags?: string[]; functions?: string[]; isBenchmark?: boolean; }) { - const { args, outArgs } = computeTestCommand( + const { args, outArgs } = await computeTestCommand( { dir: '', goConfig: getGoConfig(), @@ -40,57 +40,57 @@ suite('Test Go Test Args', () => { assert.strictEqual(outArgs.join(' '), param.expectedOutArgs, 'displayed command'); } - test('default config', () => { - runTest({ + test('default config', async () => { + await runTest({ expectedArgs: 'test -timeout 30s ./...', expectedOutArgs: 'test -timeout 30s ./...' }); }); - test('user flag [-v] enables -json flag', () => { - runTest({ + test('user flag [-v] enables -json flag', async () => { + await runTest({ expectedArgs: 'test -timeout 30s -json ./... -v', expectedOutArgs: 'test -timeout 30s ./... -v', flags: ['-v'] }); }); - test('user flag [-json -v] prevents -json flag addition', () => { - runTest({ + test('user flag [-json -v] prevents -json flag addition', async () => { + await runTest({ expectedArgs: 'test -timeout 30s ./... -json -v', expectedOutArgs: 'test -timeout 30s ./... -json -v', flags: ['-json', '-v'] }); }); - test('user flag [-args] does not crash', () => { - runTest({ + test('user flag [-args] does not crash', async () => { + await runTest({ expectedArgs: 'test -timeout 30s ./... -args', expectedOutArgs: 'test -timeout 30s ./... -args', flags: ['-args'] }); }); - test('user flag [-args -v] does not enable -json flag', () => { - runTest({ + test('user flag [-args -v] does not enable -json flag', async () => { + await runTest({ expectedArgs: 'test -timeout 30s ./... -args -v', expectedOutArgs: 'test -timeout 30s ./... -args -v', flags: ['-args', '-v'] }); }); - test('specifying functions adds -run flags', () => { - runTest({ + test('specifying functions adds -run flags', async () => { + await runTest({ expectedArgs: 'test -timeout 30s -run ^(TestA|TestB)$ ./...', expectedOutArgs: 'test -timeout 30s -run ^(TestA|TestB)$ ./...', functions: ['TestA', 'TestB'] }); }); - test('functions & benchmark adds -bench flags and skips timeout', () => { - runTest({ + test('functions & benchmark adds -bench flags and skips timeout', async () => { + await runTest({ expectedArgs: 'test -benchmem -run=^$ -bench ^(TestA|TestB)$ ./...', expectedOutArgs: 'test -benchmem -run=^$ -bench ^(TestA|TestB)$ ./...', functions: ['TestA', 'TestB'], isBenchmark: true }); }); - test('user -run flag is ignored when functions are provided', () => { - runTest({ + test('user -run flag is ignored when functions are provided', async () => { + await runTest({ expectedArgs: 'test -timeout 30s -run ^(TestA|TestB)$ ./...', expectedOutArgs: 'test -timeout 30s -run ^(TestA|TestB)$ ./...', functions: ['TestA', 'TestB'],