Skip to content

Commit

Permalink
Add istanbul code coverage tooling
Browse files Browse the repository at this point in the history
  • Loading branch information
petkaantonov committed Jan 22, 2015
1 parent 6fab36b commit ce6e495
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 13 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -2,3 +2,4 @@ node_modules
test/browser/bundle.js
js/*
zalgo.js
coverage/*
49 changes: 49 additions & 0 deletions .istanbul.yml
@@ -0,0 +1,49 @@
verbose: false
instrumentation:
root: .
default-excludes: true
excludes: []
embed-source: false
variable: __coverage__
compact: true
preserve-comments: false
complete-copy: false
save-baseline: false
baseline-file: ./coverage/coverage-baseline.json
include-all-sources: false
reporting:
print: summary
reports:
- lcov
dir: ./coverage
watermarks:
statements: [50, 80]
lines: [50, 80]
functions: [50, 80]
branches: [50, 80]
report-config:
clover: {file: clover.xml}
cobertura: {file: cobertura-coverage.xml}
json: {file: coverage-final.json}
json-summary: {file: coverage-summary.json}
lcovonly: {file: lcov.info}
teamcity: {file: null}
text: {file: null, maxCols: 0}
text-summary: {file: null}
hooks:
hook-run-in-context: false
post-require-hook: null
handle-sigint: false
check:
global:
statements: 0
lines: 0
branches: 0
functions: 0
excludes: []
each:
statements: 0
lines: 0
branches: 0
functions: 0
excludes: []
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -3,6 +3,7 @@
title="Promises/A+ 1.1 compliant" align="right" />
</a>
[![Build Status](https://travis-ci.org/petkaantonov/bluebird.svg?branch=master)](https://travis-ci.org/petkaantonov/bluebird)
<!-- coverage -->[![coverage-88%](http://img.shields.io/badge/coverage-88%-green.svg)](http://petkaantonov.github.io/bluebird/coverage/debug/index.html)<!-- /coverage -->


#Introduction
Expand Down Expand Up @@ -476,6 +477,7 @@ Keep the test tab active because some tests are timing-sensitive and will fail i
The value of boolean flags is determined by presence, if you want to pass false value for a boolean flag, use the `no-`-prefix e.g. `--no-browser`.

- `--run=String`. Which tests to run (or compile when testing in browser). Default `"all"`
- `--cover=String`. Create code coverage using the String as istanbul reporter. Coverage is created in the ./coverage folder. No coverage is created by default, default reporter is `"html"` (use `--cover` to use default reporter).
- `--browser` - Whether to compile tests for browsers. Default `false`.
- `--port=Number` - Whe port where local server is hosted when testing in browser. Default `9999`
- `--execute-browser-tests` - Whether to execute the compiled tests for browser when using `--browser`. Default `true`.
Expand Down
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -20,6 +20,7 @@
"scripts": {
"lint": "node scripts/jshint.js",
"test": "node tools/test.js",
"istanbul": "istanbul",
"prepublish": "node tools/build.js --no-debug --main --zalgo --browser --minify"
},
"homepage": "https://github.com/petkaantonov/bluebird",
Expand All @@ -44,6 +45,7 @@
"cross-spawn": "^0.2.3",
"glob": "^4.3.2",
"grunt-saucelabs": "~8.4.1",
"istanbul": "^0.3.5",
"jshint": "^2.5.11",
"jshint-stylish": "~0.2.0",
"mkdirp": "~0.5.0",
Expand Down
3 changes: 2 additions & 1 deletion src/captured_trace.js
Expand Up @@ -3,7 +3,8 @@ module.exports = function() {
var async = require("./async.js");
var ASSERT = require("./assert.js");
var inherits = require("./util.js").inherits;
var bluebirdFramePattern = /[\\\/]bluebird[\\\/]js[\\\/](main|debug|zalgo)/;
var bluebirdFramePattern =
/[\\\/]bluebird[\\\/]js[\\\/](main|debug|zalgo|instrumented)/;
var stackFramePattern = null;
var formatStack = null;

Expand Down
31 changes: 24 additions & 7 deletions tools/build.js
Expand Up @@ -262,16 +262,31 @@ function buildBrowser(sources, dir, tmpDir, depsRequireCode, minify, npmPackage,
});
}

var root = process.cwd();
// Since rm -rf is called, better be sure...
if (path.basename(root).toLowerCase() !== "bluebird") {
throw new Error("cwd must be se to bluebird project root. Cwd is currently\n\n" +
" " + process.cwd() + "\n");
}
var dirs = {
main: path.join(root, "js", "main"),
debug: path.join(root, "js", "debug"),
zalgo: path.join(root, "js", "zalgoDir"),
browser: path.join(root, "js", "browser"),
browserTmp: path.join(root, "js", "tmp"),
instrumented: path.join(root, "js", "instrumented"),
coverage: path.join(root, "coverage")
};

function build(options) {
var npmPackage = fs.readFileAsync("./package.json").then(JSON.parse);
var sourceFileNames = getSourcePaths(options.features);
var license = utils.getLicense();
var root = process.cwd();
var mainDir = ensureDirectory(path.join(root, "js", "main"), options.main);
var debugDir = ensureDirectory(path.join(root, "js", "debug"), options.debug);
var zalgoDir = ensureDirectory(path.join(root, "js", "zalgo"), options.zalgo);
var browserDir = ensureDirectory(path.join(root, "js", "browser"), options.browser);
var browserTmpDir = ensureDirectory(path.join(root, "js", "tmp"), options.browser);
var mainDir = ensureDirectory(dirs.main, options.main);
var debugDir = ensureDirectory(dirs.debug, options.debug);
var zalgoDir = ensureDirectory(dirs.zalgo, options.zalgo);
var browserDir = ensureDirectory(dirs.browser, options.browser);
var browserTmpDir = ensureDirectory(dirs.browserTmp, options.browser);
return license.then(function(license) {
return sourceFileNames.map(function(sourceFileName) {
return jobRunner.run(function() {
Expand Down Expand Up @@ -309,12 +324,14 @@ function build(options) {
zalgo = buildZalgo(results, depsRequireCode, zalgoDir);
if (options.browser)
browser = buildBrowser(results, browserDir, browserTmpDir, depsRequireCode, options.minify, npmPackage, license);

return Promise.all([main, debug, zalgo, browser]);
});
}

module.exports = build;
module.exports.ensureDirectory = ensureDirectory;
module.exports.dirs = dirs;


if (require.main === module) {
Expand Down
8 changes: 7 additions & 1 deletion tools/mocha_runner.js
Expand Up @@ -65,7 +65,10 @@ module.exports = function mochaRun(progress) {
global.clearInterval = clearInterval;
}
var failures = [];
global.adapter = require("./js/debug/bluebird.js");
global.adapter = cover
? require("./js/instrumented/bluebird.js")
: require("./js/debug/bluebird.js");
global.Promise = adapter;
return Promise.each(testGroup, function(test, index, length) {
var mocha = new Mocha({
reporter: "spec",
Expand Down Expand Up @@ -111,5 +114,8 @@ module.exports = function mochaRun(progress) {
}
throw error;
}
if (cover) {
return __coverage__;
}
});
};
128 changes: 125 additions & 3 deletions tools/test.js
Expand Up @@ -2,12 +2,15 @@ var assert = require("assert");
assert.equal(require.main, module);
var Promise = require("bluebird");
var build = require("./build.js");
var tableLogger = require("./utils.js").tableLogger;
var utils = require("./utils.js");
var tableLogger = utils.tableLogger;
var argv = require("optimist").argv;
var glob = Promise.promisify(require("glob"));
var path = require("path");
var mkdirp = Promise.promisify(require("mkdirp"));
var jobRunner = require("./job-runner/job-runner.js");
var mochaRunner = require("./mocha_runner.js");
var fs = Promise.promisifyAll(require("fs"));
jobRunner.setVerbose(0);
// Random slowness after tests complete
function getTests(options) {
Expand Down Expand Up @@ -44,6 +47,54 @@ function getTests(options) {
});
}

function getColorForCoverage(coveragePct) {
var colorThresholds = {
95: "brightgreen",
85: "green",
80: "yellowgreen",
70: "yellow",
60: "red"
};
var values = Object.keys(colorThresholds).map(Number).sort(function(a, b) {
return b - a;
});
for (var i = 0; i < values.length; ++i) {
if (coveragePct >= values[i]) return colorThresholds[values[i].toString()];
}
return colorThresholds[values[values.length - 1].toString()];
}

function getCoverage() {
return utils.run("npm", ["run", "istanbul", "--", "report", "text-summary"]).then(function(result) {
var stdout = result.stdout;
var pctPattern = /(\d+\.\d+)%/g;
var matches = stdout.match(pctPattern);
var sum = matches.map(function(pct) {
return parseFloat(pct.replace(/[^0-9.]/g, ""))
}).reduce(function(a, b) {
return a + b;
}, 0);
var average = Math.round(sum / matches.length);
return average;
});
}

function generateCoverageBadge(coverage) {
var text = "coverage-" + coverage + "%";
var color = getColorForCoverage(coverage);
var imgSrc = "http://img.shields.io/badge/" + text + "-" + color + ".svg";
var link = "http://petkaantonov.github.io/bluebird/coverage/debug/index.html";
var markdown = "[!["+text+"]("+imgSrc+")]("+link+")";
return markdown;
}

function writeCoverageFile(coverage, groupNumber) {
var dir = build.dirs.coverage;
var fileName = path.join(dir, "coverage-group" + groupNumber + ".json");
var json = JSON.stringify(coverage);
return fs.writeFileAsync(fileName, json, "utf8").thenReturn(fileName);
}

function needsFreshProcess(testName) {
return /domain|schedule/.test(testName);
}
Expand All @@ -56,7 +107,8 @@ function runTestGroup(testGroup, options, progress) {
context: {
testGroup: testGroup,
singleTest: options.singleTest,
fakeTimers: options.fakeTimers
fakeTimers: options.fakeTimers,
cover: options.cover
}
});
}
Expand Down Expand Up @@ -97,6 +149,7 @@ var options = {
return false;
}
})(),
cover: false,
testName: testName,
singleTest: false,
saucelabs: !!argv.saucelabs,
Expand All @@ -109,13 +162,58 @@ var options = {
? argv["fake-timers"] : true,
jsHint: typeof argv["js-hint"] === "boolean" ? argv["js-hint"] : true
};
options.cover = !!argv["cover"] && !options.testBrowser;

if (options.cover && typeof argv["cover"] === "string") {
options.coverFormat = argv["cover"];
} else {
options.coverFormat = "html";
}

var jsHint = options.jsHint ? require("./jshint.js")() : Promise.resolve();
var tests = getTests(options);
var buildResult = build({
debug: true
});
if (options.cover) {
var exclusions = ["assert.js", "captured_trace.js"];
var coverageInstrumentedRoot = build.ensureDirectory(build.dirs.instrumented,options.cover);
var coverageReportsRoot = build.ensureDirectory(build.dirs.coverage, true);
buildResult = Promise.join(coverageInstrumentedRoot, buildResult, coverageReportsRoot, function() {
return utils.run("npm", ["-v"]).then(function(result) {
var version = result.stdout.split(".").map(Number);
if (version[0] < 2) {
throw new Error("Npm version 2.x.x required, current version is " + result.stdout);
}
});
}).tap(function() {
var copyExclusions = Promise.map(exclusions, function(exclusion) {
var fromPath = path.join(build.dirs.debug, exclusion);
var toPath = path.join(build.dirs.instrumented, exclusion);
return fs.readFileAsync(fromPath, "utf8").then(function(contents) {
return fs.writeFileAsync(toPath, contents, "utf8");
});
});
var args = [
"run",
"istanbul",
"--",
"instrument",
"--output",
build.dirs.instrumented,
"--no-compact",
"--preserve-comments",
"--embed-source"
];
exclusions.forEach(function(x) {
args.push("-x", x);
});
args.push(build.dirs.debug);
var istanbul = utils.run("npm", args, null, true);
return Promise.all([istanbul, copyExclusions]);
});
}

var testResults = Promise.join(tests, buildResult, function(tests) {
var singleTest = tests.length === 1;
options.singleTest = singleTest;
Expand All @@ -128,16 +226,40 @@ var testResults = Promise.join(tests, buildResult, function(tests) {
process.stdout.cursorTo(0, 0);
process.stdout.clearScreenDown();
tableLogger.addTests(tests);
return Promise.map(combineTests(tests), function(testGroup) {
return Promise.map(combineTests(tests), function(testGroup, index) {
return runTestGroup(testGroup, options, function(test) {
if (test.failed) {
tableLogger.testFail(test);
} else {
tableLogger.testSuccess(test);
}
}).then(function(maybeCoverage) {
if (options.cover) {
return writeCoverageFile(maybeCoverage.result, index + 1);
}
})
}).then(function() {
var p = Promise.resolve();
if (options.cover) {
var coverage = getCoverage();
if (process.execPath.indexOf("iojs") >= 0 && testName === "all") {
var badge = coverage.then(generateCoverageBadge);
var readme = fs.readFileAsync("README.md", "utf8");
p = Promise.join(readme, badge, function(readmeContents, badgeMarkDown) {
var coveragePattern = /<!-- coverage -->.*?<!-- \/coverage -->/
badgeMarkDown = "<!-- coverage -->" + badgeMarkDown+ "<!-- /coverage -->";
readmeContents = readmeContents.replace(coveragePattern, badgeMarkDown);
return fs.writeFileAsync("README.md", readmeContents, "utf8");
});
}
p = p.then(function() {
return utils.run("npm", ["run", "istanbul", "--", "report", options.coverFormat], null, true);
}).return(coverage).then(function(coverage) {
console.log("Total coverage " + coverage + "%");
});
}
console.log("All tests passed");
return p;
});
}
});
Expand Down
4 changes: 3 additions & 1 deletion tools/utils.js
Expand Up @@ -35,7 +35,7 @@ function getLicense() {
}


function run(cmd, args, dir, pipe) {
function run(cmd, args, dir, log) {
return new Promise(function(resolve, reject) {
function makeResult(errorMessage) {
var ret = errorMessage ? new Error(errorMessage) : {};
Expand All @@ -49,9 +49,11 @@ function run(cmd, args, dir, pipe) {
var c = spawn(cmd, args, {stdin: ["ignore", "ignore", "ignore"], cwd: dir || process.cwd()});

c.stdout.on("data", function(data) {
if (log) process.stdout.write(data.toString());
out += data;
});
c.stderr.on("data", function(data) {
if (log) process.stderr.write(data.toString());
err += data;
});

Expand Down

0 comments on commit ce6e495

Please sign in to comment.