Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support check.v1 Test Suites #1494

Closed
wants to merge 4 commits into from
Closed
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
2 changes: 1 addition & 1 deletion src/goTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
91 changes: 70 additions & 21 deletions src/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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)))
);
}

Expand All @@ -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<string[]> {
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}$`];
Expand Down Expand Up @@ -281,7 +295,7 @@ export async function goTest(testconfig: TestConfig): Promise<boolean> {
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('');
Expand Down Expand Up @@ -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<string>; // test command args.
outArgs: Array<string>; // 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<string> = ['test'];
// user-specified flags
const argsFlagIdx = testconfig.flags?.indexOf('-args') ?? -1;
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -561,33 +575,47 @@ export function cancelRunningTests(): Thenable<boolean> {
*
* @param testconfig Configuration for the Go extension.
*/
function targetArgs(testconfig: TestConfig): Array<string> {
async function targetArgs(testconfig: TestConfig): Promise<Array<string>> {
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])]);
} else {
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;
Expand All @@ -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<boolean> {
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;
}
36 changes: 18 additions & 18 deletions test/integration/test.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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'],
Expand Down