diff --git a/package.json b/package.json index b1fe01ff7..fed0341a3 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,8 @@ "onCommand:go.gopath", "onCommand:go.test.cursor", "onCommand:go.test.package", - "onCommand:go.test.file" + "onCommand:go.test.file", + "onCommand:go.test.coverage" ], "main": "./out/src/goMain", "contributes": { @@ -94,6 +95,11 @@ "command": "go.test.file", "title": "Go: Run tests in file", "description": "Runs all unit tests in the current file." + }, + { + "command": "go.test.coverage", + "title": "Go: Test coverage in file", + "description": "Displays test coverage in the current file." } ], "debuggers": [ @@ -209,6 +215,11 @@ "default": false, "description": "[EXPERIMENTAL] Run formatting tool on save." }, + "go.coverOnSave": { + "type": "boolean", + "default": false, + "description": "Run 'go test -coverprofile' on save" + }, "go.testTimeout": { "type": "string", "default": "30s", diff --git a/src/goCheck.ts b/src/goCheck.ts index e3f55eef3..702da810f 100644 --- a/src/goCheck.ts +++ b/src/goCheck.ts @@ -9,7 +9,8 @@ import cp = require('child_process'); import path = require('path'); import os = require('os'); import fs = require('fs'); -import { getBinPath, getGoRuntimePath } from './goPath' +import { getBinPath, getGoRuntimePath } from './goPath'; +import { getCoverage } from './goCover'; if (!getGoRuntimePath()) { vscode.window.showInformationMessage("No 'go' binary could be found on PATH or in GOROOT."); @@ -22,7 +23,7 @@ export interface ICheckResult { severity: string; } -export function check(filename: string, buildOnSave = true, lintOnSave = true, vetOnSave = true): Promise { +export function check(filename: string, buildOnSave = true, lintOnSave = true, vetOnSave = true, coverOnSave = false): Promise { var gobuild = !buildOnSave ? Promise.resolve([]) : new Promise((resolve, reject) => { var tmppath = path.normalize(path.join(os.tmpdir(), "go-code-check")) var cwd = path.dirname(filename) @@ -104,6 +105,8 @@ export function check(filename: string, buildOnSave = true, lintOnSave = true, v } }); }); + + var gocover = !coverOnSave ? Promise.resolve([]) : getCoverage(filename); - return Promise.all([gobuild, golint, govet]).then(resultSets => [].concat.apply([], resultSets)); + return Promise.all([gobuild, golint, govet, gocover]).then(resultSets => [].concat.apply([], resultSets)); } \ No newline at end of file diff --git a/src/goCover.ts b/src/goCover.ts new file mode 100644 index 000000000..31fa6fa69 --- /dev/null +++ b/src/goCover.ts @@ -0,0 +1,100 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +'use strict'; + +import vscode = require('vscode'); +import cp = require('child_process'); +import path = require('path'); +import os = require('os'); +import fs = require('fs'); +import { getBinPath, getGoRuntimePath } from './goPath'; +import rl = require('readline'); + +if (!getGoRuntimePath()) { + vscode.window.showInformationMessage("No 'go' binary could be found on PATH or in GOROOT."); +} + +export function coverageCurrentFile() { + var editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showInformationMessage("No editor is active."); + return; + } + getCoverage(editor.document.uri.fsPath); +} + +export function getCoverage(filename: string): Promise { + return new Promise((resolve, reject) => { + var tmppath = path.normalize(path.join(os.tmpdir(), "go-code-cover")) + var cwd = path.dirname(filename) + var args = ["test", "-coverprofile=" + tmppath]; + cp.execFile(getGoRuntimePath(), args, { cwd: cwd }, (err, stdout, stderr) => { + try { + if (err && (err).code == "ENOENT") { + vscode.window.showInformationMessage("Could not generate coverage report. Install Go from http://golang.org/dl/."); + return resolve([]); + } + var ret = []; + + var lines = rl.createInterface({ + input: fs.createReadStream(tmppath), + output: undefined + }); + + var coveredRange = [], + uncoveredRange =[], + uncoveredHighLight = vscode.window.createTextEditorDecorationType({ + // Red + backgroundColor: 'rgba(128,64,64,0.5)', + isWholeLine: false + }), coveredHighLight = vscode.window.createTextEditorDecorationType({ + // Green + backgroundColor: 'rgba(64,128,64,0.5)', + isWholeLine: false + }); + + lines.on('line', function(data: string) { + // go test coverageprofile generates output: + // filename:StartLine.StartColumn,EndLine.EndColumn Hits IsCovered + var fileRange = data.match(/([^:]+)\:([\d]+)\.([\d]+)\,([\d]+)\.([\d]+)\s([\d]+)\s([\d]+)/); + if (fileRange) { + // If line matches active file + if (filename.endsWith(fileRange[1])) { + var range = { + range: new vscode.Range( + // Start Line converted to zero based + parseInt(fileRange[2]) - 1, + // Start Column converted to zero based + parseInt(fileRange[3]) - 1, + // End Line converted to zero based + parseInt(fileRange[4]) - 1, + // End Column converted to zero based + parseInt(fileRange[5]) - 1 + ) + }; + // If is Covered + if (parseInt(fileRange[7]) === 1) { + coveredRange.push(range); + } + // Not Covered + else { + uncoveredRange.push(range); + } + } + } + }); + lines.on('close', function(data) { + // Highlight lines in current editor. + vscode.window.activeTextEditor.setDecorations(uncoveredHighLight, uncoveredRange); + vscode.window.activeTextEditor.setDecorations(coveredHighLight, coveredRange); + resolve(ret); + }); + } catch (e) { + vscode.window.showInformationMessage(e.msg); + reject(e); + } + }); + }); +} \ No newline at end of file diff --git a/src/goMain.ts b/src/goMain.ts index d8ce9fbfe..e22f64220 100644 --- a/src/goMain.ts +++ b/src/goMain.ts @@ -19,6 +19,7 @@ import { check, ICheckResult } from './goCheck'; import { setupGoPathAndOfferToInstallTools } from './goInstallTools' import { GO_MODE } from './goMode' import { showHideStatus } from './goStatus' +import { coverageCurrentFile } from './goCover'; import { testAtCursor, testCurrentPackage, testCurrentFile } from './goTest' let diagnosticCollection: vscode.DiagnosticCollection; @@ -59,6 +60,10 @@ export function activate(ctx: vscode.ExtensionContext): void { let goConfig = vscode.workspace.getConfiguration('go'); testCurrentFile(goConfig['testTimeout']); })); + + ctx.subscriptions.push(vscode.commands.registerCommand("go.test.coverage", () => { + coverageCurrentFile(); + })); vscode.languages.setLanguageConfiguration(GO_MODE.language, { indentationRules: { @@ -121,7 +126,7 @@ function runBuilds(document: vscode.TextDocument, goConfig: vscode.WorkspaceConf } var uri = document.uri; - check(uri.fsPath, goConfig['buildOnSave'], goConfig['lintOnSave'], goConfig['vetOnSave']).then(errors => { + check(uri.fsPath, goConfig['buildOnSave'], goConfig['lintOnSave'], goConfig['vetOnSave'], goConfig['coverOnSave']).then(errors => { diagnosticCollection.clear(); let diagnosticMap: Map = new Map();;