blob: a9e71c98cab53f304eb76e72ae84d9c586f365a0 [file] [log] [blame]
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------*/
'use strict';
import cp = require('child_process');
import path = require('path');
import vscode = require('vscode');
import { toolExecutionEnvironment } from './goEnv';
import { promptForMissingTool } from './goInstallTools';
import { GoDocumentSymbolProvider } from './goOutline';
import { outputChannel } from './goStatus';
import { getBinPath, getGoConfig } from './util';
const generatedWord = 'Generated ';
/**
* If current active editor has a Go file, returns the editor.
*/
function checkActiveEditor(): vscode.TextEditor | undefined {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('Cannot generate unit tests. No editor selected.');
return;
}
if (!editor.document.fileName.endsWith('.go')) {
vscode.window.showInformationMessage('Cannot generate unit tests. File in the editor is not a Go file.');
return;
}
if (editor.document.isDirty) {
vscode.window.showInformationMessage('File has unsaved changes. Save and try again.');
return;
}
return editor;
}
/**
* Toggles between file in current active editor and the corresponding test file.
*/
export function toggleTestFile(): void {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('Cannot toggle test file. No editor selected.');
return;
}
const currentFilePath = editor.document.fileName;
if (!currentFilePath.endsWith('.go')) {
vscode.window.showInformationMessage('Cannot toggle test file. File in the editor is not a Go file.');
return;
}
let targetFilePath = '';
if (currentFilePath.endsWith('_test.go')) {
targetFilePath = currentFilePath.substr(0, currentFilePath.lastIndexOf('_test.go')) + '.go';
} else {
targetFilePath = currentFilePath.substr(0, currentFilePath.lastIndexOf('.go')) + '_test.go';
}
for (const doc of vscode.window.visibleTextEditors) {
if (doc.document.fileName === targetFilePath) {
vscode.commands.executeCommand('vscode.open', vscode.Uri.file(targetFilePath), doc.viewColumn);
return;
}
}
vscode.commands.executeCommand('vscode.open', vscode.Uri.file(targetFilePath));
}
export function generateTestCurrentPackage(): Promise<boolean> {
const editor = checkActiveEditor();
if (!editor) {
return;
}
return generateTests(
{
dir: path.dirname(editor.document.uri.fsPath),
isTestFile: editor.document.fileName.endsWith('_test.go')
},
getGoConfig(editor.document.uri)
);
}
export function generateTestCurrentFile(): Promise<boolean> {
const editor = checkActiveEditor();
if (!editor) {
return;
}
return generateTests(
{
dir: editor.document.uri.fsPath,
isTestFile: editor.document.fileName.endsWith('_test.go')
},
getGoConfig(editor.document.uri)
);
}
export async function generateTestCurrentFunction(): Promise<boolean> {
const editor = checkActiveEditor();
if (!editor) {
return;
}
const functions = await getFunctions(editor.document);
const selection = editor.selection;
const currentFunction: vscode.DocumentSymbol = functions.find(
(func) => selection && func.range.contains(selection.start)
);
if (!currentFunction) {
vscode.window.showInformationMessage('No function found at cursor.');
return Promise.resolve(false);
}
let funcName = currentFunction.name;
const funcNameParts = funcName.match(/^\(\*?(.*)\)\.(.*)$/);
if (funcNameParts != null && funcNameParts.length === 3) {
// receiver type specified
const rType = funcNameParts[1].replace(/^\w/, (c) => c.toUpperCase());
const fName = funcNameParts[2].replace(/^\w/, (c) => c.toUpperCase());
funcName = rType + fName;
}
return generateTests(
{
dir: editor.document.uri.fsPath,
func: funcName,
isTestFile: editor.document.fileName.endsWith('_test.go')
},
getGoConfig(editor.document.uri)
);
}
/**
* Input to goTests.
*/
interface Config {
/**
* The working directory for `gotests`.
*/
dir: string;
/**
* Specific function names to generate tests skeleton.
*/
func?: string;
/**
* Whether or not the file to generate test functions for is a test file.
*/
isTestFile?: boolean;
}
function generateTests(conf: Config, goConfig: vscode.WorkspaceConfiguration): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
const cmd = getBinPath('gotests');
let args = ['-w'];
const goGenerateTestsFlags: string[] = goConfig['generateTestsFlags'] || [];
for (let i = 0; i < goGenerateTestsFlags.length; i++) {
const flag = goGenerateTestsFlags[i];
if (flag === '-w' || flag === 'all') {
continue;
}
if (flag === '-only') {
i++;
continue;
}
args.push(flag);
}
if (conf.func) {
args = args.concat(['-only', `^${conf.func}$`, conf.dir]);
} else {
args = args.concat(['-all', conf.dir]);
}
cp.execFile(cmd, args, { env: toolExecutionEnvironment() }, (err, stdout, stderr) => {
outputChannel.appendLine('Generating Tests: ' + cmd + ' ' + args.join(' '));
try {
if (err && (<any>err).code === 'ENOENT') {
promptForMissingTool('gotests');
return resolve(false);
}
if (err) {
console.log(err);
outputChannel.appendLine(err.message);
return reject('Cannot generate test due to errors');
}
let message = stdout;
let testsGenerated = false;
// Expected stdout is of the format "Generated TestMain\nGenerated Testhello\n"
if (stdout.startsWith(generatedWord)) {
const lines = stdout
.split('\n')
.filter((element) => {
return element.startsWith(generatedWord);
})
.map((element) => {
return element.substr(generatedWord.length);
});
message = `Generated ${lines.join(', ')}`;
testsGenerated = true;
}
vscode.window.showInformationMessage(message);
outputChannel.append(message);
if (testsGenerated && !conf.isTestFile) {
toggleTestFile();
}
return resolve(true);
} catch (e) {
vscode.window.showInformationMessage(e.msg);
outputChannel.append(e.msg);
reject(e);
}
});
});
}
async function getFunctions(doc: vscode.TextDocument): Promise<vscode.DocumentSymbol[]> {
const documentSymbolProvider = new GoDocumentSymbolProvider();
const symbols = await documentSymbolProvider.provideDocumentSymbols(doc, null);
return symbols[0].children.filter((sym) => sym.kind === vscode.SymbolKind.Function);
}