Skip to content
This repository has been archived by the owner on Jul 15, 2023. It is now read-only.

Add support for coverage markup #143

Merged
merged 13 commits into from
Mar 8, 2016
Merged
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
13 changes: 12 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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 current package",
"description": "Displays test coverage in the current package."
}
],
"debuggers": [
Expand Down Expand Up @@ -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",
Expand Down
9 changes: 6 additions & 3 deletions src/goCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand All @@ -22,7 +23,7 @@ export interface ICheckResult {
severity: string;
}

export function check(filename: string, buildOnSave = true, lintOnSave = true, vetOnSave = true): Promise<ICheckResult[]> {
export function check(filename: string, buildOnSave = true, lintOnSave = true, vetOnSave = true, coverOnSave = false): Promise<ICheckResult[]> {
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)
Expand Down Expand Up @@ -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));
}
156 changes: 156 additions & 0 deletions src/goCover.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*---------------------------------------------------------
* 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.");
}

var coveredHighLight = vscode.window.createTextEditorDecorationType({
// Green
backgroundColor: 'rgba(64,128,64,0.5)',
isWholeLine: false
}),
uncoveredHighLight = vscode.window.createTextEditorDecorationType({
// Red
backgroundColor: 'rgba(128,64,64,0.5)',
isWholeLine: false
}),
coverageFiles = {};

interface coverageFile {
filename: string;
uncoveredRange: vscode.Range[];
coveredRange: vscode.Range[];
}

function clearCoverage() {
applyCoverage(true);
coverageFiles = {};
}

export function removeCodeCoverage(e: vscode.TextDocumentChangeEvent) {
var editor = vscode.window.visibleTextEditors.find((value, index, obj) => {
return value.document == e.document;
});
if (!editor) {
return;
}
for(var filename in coverageFiles) {
if (editor.document.uri.fsPath.endsWith(filename)) {
highlightCoverage(editor, coverageFiles[filename], true);
delete coverageFiles[filename];
}
}
}

export function coverageCurrentPackage() {
var editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage("No editor is active.");
return;
}
getCoverage(editor.document.uri.fsPath);
}

export function getCodeCoverage(editor: vscode.TextEditor) {
for(var filename in coverageFiles) {
if (editor.document.uri.fsPath.endsWith(filename)) {
highlightCoverage(editor, coverageFiles[filename], false);
}
}
}

function applyCoverage(remove: boolean = false) {
console.log(coverageFiles)
for(var filename in coverageFiles) {
var file = coverageFiles[filename];
// Highlight lines in current editor.
var editor = vscode.window.visibleTextEditors.find((value, index, obj) => {
return value.document.fileName.endsWith(filename);
});
console.log(editor);
if (editor) {
highlightCoverage(editor, file, remove);
}
}
}

function highlightCoverage(editor: vscode.TextEditor, file: coverageFile, remove: boolean) {
editor.setDecorations(uncoveredHighLight, remove ? [] : file.uncoveredRange);
editor.setDecorations(coveredHighLight, remove ? [] : file.coveredRange);

}

export function getCoverage(filename: string): Promise<any[]> {
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 {
// Clear existing coverage files
clearCoverage();
if (err && (<any>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
});

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) {
var coverage = coverageFiles[fileRange[1]] || { coveredRange:[], uncoveredRange:[]};

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) {
coverage.coveredRange.push(range);
}
// Not Covered
else {
coverage.uncoveredRange.push(range);
}
coverageFiles[fileRange[1]] = coverage;
}
});
lines.on('close', function(data) {
applyCoverage();
resolve(ret);
});
} catch (e) {
vscode.window.showInformationMessage(e.msg);
reject(e);
}
});
});
}
12 changes: 10 additions & 2 deletions src/goMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { check, ICheckResult } from './goCheck';
import { setupGoPathAndOfferToInstallTools } from './goInstallTools'
import { GO_MODE } from './goMode'
import { showHideStatus } from './goStatus'
import { coverageCurrentPackage, getCodeCoverage, removeCodeCoverage } from './goCover';
import { testAtCursor, testCurrentPackage, testCurrentFile } from './goTest'

let diagnosticCollection: vscode.DiagnosticCollection;
Expand All @@ -32,11 +33,14 @@ export function activate(ctx: vscode.ExtensionContext): void {
ctx.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider(GO_MODE, new GoDocumentFormattingEditProvider()));
ctx.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(GO_MODE, new GoDocumentSymbolProvider()));
ctx.subscriptions.push(vscode.languages.registerRenameProvider(GO_MODE, new GoRenameProvider()));

diagnosticCollection = vscode.languages.createDiagnosticCollection('go');
ctx.subscriptions.push(diagnosticCollection);

vscode.workspace.onDidChangeTextDocument(removeCodeCoverage, null, ctx.subscriptions);
vscode.window.onDidChangeActiveTextEditor(showHideStatus, null, ctx.subscriptions);
vscode.window.onDidChangeActiveTextEditor(getCodeCoverage, null, ctx.subscriptions);

setupGoPathAndOfferToInstallTools();
startBuildOnSaveWatcher(ctx.subscriptions);

Expand All @@ -59,6 +63,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", () => {
coverageCurrentPackage();
}));

vscode.languages.setLanguageConfiguration(GO_MODE.language, {
indentationRules: {
Expand Down Expand Up @@ -121,7 +129,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<vscode.Uri, vscode.Diagnostic[]> = new Map();;
Expand Down