| /* eslint-disable @typescript-eslint/no-explicit-any */ |
| /*--------------------------------------------------------- |
| * Copyright (C) Microsoft Corporation. All rights reserved. |
| * Licensed under the MIT License. See LICENSE in the project root for license information. |
| *--------------------------------------------------------*/ |
| 'use strict'; |
| |
| import path = require('path'); |
| import vscode = require('vscode'); |
| import { isModSupported } from './goModules'; |
| import { |
| extractInstanceTestName, |
| findAllTestSuiteRuns, |
| getBenchmarkFunctions, |
| getTestFlags, |
| getTestFunctionDebugArgs, |
| getTestFunctions, |
| getTestTags, |
| goTest, |
| TestConfig |
| } from './testUtils'; |
| |
| // lastTestConfig holds a reference to the last executed TestConfig which allows |
| // the last test to be easily re-executed. |
| let lastTestConfig: TestConfig; |
| |
| // lastDebugConfig holds a reference to the last executed DebugConfiguration which allows |
| // the last test to be easily re-executed and debugged. |
| let lastDebugConfig: vscode.DebugConfiguration; |
| let lastDebugWorkspaceFolder: vscode.WorkspaceFolder; |
| |
| export type TestAtCursorCmd = 'debug' | 'test' | 'benchmark'; |
| |
| class NotFoundError extends Error {} |
| |
| async function _testAtCursor(goConfig: vscode.WorkspaceConfiguration, cmd: TestAtCursorCmd, args: any) { |
| const editor = vscode.window.activeTextEditor; |
| if (!editor) { |
| throw new NotFoundError('No editor is active.'); |
| } |
| if (!editor.document.fileName.endsWith('_test.go')) { |
| throw new NotFoundError('No tests found. Current file is not a test file.'); |
| } |
| |
| const getFunctions = cmd === 'benchmark' ? getBenchmarkFunctions : getTestFunctions; |
| const testFunctions = await getFunctions(editor.document, null); |
| // We use functionName if it was provided as argument |
| // Otherwise find any test function containing the cursor. |
| const testFunctionName = |
| args && args.functionName |
| ? args.functionName |
| : testFunctions.filter((func) => func.range.contains(editor.selection.start)).map((el) => el.name)[0]; |
| if (!testFunctionName) { |
| throw new NotFoundError('No test function found at cursor.'); |
| } |
| |
| await editor.document.save(); |
| |
| if (cmd === 'debug') { |
| return debugTestAtCursor(editor, testFunctionName, testFunctions, goConfig); |
| } else if (cmd === 'benchmark' || cmd === 'test') { |
| return runTestAtCursor(editor, testFunctionName, testFunctions, goConfig, cmd, args); |
| } else { |
| throw new Error(`Unsupported command: ${cmd}`); |
| } |
| } |
| |
| /** |
| * Executes the unit test at the primary cursor using `go test`. Output |
| * is sent to the 'Go' channel. |
| * @param goConfig Configuration for the Go extension. |
| * @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); |
| } |
| }); |
| } |
| |
| /** |
| * Executes the unit test at the primary cursor if found, otherwise re-runs the previous test. |
| * @param goConfig Configuration for the Go extension. |
| * @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(); |
| } |
| await testPrevious(); |
| } else { |
| console.error(err); |
| } |
| } |
| } |
| |
| /** |
| * Runs the test at cursor. |
| */ |
| async function runTestAtCursor( |
| editor: vscode.TextEditor, |
| testFunctionName: string, |
| testFunctions: vscode.DocumentSymbol[], |
| goConfig: vscode.WorkspaceConfiguration, |
| cmd: TestAtCursorCmd, |
| args: any |
| ) { |
| const testConfigFns = [testFunctionName]; |
| if (cmd !== 'benchmark' && extractInstanceTestName(testFunctionName)) { |
| testConfigFns.push(...findAllTestSuiteRuns(editor.document, testFunctions).map((t) => t.name)); |
| } |
| |
| const isMod = await isModSupported(editor.document.uri); |
| const testConfig: TestConfig = { |
| goConfig, |
| dir: path.dirname(editor.document.fileName), |
| flags: getTestFlags(goConfig, args), |
| functions: testConfigFns, |
| isBenchmark: cmd === 'benchmark', |
| isMod, |
| applyCodeCoverage: goConfig.get<boolean>('coverOnSingleTest') |
| }; |
| // Remember this config as the last executed test. |
| lastTestConfig = testConfig; |
| return goTest(testConfig); |
| } |
| |
| /** |
| * 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, null); |
| // 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.'); |
| return; |
| } |
| |
| const testFunction = currentTestFunctions[0]; |
| const simpleRunRegex = /t.Run\("([^"]+)",/; |
| const runRegex = /t.Run\(/; |
| let lineText: string; |
| let runMatch: RegExpMatchArray | null; |
| let simpleMatch: RegExpMatchArray | null; |
| 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.toString()); |
| console.error(err); |
| } |
| } |
| |
| /** |
| * Debugs the test at cursor. |
| * @param editorOrDocument The text document (or editor) that defines the test. |
| * @param testFunctionName The name of the test function. |
| * @param testFunctions All test function symbols defined by the document. |
| * @param goConfig Go configuration, i.e. flags, tags, environment, etc. |
| * @param sessionID If specified, `sessionID` is added to the debug |
| * configuration and can be used to identify the debug session. |
| * @returns Whether the debug session was successfully started. |
| */ |
| export async function debugTestAtCursor( |
| editorOrDocument: vscode.TextEditor | vscode.TextDocument, |
| testFunctionName: string, |
| testFunctions: vscode.DocumentSymbol[], |
| goConfig: vscode.WorkspaceConfiguration, |
| sessionID?: string |
| ) { |
| const doc = 'document' in editorOrDocument ? editorOrDocument.document : editorOrDocument; |
| const args = getTestFunctionDebugArgs(doc, testFunctionName, testFunctions); |
| const tags = getTestTags(goConfig); |
| const buildFlags = tags ? ['-tags', tags] : []; |
| const flagsFromConfig = getTestFlags(goConfig); |
| let foundArgsFlag = false; |
| flagsFromConfig.forEach((x) => { |
| if (foundArgsFlag) { |
| args.push(x); |
| return; |
| } |
| if (x === '-args') { |
| foundArgsFlag = true; |
| return; |
| } |
| buildFlags.push(x); |
| }); |
| const workspaceFolder = vscode.workspace.getWorkspaceFolder(doc.uri); |
| const debugConfig: vscode.DebugConfiguration = { |
| name: 'Debug Test', |
| type: 'go', |
| request: 'launch', |
| mode: 'test', |
| program: path.dirname(doc.fileName), |
| env: goConfig.get('testEnvVars', {}), |
| envFile: goConfig.get('testEnvFile'), |
| args, |
| buildFlags: buildFlags.join(' '), |
| sessionID |
| }; |
| lastDebugConfig = debugConfig; |
| lastDebugWorkspaceFolder = workspaceFolder; |
| return await vscode.debug.startDebugging(workspaceFolder, debugConfig); |
| } |
| |
| /** |
| * Runs all tests in the package of the source of the active editor. |
| * |
| * @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; |
| } |
| |
| 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); |
| } |
| |
| /** |
| * Runs all tests from all directories in the workspace. |
| * |
| * @param goConfig Configuration for the Go extension. |
| */ |
| export function testWorkspace(goConfig: vscode.WorkspaceConfiguration, args: any) { |
| if (!vscode.workspace.workspaceFolders.length) { |
| vscode.window.showInformationMessage('No workspace is open to run tests.'); |
| return; |
| } |
| let workspaceUri = vscode.workspace.workspaceFolders[0].uri; |
| if ( |
| vscode.window.activeTextEditor && |
| vscode.workspace.getWorkspaceFolder(vscode.window.activeTextEditor.document.uri) |
| ) { |
| workspaceUri = vscode.workspace.getWorkspaceFolder(vscode.window.activeTextEditor.document.uri).uri; |
| } |
| |
| const testConfig: TestConfig = { |
| goConfig, |
| dir: workspaceUri.fsPath, |
| flags: getTestFlags(goConfig, args), |
| includeSubDirectories: true |
| }; |
| // Remember this config as the last executed test. |
| lastTestConfig = testConfig; |
| |
| isModSupported(workspaceUri, true).then((isMod) => { |
| testConfig.isMod = isMod; |
| goTest(testConfig).then(null, (err) => { |
| console.error(err); |
| }); |
| }); |
| } |
| |
| /** |
| * Runs all tests in the source of the active editor. |
| * |
| * @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; |
| } |
| if (!editor.document.fileName.endsWith('_test.go')) { |
| vscode.window.showInformationMessage('No tests found. Current file is not a test file.'); |
| return; |
| } |
| |
| const getFunctions = isBenchmark ? getBenchmarkFunctions : getTestFunctions; |
| const isMod = await isModSupported(editor.document.uri); |
| |
| return editor.document |
| .save() |
| .then(() => { |
| return getFunctions(editor.document, null).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(null, (err) => { |
| console.error(err); |
| return Promise.resolve(false); |
| }); |
| } |
| |
| /** |
| * Runs the previously executed test. |
| */ |
| export function testPrevious() { |
| if (!lastTestConfig) { |
| vscode.window.showInformationMessage('No test has been recently executed.'); |
| return; |
| } |
| goTest(lastTestConfig).then(null, (err) => { |
| console.error(err); |
| }); |
| } |
| |
| /** |
| * Runs the previously executed test. |
| */ |
| export function debugPrevious() { |
| if (!lastDebugConfig) { |
| vscode.window.showInformationMessage('No test has been recently debugged.'); |
| return; |
| } |
| return vscode.debug.startDebugging(lastDebugWorkspaceFolder, lastDebugConfig); |
| } |