src/goTest.ts: refactor goTest to factory pattern
Change-Id: I84c0ff4885dedc3053181d30a179ee167db5bd37
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/405074
Run-TryBot: Jamal Carvalho <jamal@golang.org>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/src/commands/index.ts b/src/commands/index.ts
index cffff3f..e239205 100644
--- a/src/commands/index.ts
+++ b/src/commands/index.ts
@@ -14,6 +14,7 @@
export { runFillStruct } from '../goFillStruct';
export { implCursor } from '../goImpl';
export { addTags, removeTags } from '../goModifytags';
+export * from '../goTest';
export { installTools } from './installTools';
export { showCommands } from './showCommands';
export { startDebugSession } from './startDebugSession';
diff --git a/src/goMain.ts b/src/goMain.ts
index 056da0c..a0b5a0b 100644
--- a/src/goMain.ts
+++ b/src/goMain.ts
@@ -9,7 +9,7 @@
'use strict';
import * as path from 'path';
-import { extensionInfo, getGoConfig, getGoplsConfig } from './config';
+import { getGoConfig, getGoplsConfig } from './config';
import { browsePackages } from './goBrowsePackage';
import { buildCode } from './goBuild';
import { check, notifyIfGeneratedFile, removeTestStatus } from './goCheck';
@@ -23,7 +23,6 @@
} from './goCover';
import { GoDebugConfigurationProvider } from './goDebugConfiguration';
import * as GoDebugFactory from './goDebugFactory';
-import { extractFunction, extractVariable } from './goDoctor';
import { toolExecutionEnvironment } from './goEnv';
import {
chooseGoEnvironment,
@@ -32,7 +31,6 @@
} from './goEnvironmentStatus';
import * as goGenerateTests from './goGenerateTests';
import { goGetPackage } from './goGetPackage';
-import { implCursor } from './goImpl';
import { addImport, addImportToWorkspace } from './goImport';
import { installCurrentPackage } from './goInstall';
import {
@@ -51,19 +49,9 @@
import { GoReferencesCodeLensProvider } from './goReferencesCodelens';
import { GoRunTestCodeLensProvider } from './goRunTestCodelens';
import { disposeGoStatusBar, expandGoStatusBar, updateGoStatusBar } from './goStatus';
-import {
- debugPrevious,
- subTestAtCursor,
- testAtCursor,
- testAtCursorOrPrevious,
- testCurrentFile,
- testCurrentPackage,
- testPrevious,
- testWorkspace
-} from './goTest';
+
import { getConfiguredTools, Tool } from './goTools';
import { vetCode } from './goVet';
-import { pickGoProcess, pickProcess } from './pickProcess';
import {
getFromGlobalState,
resetGlobalState,
@@ -85,7 +73,6 @@
} from './util';
import { clearCacheForTools, dirExists } from './utils/pathUtils';
import { WelcomePanel } from './welcome';
-import semver = require('semver');
import vscode = require('vscode');
import { getFormatTool } from './language/legacy/goFormat';
import { resetSurveyConfigs, showSurveyConfig } from './goSurvey';
@@ -223,26 +210,19 @@
registerCommand('go.impl.cursor', commands.implCursor);
registerCommand('go.godoctor.extract', commands.extractFunction);
registerCommand('go.godoctor.var', commands.extractVariable);
- registerCommand('go.test.cursor', () => (args) => testAtCursor(getGoConfig(), 'test', args));
- registerCommand('go.test.cursorOrPrevious', () => (args) => testAtCursorOrPrevious(getGoConfig(), 'test', args));
+ registerCommand('go.test.cursor', commands.testAtCursor('test'));
+ registerCommand('go.test.cursorOrPrevious', commands.testAtCursorOrPrevious('test'));
+ registerCommand('go.subtest.cursor', commands.subTestAtCursor);
+ registerCommand('go.debug.cursor', commands.testAtCursor('debug'));
+ registerCommand('go.benchmark.cursor', commands.testAtCursor('benchmark'));
+ registerCommand('go.test.package', commands.testCurrentPackage(false));
+ registerCommand('go.benchmark.package', commands.testCurrentPackage(true));
+ registerCommand('go.test.file', commands.testCurrentFile(false));
+ registerCommand('go.benchmark.file', commands.testCurrentFile(true));
+ registerCommand('go.test.workspace', commands.testWorkspace);
+ registerCommand('go.test.previous', commands.testPrevious);
+ registerCommand('go.debug.previous', commands.debugPrevious);
- if (isVscodeTestingAPIAvailable && cfg.get<boolean>('testExplorer.enable')) {
- GoTestExplorer.setup(ctx);
- }
-
- GoExplorerProvider.setup(ctx);
- VulncheckProvider.setup(ctx, goCtx);
-
- registerCommand('go.subtest.cursor', () => (args) => subTestAtCursor(getGoConfig(), args));
- registerCommand('go.debug.cursor', () => (args) => testAtCursor(getGoConfig(), 'debug', args));
- registerCommand('go.benchmark.cursor', () => (args) => testAtCursor(getGoConfig(), 'benchmark', args));
- registerCommand('go.test.package', () => (args) => testCurrentPackage(getGoConfig(), false, args));
- registerCommand('go.benchmark.package', () => (args) => testCurrentPackage(getGoConfig(), true, args));
- registerCommand('go.test.file', () => (args) => testCurrentFile(getGoConfig(), false, args));
- registerCommand('go.benchmark.file', () => (args) => testCurrentFile(getGoConfig(), true, args));
- registerCommand('go.test.workspace', () => (args) => testWorkspace(getGoConfig(), args));
- registerCommand('go.test.previous', () => testPrevious);
- registerCommand('go.debug.previous', () => debugPrevious);
registerCommand('go.test.coverage', () => toggleCoverageCurrentPackage);
registerCommand('go.test.showOutput', () => showTestOutput);
registerCommand('go.test.cancel', () => cancelRunningTests);
@@ -251,6 +231,13 @@
registerCommand('go.tools.install', commands.installTools);
registerCommand('go.browse.packages', () => browsePackages);
+ if (isVscodeTestingAPIAvailable && cfg.get<boolean>('testExplorer.enable')) {
+ GoTestExplorer.setup(ctx);
+ }
+
+ GoExplorerProvider.setup(ctx);
+ VulncheckProvider.setup(ctx, goCtx);
+
ctx.subscriptions.push(
vscode.workspace.onDidChangeConfiguration(async (e: vscode.ConfigurationChangeEvent) => {
if (!e.affectsConfiguration('go')) {
diff --git a/src/goTest.ts b/src/goTest.ts
index d13d1ef..b4332e7 100644
--- a/src/goTest.ts
+++ b/src/goTest.ts
@@ -7,6 +7,8 @@
import path = require('path');
import vscode = require('vscode');
+import { CommandFactory } from './commands';
+import { getGoConfig } from './config';
import { isModSupported } from './goModules';
import {
extractInstanceTestName,
@@ -72,14 +74,17 @@
* @param cmd Whether the command is test, benchmark, or debug.
* @param args
*/
-export function testAtCursor(goConfig: vscode.WorkspaceConfiguration, cmd: TestAtCursorCmd, args: any) {
- _testAtCursor(goConfig, cmd, args).catch((err) => {
- if (err instanceof NotFoundError) {
- vscode.window.showInformationMessage(err.message);
- } else {
- console.error(err);
- }
- });
+export function testAtCursor(cmd: TestAtCursorCmd): CommandFactory {
+ return () => (args: any) => {
+ const goConfig = getGoConfig();
+ _testAtCursor(goConfig, cmd, args).catch((err) => {
+ if (err instanceof NotFoundError) {
+ vscode.window.showInformationMessage(err.message);
+ } else {
+ console.error(err);
+ }
+ });
+ };
}
/**
@@ -88,20 +93,23 @@
* @param cmd Whether the command is test, benchmark, or debug.
* @param args
*/
-export async function testAtCursorOrPrevious(goConfig: vscode.WorkspaceConfiguration, cmd: TestAtCursorCmd, args: any) {
- try {
- await _testAtCursor(goConfig, cmd, args);
- } catch (err) {
- if (err instanceof NotFoundError) {
- const editor = vscode.window.activeTextEditor;
- if (editor) {
- await editor.document.save();
+export function testAtCursorOrPrevious(cmd: TestAtCursorCmd): CommandFactory {
+ return (ctx, goCtx) => async (args: any) => {
+ const goConfig = getGoConfig();
+ try {
+ await _testAtCursor(goConfig, cmd, args);
+ } catch (err) {
+ if (err instanceof NotFoundError) {
+ const editor = vscode.window.activeTextEditor;
+ if (editor) {
+ await editor.document.save();
+ }
+ await testPrevious(ctx, goCtx)();
+ } else {
+ console.error(err);
}
- await testPrevious();
- } else {
- console.error(err);
}
- }
+ };
}
/**
@@ -138,71 +146,74 @@
/**
* 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)) ?? [];
- // 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.');
+export function subTestAtCursor() {
+ return async (args: any) => {
+ const goConfig = getGoConfig();
+ 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;
}
- const testFunction = currentTestFunctions[0];
- const simpleRunRegex = /t.Run\("([^"]+)",/;
- const runRegex = /t.Run\(/;
- let lineText: string;
- let runMatch: RegExpMatchArray | null | undefined;
- let simpleMatch: RegExpMatchArray | null | undefined;
- 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;
- }
- }
+ await editor.document.save();
+ try {
+ const testFunctions = (await getTestFunctions(editor.document)) ?? [];
+ // 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];
- let subtest: string;
- if (!simpleMatch) {
- const input = await vscode.window.showInputBox({
- prompt: 'Enter sub test name'
- });
- if (input) {
- subtest = input;
- } else {
- vscode.window.showInformationMessage('No subtest function with a simple subtest name found at cursor.');
+ if (!testFunctionName || currentTestFunctions.length === 0) {
+ vscode.window.showInformationMessage('No test function found at cursor.');
return;
}
- } else {
- subtest = simpleMatch[1];
+
+ const testFunction = currentTestFunctions[0];
+ const simpleRunRegex = /t.Run\("([^"]+)",/;
+ const runRegex = /t.Run\(/;
+ let lineText: string;
+ let runMatch: RegExpMatchArray | null | undefined;
+ let simpleMatch: RegExpMatchArray | null | undefined;
+ 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;
+ }
+ }
+
+ let subtest: string;
+ if (!simpleMatch) {
+ const input = await vscode.window.showInputBox({
+ prompt: 'Enter sub test name'
+ });
+ if (input) {
+ subtest = input;
+ } else {
+ vscode.window.showInformationMessage(
+ 'No subtest function with a simple subtest name found at cursor.'
+ );
+ return;
+ }
+ } else {
+ subtest = simpleMatch[1];
+ }
+
+ const subTestName = testFunctionName + '/' + subtest;
+
+ return await runTestAtCursor(editor, subTestName, testFunctions, goConfig, 'test', args);
+ } catch (err) {
+ vscode.window.showInformationMessage('Unable to run subtest: ' + (err as any).toString());
+ console.error(err);
}
-
- const subTestName = testFunctionName + '/' + subtest;
-
- return await runTestAtCursor(editor, subTestName, testFunctions, goConfig, 'test', args);
- } catch (err) {
- vscode.window.showInformationMessage('Unable to run subtest: ' + (err as any).toString());
- console.error(err);
- }
+ };
}
/**
@@ -262,25 +273,28 @@
*
* @param goConfig Configuration for the Go extension.
*/
-export async function testCurrentPackage(goConfig: vscode.WorkspaceConfiguration, isBenchmark: boolean, args: any) {
- const editor = vscode.window.activeTextEditor;
- if (!editor) {
- vscode.window.showInformationMessage('No editor is active.');
- return;
- }
+export function testCurrentPackage(isBenchmark: boolean): CommandFactory {
+ return () => async (args: any) => {
+ const goConfig = getGoConfig();
+ const editor = vscode.window.activeTextEditor;
+ if (!editor) {
+ vscode.window.showInformationMessage('No editor is active.');
+ return;
+ }
- const isMod = await isModSupported(editor.document.uri);
- const testConfig: TestConfig = {
- goConfig,
- dir: path.dirname(editor.document.fileName),
- flags: getTestFlags(goConfig, args),
- isBenchmark,
- isMod,
- applyCodeCoverage: goConfig.get<boolean>('coverOnTestPackage')
+ const isMod = await isModSupported(editor.document.uri);
+ const testConfig: TestConfig = {
+ goConfig,
+ dir: path.dirname(editor.document.fileName),
+ flags: getTestFlags(goConfig, args),
+ isBenchmark,
+ isMod,
+ applyCodeCoverage: goConfig.get<boolean>('coverOnTestPackage')
+ };
+ // Remember this config as the last executed test.
+ lastTestConfig = testConfig;
+ return goTest(testConfig);
};
- // Remember this config as the last executed test.
- lastTestConfig = testConfig;
- return goTest(testConfig);
}
/**
@@ -288,7 +302,8 @@
*
* @param goConfig Configuration for the Go extension.
*/
-export function testWorkspace(goConfig: vscode.WorkspaceConfiguration, args: any) {
+export const testWorkspace: CommandFactory = () => (args: any) => {
+ const goConfig = getGoConfig();
if (!vscode.workspace.workspaceFolders?.length) {
vscode.window.showInformationMessage('No workspace is open to run tests.');
return;
@@ -316,7 +331,7 @@
console.error(err);
});
});
-}
+};
/**
* Runs all tests in the source of the active editor.
@@ -324,52 +339,51 @@
* @param goConfig Configuration for the Go extension.
* @param isBenchmark Boolean flag indicating if these are benchmark tests or not.
*/
-export async function testCurrentFile(
- goConfig: vscode.WorkspaceConfiguration,
- isBenchmark: boolean,
- args: string[]
-): Promise<boolean> {
- const editor = vscode.window.activeTextEditor;
- if (!editor) {
- vscode.window.showInformationMessage('No editor is active.');
- return false;
- }
- if (!editor.document.fileName.endsWith('_test.go')) {
- vscode.window.showInformationMessage('No tests found. Current file is not a test file.');
- return false;
- }
+export function testCurrentFile(isBenchmark: boolean, getConfig = getGoConfig): CommandFactory {
+ return () => async (args: string[]) => {
+ const goConfig = getConfig();
+ const editor = vscode.window.activeTextEditor;
+ if (!editor) {
+ vscode.window.showInformationMessage('No editor is active.');
+ return false;
+ }
+ if (!editor.document.fileName.endsWith('_test.go')) {
+ vscode.window.showInformationMessage('No tests found. Current file is not a test file.');
+ return false;
+ }
- const getFunctions = isBenchmark ? getBenchmarkFunctions : getTestFunctions;
- const isMod = await isModSupported(editor.document.uri);
+ const getFunctions = isBenchmark ? getBenchmarkFunctions : getTestFunctions;
+ const isMod = await isModSupported(editor.document.uri);
- return editor.document
- .save()
- .then(() => {
- return getFunctions(editor.document).then((testFunctions) => {
- const testConfig: TestConfig = {
- goConfig,
- dir: path.dirname(editor.document.fileName),
- flags: getTestFlags(goConfig, args),
- functions: testFunctions?.map((sym) => sym.name),
- isBenchmark,
- isMod,
- applyCodeCoverage: goConfig.get<boolean>('coverOnSingleTestFile')
- };
- // Remember this config as the last executed test.
- lastTestConfig = testConfig;
- return goTest(testConfig);
+ return editor.document
+ .save()
+ .then(() => {
+ return getFunctions(editor.document).then((testFunctions) => {
+ const testConfig: TestConfig = {
+ goConfig,
+ dir: path.dirname(editor.document.fileName),
+ flags: getTestFlags(goConfig, args),
+ functions: testFunctions?.map((sym) => sym.name),
+ isBenchmark,
+ isMod,
+ applyCodeCoverage: goConfig.get<boolean>('coverOnSingleTestFile')
+ };
+ // Remember this config as the last executed test.
+ lastTestConfig = testConfig;
+ return goTest(testConfig);
+ });
+ })
+ .then(undefined, (err) => {
+ console.error(err);
+ return Promise.resolve(false);
});
- })
- .then(undefined, (err) => {
- console.error(err);
- return Promise.resolve(false);
- });
+ };
}
/**
* Runs the previously executed test.
*/
-export function testPrevious() {
+export const testPrevious: CommandFactory = () => () => {
if (!lastTestConfig) {
vscode.window.showInformationMessage('No test has been recently executed.');
return;
@@ -377,15 +391,15 @@
goTest(lastTestConfig).then(null, (err) => {
console.error(err);
});
-}
+};
/**
* Runs the previously executed test.
*/
-export function debugPrevious() {
+export const debugPrevious: CommandFactory = () => () => {
if (!lastDebugConfig) {
vscode.window.showInformationMessage('No test has been recently debugged.');
return;
}
return vscode.debug.startDebugging(lastDebugWorkspaceFolder, lastDebugConfig);
-}
+};
diff --git a/test/integration/codelens.test.ts b/test/integration/codelens.test.ts
index 44a6284..ca6f093 100644
--- a/test/integration/codelens.test.ts
+++ b/test/integration/codelens.test.ts
@@ -54,7 +54,6 @@
fs.copySync(fixtureSourcePath, fixturePath, {
recursive: true
});
- goConfig = getGoConfig();
const uri = vscode.Uri.file(path.join(fixturePath, 'codelens_test.go'));
document = await vscode.workspace.openTextDocument(uri);
});
@@ -70,21 +69,21 @@
test('Subtests - runs a test with cursor on t.Run line', async () => {
const editor = await vscode.window.showTextDocument(document);
editor.selection = new vscode.Selection(7, 4, 7, 4);
- const result = await subTestAtCursor(goConfig, []);
+ const result = await subTestAtCursor()([]);
assert.equal(result, true);
});
test('Subtests - runs a test with cursor within t.Run function', async () => {
const editor = await vscode.window.showTextDocument(document);
editor.selection = new vscode.Selection(8, 4, 8, 4);
- const result = await subTestAtCursor(goConfig, []);
+ const result = await subTestAtCursor()([]);
assert.equal(result, true);
});
test('Subtests - returns false for a failing test', async () => {
const editor = await vscode.window.showTextDocument(document);
editor.selection = new vscode.Selection(11, 4, 11, 4);
- const result = await subTestAtCursor(goConfig, []);
+ const result = await subTestAtCursor()([]);
assert.equal(result, false);
});
@@ -92,7 +91,7 @@
const editor = await vscode.window.showTextDocument(document);
editor.selection = new vscode.Selection(17, 4, 17, 4);
sinon.stub(vscode.window, 'showInputBox').onFirstCall().resolves(undefined);
- const result = await subTestAtCursor(goConfig, []);
+ const result = await subTestAtCursor()([]);
assert.equal(result, undefined);
});
@@ -100,21 +99,21 @@
const editor = await vscode.window.showTextDocument(document);
editor.selection = new vscode.Selection(17, 4, 17, 4);
sinon.stub(vscode.window, 'showInputBox').onFirstCall().resolves('dynamic test name');
- const result = await subTestAtCursor(goConfig, []);
+ const result = await subTestAtCursor()([]);
assert.equal(result, false);
});
test('Subtests - does nothing when cursor outside of a test function', async () => {
const editor = await vscode.window.showTextDocument(document);
editor.selection = new vscode.Selection(5, 0, 5, 0);
- const result = await subTestAtCursor(goConfig, []);
+ const result = await subTestAtCursor()([]);
assert.equal(result, undefined);
});
test('Subtests - does nothing when no test function covers the cursor and a function name is passed in', async () => {
const editor = await vscode.window.showTextDocument(document);
editor.selection = new vscode.Selection(5, 0, 5, 0);
- const result = await subTestAtCursor(goConfig, { functionName: 'TestMyFunction' });
+ const result = await subTestAtCursor()({ functionName: 'TestMyFunction' });
assert.equal(result, undefined);
});
diff --git a/test/integration/extension.test.ts b/test/integration/extension.test.ts
index bbe6de1..fa74231 100644
--- a/test/integration/extension.test.ts
+++ b/test/integration/extension.test.ts
@@ -33,7 +33,7 @@
import { GoSignatureHelpProvider } from '../../src/language/legacy/goSignature';
import { GoCompletionItemProvider } from '../../src/language/legacy/goSuggest';
import { getWorkspaceSymbols } from '../../src/language/legacy/goSymbol';
-import { testCurrentFile } from '../../src/goTest';
+import { testCurrentFile } from '../../src/commands';
import {
getBinPath,
getCurrentGoPath,
@@ -679,8 +679,13 @@
const uri = vscode.Uri.file(path.join(fixturePath, 'baseTest', 'sample_test.go'));
const document = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(document);
-
- const result = await testCurrentFile(config, false, []);
+ const ctx = new MockExtensionContext() as any;
+ const goCtx: GoExtensionContext = {
+ lastUserAction: new Date(),
+ crashCount: 0,
+ restartHistory: []
+ };
+ const result = await testCurrentFile(false, () => config)(ctx, goCtx)([]);
assert.equal(result, true);
});
@@ -1507,17 +1512,23 @@
const uri = vscode.Uri.file(path.join(fixturePath, 'testTags', 'hello_test.go'));
const document = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(document);
+ const ctx = new MockExtensionContext() as any;
+ const goCtx: GoExtensionContext = {
+ lastUserAction: new Date(),
+ crashCount: 0,
+ restartHistory: []
+ };
- const result1 = await testCurrentFile(config1, false, []);
+ const result1 = await testCurrentFile(false, () => config1)(ctx, goCtx)([]);
assert.equal(result1, true);
- const result2 = await testCurrentFile(config2, false, []);
+ const result2 = await testCurrentFile(false, () => config2)(ctx, goCtx)([]);
assert.equal(result2, true);
- const result3 = await testCurrentFile(config3, false, []);
+ const result3 = await testCurrentFile(false, () => config3)(ctx, goCtx)([]);
assert.equal(result3, true);
- const result4 = await testCurrentFile(config4, false, []);
+ const result4 = await testCurrentFile(false, () => config4)(ctx, goCtx)([]);
assert.equal(result4, false);
});