Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions extension/src/goRunTestCodelens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,27 +109,44 @@ export class GoRunTestCodeLensProvider extends GoBaseCodeLensProvider {
return codelens;
}

const simpleRunRegex = /t.Run\("([^"]+)",/;
// Captures receiver name and function name.
// reason is: function name coming from f.name is not compatible with struct methods.
const testSuiteRegex = /func \((.*?) .*?\) (Test.*?)\(/;

for (const f of testFunctions) {
const functionName = f.name;
let functionName = f.name;
const line = document.lineAt(f.range.start.line);
const testSuiteMatch = line.text.match(testSuiteRegex);
if (testSuiteMatch) functionName = testSuiteMatch[2]
var receiverName = testSuiteMatch ? testSuiteMatch[1] : 't';

codelens.push(
new CodeLens(f.range, {
title: 'run test',
command: 'go.test.cursor',
arguments: [{ functionName }]
arguments: [{
functionName: functionName,
isTestSuite: !!testSuiteMatch
}]
}),
new CodeLens(f.range, {
title: 'debug test',
command: 'go.debug.cursor',
arguments: [{ functionName }]
arguments: [{
functionName: functionName,
isTestSuite: !!testSuiteMatch
}]
})
);

// Dynamic regex for capturing receiverName.Run("testName", ...)
// receiver name is either t for normal test functions or the receiver of a test suite.
// example: func (s *testSuite) TestFunc() // Returns 's'
let testCaseRegex = new RegExp(receiverName + "\.Run\\(\"([^\"]+)\",");

for (let i = f.range.start.line; i < f.range.end.line; i++) {
const line = document.lineAt(i);
const simpleMatch = line.text.match(simpleRunRegex);
const simpleMatch = line.text.match(testCaseRegex);

// BUG: this does not handle nested subtests. This should
// be solved once codelens is handled by gopls and not by
Expand All @@ -141,12 +158,20 @@ export class GoRunTestCodeLensProvider extends GoBaseCodeLensProvider {
new CodeLens(line.range, {
title: 'run test',
command: 'go.subtest.cursor',
arguments: [{ functionName, subTestName }]
arguments: [{
functionName: functionName,
subTestName: subTestName,
isTestSuite: !!testSuiteMatch
}]
}),
new CodeLens(line.range, {
title: 'debug test',
command: 'go.debug.subtest.cursor',
arguments: [{ functionName, subTestName }]
arguments: [{
functionName: functionName,
subTestName: subTestName,
isTestSuite: !!testSuiteMatch
}]
})
);
}
Expand Down
22 changes: 14 additions & 8 deletions extension/src/goTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ async function _testAtCursor(
goCtx: GoExtensionContext,
goConfig: vscode.WorkspaceConfiguration,
cmd: TestAtCursorCmd,
args: any
args?: SubTestAtCursorArgs
) {
const editor = vscode.window.activeTextEditor;
if (!editor) {
Expand Down Expand Up @@ -72,7 +72,7 @@ async function _testAtCursor(
await editor.document.save();

if (cmd === 'debug') {
return debugTestAtCursor(editor, testFunctionName, testFunctions, suiteToTest, goConfig);
return debugTestAtCursor(editor, testFunctionName, testFunctions, suiteToTest, goConfig, undefined, args);
} else if (cmd === 'benchmark' || cmd === 'test') {
return runTestAtCursor(editor, testFunctionName, testFunctions, suiteToTest, goConfig, cmd, args);
} else {
Expand Down Expand Up @@ -117,7 +117,7 @@ async function _subTestAtCursor(
// We use functionName if it was provided as argument
// Otherwise find any test function containing the cursor.
const currentTestFunctions = args?.functionName
? testFunctions.filter((func) => func.name === args.functionName)
? testFunctions.filter((func) => func.name.endsWith(String(args.functionName)))
: testFunctions.filter((func) => func.range.contains(editor.selection.start));
const testFunctionName =
args && args.functionName ? args.functionName : currentTestFunctions.map((el) => el.name)[0];
Expand Down Expand Up @@ -165,7 +165,7 @@ async function _subTestAtCursor(
const escapedName = escapeSubTestName(testFunctionName, subTestName);

if (cmd === 'debug') {
return debugTestAtCursor(editor, escapedName, testFunctions, suiteToTest, goConfig);
return debugTestAtCursor(editor, escapedName, testFunctions, suiteToTest, goConfig, undefined, args);
} else if (cmd === 'test') {
return runTestAtCursor(editor, escapedName, testFunctions, suiteToTest, goConfig, cmd, args);
} else {
Expand Down Expand Up @@ -226,6 +226,10 @@ type TestAtCursor = {
* Flags to be passed to `go test`.
*/
flags?: string[];
/**
* Whether it's a test suite.
*/
isTestSuite?: boolean;
};

/**
Expand All @@ -252,6 +256,7 @@ async function runTestAtCursor(
flags: getTestFlags(goConfig, args),
functions: testConfigFns,
isBenchmark: cmd === 'benchmark',
isTestSuite: !!args?.isTestSuite,
isMod,
applyCodeCoverage: goConfig.get<boolean>('coverOnSingleTest')
};
Expand All @@ -273,10 +278,10 @@ export function subTestAtCursor(cmd: SubTestAtCursorCmd): CommandFactory {
* codelens provided by {@link GoRunTestCodeLensProvider}, args
* specifies the function and subtest names.
*/
args?: [SubTestAtCursorArgs]
args?:SubTestAtCursorArgs
) => {
try {
return await _subTestAtCursor(goCtx, getGoConfig(), cmd, args?.[0]);
return await _subTestAtCursor(goCtx, getGoConfig(), cmd, args);
} catch (err) {
if (err instanceof NotFoundError) {
vscode.window.showInformationMessage(err.message);
Expand All @@ -303,10 +308,11 @@ export async function debugTestAtCursor(
testFunctions: vscode.DocumentSymbol[],
suiteToFunc: SuiteToTestMap,
goConfig: vscode.WorkspaceConfiguration,
sessionID?: string
sessionID?: string,
testArgs?: { isTestSuite?: boolean },
) {
const doc = 'document' in editorOrDocument ? editorOrDocument.document : editorOrDocument;
const args = getTestFunctionDebugArgs(doc, testFunctionName, testFunctions, suiteToFunc);
const args = getTestFunctionDebugArgs(doc, testFunctionName, testFunctions, suiteToFunc, testArgs?.isTestSuite);
const tags = getTestTags(goConfig);
const buildFlags = tags ? ['-tags', tags] : [];
const flagsFromConfig = getTestFlags(goConfig);
Expand Down
19 changes: 16 additions & 3 deletions extension/src/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ export interface TestConfig {
* Whether this is a benchmark.
*/
isBenchmark?: boolean;
/**
* Whether this is a test suite
*/
isTestSuite?: boolean;
/**
* Whether the tests are being run in a project that uses Go modules
*/
Expand Down Expand Up @@ -246,12 +250,14 @@ export function extractInstanceTestName(symbolName: string): string {
* @param document The document containing the tests
* @param testFunctionName The test function to get the debug args
* @param testFunctions The test functions found in the document
* @param isTestSuite Indicator if the test function is part of a test suite
*/
export function getTestFunctionDebugArgs(
document: vscode.TextDocument,
testFunctionName: string,
testFunctions: vscode.DocumentSymbol[],
suiteToFunc: SuiteToTestMap
suiteToFunc: SuiteToTestMap,
isTestSuite ?: boolean,
): string[] {
if (benchmarkRegex.test(testFunctionName)) {
return ['-test.bench', '^' + testFunctionName + '$', '-test.run', 'a^'];
Expand All @@ -261,7 +267,7 @@ export function getTestFunctionDebugArgs(
const testFns = findAllTestSuiteRuns(document, testFunctions, suiteToFunc);
return ['-test.run', `^${testFns.map((t) => t.name).join('|')}$/^${instanceMethod}$`];
} else {
return ['-test.run', `^${testFunctionName}$`];
return ['-test.run', `${!!isTestSuite ? '/': ''}^${testFunctionName}$`];
}
}
/**
Expand Down Expand Up @@ -736,7 +742,14 @@ function targetArgs(testconfig: TestConfig): Array<string> {
// which will result in the correct thing to happen
if (testFunctions.length > 0) {
if (testFunctions.length === 1) {
params = params.concat(['-run', util.format('^%s$', testFunctions[0])]);
// If it's a test suite, it means the test function is not a root test function.
// By prepending '/', the command will interpret and execute all child test functions
// that matches the pattern of anyTestSuite/givenFunctionName/givenCaseName.
if (testconfig.isTestSuite) {
params = params.concat(['-run', util.format('/^%s$', testFunctions[0])]);
} else {
params = params.concat(['-run', util.format('^%s$', testFunctions[0])]);
}
} else {
params = params.concat(['-run', util.format('^(%s)$', testFunctions.join('|'))]);
}
Expand Down