Commit a9e089a0 by lxyang

上传

parents
This diff is collapsed. Click to expand it.
Copyright 2012 Yahoo! Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Yahoo! Inc. nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
This diff is collapsed. Click to expand it.
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
/*jslint nomen: true */
var path = require('path'),
Store = require('./lib/store'),
Report = require('./lib/report'),
meta = require('./lib/util/meta');
//register our standard plugins
require('./lib/register-plugins');
/**
* the top-level API for `istanbul`. provides access to the key libraries in
* istanbul so you can write your own tools using `istanbul` as a library.
*
* Usage
* -----
*
* var istanbul = require('istanbul');
*
*
* @class Istanbul
* @static
* @module main
* @main main
*/
module.exports = {
/**
* the Instrumenter class.
* @property Instrumenter
* @type Instrumenter
* @static
*/
Instrumenter: require('./lib/instrumenter'),
/**
* the Store class.
* @property Store
* @type Store
* @static
*/
Store: Store,
/**
* the Collector class
* @property Collector
* @type Collector
* @static
*/
Collector: require('./lib/collector'),
/**
* the hook module
* @property hook
* @type Hook
* @static
*/
hook: require('./lib/hook'),
/**
* the Report class
* @property Report
* @type Report
* @static
*/
Report: Report,
/**
* the config module
* @property config
* @type Config
* @static
*/
config: require('./lib/config'),
/**
* the Reporter class
* @property Reporter
* @type Reporter
* @static
*/
Reporter: require('./lib/reporter'),
/**
* utility for processing coverage objects
* @property utils
* @type ObjectUtils
* @static
*/
utils: require('./lib/object-utils'),
/**
* asynchronously returns a function that can match filesystem paths.
* The function returned in the callback may be passed directly as a `matcher`
* to the functions in the `hook` module.
*
* When no options are passed, the match function is one that matches all JS
* files under the current working directory except ones under `node_modules`
*
* Match patterns are `ant`-style patterns processed using the `glob` library.
* Examples not provided due to limitations in putting asterisks inside
* jsdoc comments. Please refer to tests under `test/other/test-matcher.js`
* for examples.
*
* @method matcherFor
* @static
* @param {Object} options Optional. Lookup options.
* @param {String} [options.root] the root of the filesystem tree under
* which to match files. Defaults to `process.cwd()`
* @param {Array} [options.includes] an array of include patterns to match.
* Defaults to all JS files under the root.
* @param {Array} [options.excludes] and array of exclude patterns. File paths
* matching these patterns will be excluded by the returned matcher.
* Defaults to files under `node_modules` found anywhere under root.
* @param {Function(err, matchFunction)} callback The callback that is
* called with two arguments. The first is an `Error` object in case
* of errors or a falsy value if there were no errors. The second
* is a function that may be use as a matcher.
*/
matcherFor: require('./lib/util/file-matcher').matcherFor,
/**
* the version of the library
* @property VERSION
* @type String
* @static
*/
VERSION: meta.VERSION,
/**
* the abstract Writer class
* @property Writer
* @type Writer
* @static
*/
Writer: require('./lib/util/writer').Writer,
/**
* the abstract ContentWriter class
* @property ContentWriter
* @type ContentWriter
* @static
*/
ContentWriter: require('./lib/util/writer').ContentWriter,
/**
* the concrete FileWriter class
* @property FileWriter
* @type FileWriter
* @static
*/
FileWriter: require('./lib/util/file-writer'),
//undocumented
_yuiLoadHook: require('./lib/util/yui-load-hook'),
//undocumented
TreeSummarizer: require('./lib/util/tree-summarizer'),
//undocumented
assetsDir: path.resolve(__dirname, 'lib', 'assets')
};
body, html {
margin:0; padding: 0;
height: 100%;
}
body {
font-family: Helvetica Neue, Helvetica, Arial;
font-size: 14px;
color:#333;
}
.small { font-size: 12px; }
*, *:after, *:before {
-webkit-box-sizing:border-box;
-moz-box-sizing:border-box;
box-sizing:border-box;
}
h1 { font-size: 20px; margin: 0;}
h2 { font-size: 14px; }
pre {
font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
margin: 0;
padding: 0;
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
}
a { color:#0074D9; text-decoration:none; }
a:hover { text-decoration:underline; }
.strong { font-weight: bold; }
.space-top1 { padding: 10px 0 0 0; }
.pad2y { padding: 20px 0; }
.pad1y { padding: 10px 0; }
.pad2x { padding: 0 20px; }
.pad2 { padding: 20px; }
.pad1 { padding: 10px; }
.space-left2 { padding-left:55px; }
.space-right2 { padding-right:20px; }
.center { text-align:center; }
.clearfix { display:block; }
.clearfix:after {
content:'';
display:block;
height:0;
clear:both;
visibility:hidden;
}
.fl { float: left; }
@media only screen and (max-width:640px) {
.col3 { width:100%; max-width:100%; }
.hide-mobile { display:none!important; }
}
.quiet {
color: #7f7f7f;
color: rgba(0,0,0,0.5);
}
.quiet a { opacity: 0.7; }
.fraction {
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 10px;
color: #555;
background: #E8E8E8;
padding: 4px 5px;
border-radius: 3px;
vertical-align: middle;
}
div.path a:link, div.path a:visited { color: #333; }
table.coverage {
border-collapse: collapse;
margin: 10px 0 0 0;
padding: 0;
}
table.coverage td {
margin: 0;
padding: 0;
vertical-align: top;
}
table.coverage td.line-count {
text-align: right;
padding: 0 5px 0 20px;
}
table.coverage td.line-coverage {
text-align: right;
padding-right: 10px;
min-width:20px;
}
table.coverage td span.cline-any {
display: inline-block;
padding: 0 5px;
width: 100%;
}
.missing-if-branch {
display: inline-block;
margin-right: 5px;
border-radius: 3px;
position: relative;
padding: 0 4px;
background: #333;
color: yellow;
}
.skip-if-branch {
display: none;
margin-right: 10px;
position: relative;
padding: 0 4px;
background: #ccc;
color: white;
}
.missing-if-branch .typ, .skip-if-branch .typ {
color: inherit !important;
}
.coverage-summary {
border-collapse: collapse;
width: 100%;
}
.coverage-summary tr { border-bottom: 1px solid #bbb; }
.keyline-all { border: 1px solid #ddd; }
.coverage-summary td, .coverage-summary th { padding: 10px; }
.coverage-summary tbody { border: 1px solid #bbb; }
.coverage-summary td { border-right: 1px solid #bbb; }
.coverage-summary td:last-child { border-right: none; }
.coverage-summary th {
text-align: left;
font-weight: normal;
white-space: nowrap;
}
.coverage-summary th.file { border-right: none !important; }
.coverage-summary th.pct { }
.coverage-summary th.pic,
.coverage-summary th.abs,
.coverage-summary td.pct,
.coverage-summary td.abs { text-align: right; }
.coverage-summary td.file { white-space: nowrap; }
.coverage-summary td.pic { min-width: 120px !important; }
.coverage-summary tfoot td { }
.coverage-summary .sorter {
height: 10px;
width: 7px;
display: inline-block;
margin-left: 0.5em;
background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
}
.coverage-summary .sorted .sorter {
background-position: 0 -20px;
}
.coverage-summary .sorted-desc .sorter {
background-position: 0 -10px;
}
.status-line { height: 10px; }
/* dark red */
.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
.low .chart { border:1px solid #C21F39 }
/* medium red */
.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
/* light red */
.low, .cline-no { background:#FCE1E5 }
/* light green */
.high, .cline-yes { background:rgb(230,245,208) }
/* 新增语句 */
.cline-new { color: #fc5531; }
/* medium green */
.cstat-yes { background:rgb(161,215,106) }
/* dark green */
.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
.high .chart { border:1px solid rgb(77,146,33) }
/* dark yellow (gold) */
.medium .chart { border:1px solid #f9cd0b; }
.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
/* light yellow */
.medium { background: #fff4c2; }
/* light gray */
span.cline-neutral { background: #eaeaea; }
.cbranch-no { background: yellow !important; color: #111; }
.cstat-skip { background: #ddd; color: #111; }
.fstat-skip { background: #ddd; color: #111 !important; }
.cbranch-skip { background: #ddd !important; color: #111; }
.cover-fill, .cover-empty {
display:inline-block;
height: 12px;
}
.chart {
line-height: 0;
}
.cover-empty {
background: white;
}
.cover-full {
border-right: none !important;
}
pre.prettyprint {
border: none !important;
padding: 0 !important;
margin: 0 !important;
}
.com { color: #999 !important; }
.ignore-none { color: #999; font-weight: normal; }
.wrapper {
min-height: 100%;
height: auto !important;
height: 100%;
margin: 0 auto -48px;
}
.footer, .push {
height: 48px;
}
var addSorting = (function () {
"use strict";
var cols,
currentSort = {
index: 0,
desc: false
};
// returns the summary table element
function getTable() { return document.querySelector('.coverage-summary'); }
// returns the thead element of the summary table
function getTableHeader() { return getTable().querySelector('thead tr'); }
// returns the tbody element of the summary table
function getTableBody() { return getTable().querySelector('tbody'); }
// returns the th element for nth column
function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; }
// loads all columns
function loadColumns() {
var colNodes = getTableHeader().querySelectorAll('th'),
colNode,
cols = [],
col,
i;
for (i = 0; i < colNodes.length; i += 1) {
colNode = colNodes[i];
col = {
key: colNode.getAttribute('data-col'),
sortable: !colNode.getAttribute('data-nosort'),
type: colNode.getAttribute('data-type') || 'string'
};
cols.push(col);
if (col.sortable) {
col.defaultDescSort = col.type === 'number';
colNode.innerHTML = colNode.innerHTML + '<span class="sorter"></span>';
}
}
return cols;
}
// attaches a data attribute to every tr element with an object
// of data values keyed by column name
function loadRowData(tableRow) {
var tableCols = tableRow.querySelectorAll('td'),
colNode,
col,
data = {},
i,
val;
for (i = 0; i < tableCols.length; i += 1) {
colNode = tableCols[i];
col = cols[i];
val = colNode.getAttribute('data-value');
if (col.type === 'number') {
val = Number(val);
}
data[col.key] = val;
}
return data;
}
// loads all row data
function loadData() {
var rows = getTableBody().querySelectorAll('tr'),
i;
for (i = 0; i < rows.length; i += 1) {
rows[i].data = loadRowData(rows[i]);
}
}
// sorts the table using the data for the ith column
function sortByIndex(index, desc) {
var key = cols[index].key,
sorter = function (a, b) {
a = a.data[key];
b = b.data[key];
return a < b ? -1 : a > b ? 1 : 0;
},
finalSorter = sorter,
tableBody = document.querySelector('.coverage-summary tbody'),
rowNodes = tableBody.querySelectorAll('tr'),
rows = [],
i;
if (desc) {
finalSorter = function (a, b) {
return -1 * sorter(a, b);
};
}
for (i = 0; i < rowNodes.length; i += 1) {
rows.push(rowNodes[i]);
tableBody.removeChild(rowNodes[i]);
}
rows.sort(finalSorter);
for (i = 0; i < rows.length; i += 1) {
tableBody.appendChild(rows[i]);
}
}
// removes sort indicators for current column being sorted
function removeSortIndicators() {
var col = getNthColumn(currentSort.index),
cls = col.className;
cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
col.className = cls;
}
// adds sort indicators for current column being sorted
function addSortIndicators() {
getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted';
}
// adds event listeners for all sorter widgets
function enableUI() {
var i,
el,
ithSorter = function ithSorter(i) {
var col = cols[i];
return function () {
var desc = col.defaultDescSort;
if (currentSort.index === i) {
desc = !currentSort.desc;
}
sortByIndex(i, desc);
removeSortIndicators();
currentSort.index = i;
currentSort.desc = desc;
addSortIndicators();
};
};
for (i =0 ; i < cols.length; i += 1) {
if (cols[i].sortable) {
// add the click event handler on the th so users
// dont have to click on those tiny arrows
el = getNthColumn(i).querySelector('.sorter').parentElement;
if (el.addEventListener) {
el.addEventListener('click', ithSorter(i));
} else {
el.attachEvent('onclick', ithSorter(i));
}
}
}
}
// adds sorting functionality to the UI
return function () {
if (!getTable()) {
return;
}
cols = loadColumns();
loadData(cols);
addSortIndicators();
enableUI();
};
})();
window.addEventListener('load', addSorting);
.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
#!/usr/bin/env node
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var async = require('async'),
Command = require('./command'),
inputError = require('./util/input-error'),
exitProcess = process.exit; //hold a reference to original process.exit so that we are not affected even when a test changes it
require('./register-plugins');
function findCommandPosition(args) {
var i;
for (i = 0; i < args.length; i += 1) {
if (args[i].charAt(0) !== '-') {
return i;
}
}
return -1;
}
function exit(ex, code) {
// flush output for Node.js Windows pipe bug
// https://github.com/joyent/node/issues/6247 is just one bug example
// https://github.com/visionmedia/mocha/issues/333 has a good discussion
var streams = [process.stdout, process.stderr];
async.forEach(streams, function (stream, done) {
// submit a write request and wait until it's written
stream.write('', done);
}, function () {
if (ex) {
if (typeof ex === 'string') {
console.error(ex);
exitProcess(1);
} else {
throw ex; // turn it into an uncaught exception
}
} else {
exitProcess(code);
}
});
}
function errHandler (ex) {
if (!ex) { return; }
if (!ex.inputError) {
// exit with exception stack trace
exit(ex);
} else {
//don't print nasty traces but still exit(1)
console.error(ex.message);
console.error('Try "istanbul help" for usage');
exit(null, 1);
}
}
function runCommand(args, callback) {
var pos = findCommandPosition(args),
command,
commandArgs,
commandObject;
if (pos < 0) {
return callback(inputError.create('Need a command to run'));
}
commandArgs = args.slice(0, pos);
command = args[pos];
commandArgs.push.apply(commandArgs, args.slice(pos + 1));
try {
commandObject = Command.create(command);
} catch (ex) {
errHandler(inputError.create(ex.message));
return;
}
commandObject.run(commandArgs, errHandler);
}
function runToCompletion(args) {
runCommand(args, errHandler);
}
/* istanbul ignore if: untestable */
if (require.main === module) {
var args = Array.prototype.slice.call(process.argv, 2);
runToCompletion(args);
}
module.exports = {
runToCompletion: runToCompletion
};
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
"use strict";
var MemoryStore = require('./store/memory'),
utils = require('./object-utils');
/**
* a mechanism to merge multiple coverage objects into one. Handles the use case
* of overlapping coverage information for the same files in multiple coverage
* objects and does not double-count in this situation. For example, if
* you pass the same coverage object multiple times, the final merged object will be
* no different that any of the objects passed in (except for execution counts).
*
* The `Collector` is built for scale to handle thousands of coverage objects.
* By default, all processing is done in memory since the common use-case is of
* one or a few coverage objects. You can work around memory
* issues by passing in a `Store` implementation that stores temporary computations
* on disk (the `tmp` store, for example).
*
* The `getFinalCoverage` method returns an object with merged coverage information
* and is provided as a convenience for implementors working with coverage information
* that can fit into memory. Reporters, in the interest of generality, should *not* use this method for
* creating reports.
*
* Usage
* -----
*
* var collector = new require('istanbul').Collector();
*
* files.forEach(function (f) {
* //each coverage object can have overlapping information about multiple files
* collector.add(JSON.parse(fs.readFileSync(f, 'utf8')));
* });
*
* collector.files().forEach(function(file) {
* var fileCoverage = collector.fileCoverageFor(file);
* console.log('Coverage for ' + file + ' is:' + JSON.stringify(fileCoverage));
* });
*
* // convenience method: do not use this when dealing with a large number of files
* var finalCoverage = collector.getFinalCoverage();
*
* @class Collector
* @module main
* @constructor
* @param {Object} options Optional. Configuration options.
* @param {Store} options.store - an implementation of `Store` to use for temporary
* calculations.
*/
function Collector(options) {
options = options || {};
this.store = options.store || new MemoryStore();
}
Collector.prototype = {
/**
* adds a coverage object to the collector.
*
* @method add
* @param {Object} coverage the coverage object.
* @param {String} testName Optional. The name of the test used to produce the object.
* This is currently not used.
*/
add: function (coverage /*, testName */) {
var store = this.store;
Object.keys(coverage).forEach(function (key) {
var fileCoverage = coverage[key];
if (store.hasKey(key)) {
store.setObject(key, utils.mergeFileCoverage(fileCoverage, store.getObject(key)));
} else {
store.setObject(key, fileCoverage);
}
});
},
/**
* returns a list of unique file paths for which coverage information has been added.
* @method files
* @return {Array} an array of file paths for which coverage information is present.
*/
files: function () {
return this.store.keys();
},
/**
* return file coverage information for a single file
* @method fileCoverageFor
* @param {String} fileName the path for the file for which coverage information is
* required. Must be one of the values returned in the `files()` method.
* @return {Object} the coverage information for the specified file.
*/
fileCoverageFor: function (fileName) {
var ret = this.store.getObject(fileName);
utils.addDerivedInfoForFile(ret);
return ret;
},
/**
* returns file coverage information for all files. This has the same format as
* any of the objects passed in to the `add` method. The number of keys in this
* object will be a superset of all keys found in the objects passed to `add()`
* @method getFinalCoverage
* @return {Object} the merged coverage information
*/
getFinalCoverage: function () {
var ret = {},
that = this;
this.files().forEach(function (file) {
ret[file] = that.fileCoverageFor(file);
});
return ret;
},
/**
* disposes this collector and reclaims temporary resources used in the
* computation. Calls `dispose()` on the underlying store.
* @method dispose
*/
dispose: function () {
this.store.dispose();
}
};
module.exports = Collector;
\ No newline at end of file
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var nopt = require('nopt'),
path = require('path'),
fs = require('fs'),
Collector = require('../collector'),
formatOption = require('../util/help-formatter').formatOption,
util = require('util'),
utils = require('../object-utils'),
filesFor = require('../util/file-matcher').filesFor,
Command = require('./index'),
configuration = require('../config');
function isAbsolute(file) {
if (path.isAbsolute) {
return path.isAbsolute(file);
}
return path.resolve(file) === path.normalize(file);
}
function CheckCoverageCommand() {
Command.call(this);
}
function removeFiles(covObj, root, files) {
var filesObj = {},
obj = {};
// Create lookup table.
files.forEach(function (file) {
filesObj[file] = true;
});
Object.keys(covObj).forEach(function (key) {
// Exclude keys will always be relative, but covObj keys can be absolute or relative
var excludeKey = isAbsolute(key) ? path.relative(root, key) : key;
// Also normalize for files that start with `./`, etc.
excludeKey = path.normalize(excludeKey);
if (filesObj[excludeKey] !== true) {
obj[key] = covObj[key];
}
});
return obj;
}
CheckCoverageCommand.TYPE = 'check-coverage';
util.inherits(CheckCoverageCommand, Command);
Command.mix(CheckCoverageCommand, {
synopsis: function () {
return "checks overall/per-file coverage against thresholds from coverage JSON files. Exits 1 if thresholds are not met, 0 otherwise";
},
usage: function () {
console.error('\nUsage: ' + this.toolName() + ' ' + this.type() + ' <options> [<include-pattern>]\n\nOptions are:\n\n' +
[
formatOption('--statements <threshold>', 'global statement coverage threshold'),
formatOption('--functions <threshold>', 'global function coverage threshold'),
formatOption('--branches <threshold>', 'global branch coverage threshold'),
formatOption('--lines <threshold>', 'global line coverage threshold')
].join('\n\n') + '\n');
console.error('\n\n');
console.error('Thresholds, when specified as a positive number are taken to be the minimum percentage required.');
console.error('When a threshold is specified as a negative number it represents the maximum number of uncovered entities allowed.\n');
console.error('For example, --statements 90 implies minimum statement coverage is 90%.');
console.error(' --statements -10 implies that no more than 10 uncovered statements are allowed\n');
console.error('Per-file thresholds can be specified via a configuration file.\n');
console.error('<include-pattern> is a glob pattern that can be used to select one or more coverage files ' +
'for merge. This defaults to "**/coverage*.json"');
console.error('\n');
},
run: function (args, callback) {
var template = {
config: path,
root: path,
statements: Number,
lines: Number,
branches: Number,
functions: Number,
verbose: Boolean
},
opts = nopt(template, { v : '--verbose' }, args, 0),
// Translate to config opts.
config = configuration.loadFile(opts.config, {
verbose: opts.verbose,
check: {
global: {
statements: opts.statements,
lines: opts.lines,
branches: opts.branches,
functions: opts.functions
}
}
}),
includePattern = '**/coverage*.json',
root,
collector = new Collector(),
errors = [];
if (opts.argv.remain.length > 0) {
includePattern = opts.argv.remain[0];
}
root = opts.root || process.cwd();
filesFor({
root: root,
includes: [ includePattern ]
}, function (err, files) {
if (err) { throw err; }
if (files.length === 0) {
return callback('ERROR: No coverage files found.');
}
files.forEach(function (file) {
var coverageObject = JSON.parse(fs.readFileSync(file, 'utf8'));
collector.add(coverageObject);
});
var thresholds = {
global: {
statements: config.check.global.statements || 0,
branches: config.check.global.branches || 0,
lines: config.check.global.lines || 0,
functions: config.check.global.functions || 0,
excludes: config.check.global.excludes || []
},
each: {
statements: config.check.each.statements || 0,
branches: config.check.each.branches || 0,
lines: config.check.each.lines || 0,
functions: config.check.each.functions || 0,
excludes: config.check.each.excludes || []
}
},
rawCoverage = collector.getFinalCoverage(),
globalResults = utils.summarizeCoverage(removeFiles(rawCoverage, root, thresholds.global.excludes)),
eachResults = removeFiles(rawCoverage, root, thresholds.each.excludes);
// Summarize per-file results and mutate original results.
Object.keys(eachResults).forEach(function (key) {
eachResults[key] = utils.summarizeFileCoverage(eachResults[key]);
});
if (config.verbose) {
console.log('Compare actuals against thresholds');
console.log(JSON.stringify({ global: globalResults, each: eachResults, thresholds: thresholds }, undefined, 4));
}
function check(name, thresholds, actuals) {
[
"statements",
"branches",
"lines",
"functions"
].forEach(function (key) {
var actual = actuals[key].pct,
actualUncovered = actuals[key].total - actuals[key].covered,
threshold = thresholds[key];
if (threshold < 0) {
if (threshold * -1 < actualUncovered) {
errors.push('ERROR: Uncovered count for ' + key + ' (' + actualUncovered +
') exceeds ' + name + ' threshold (' + -1 * threshold + ')');
}
} else {
if (actual < threshold) {
errors.push('ERROR: Coverage for ' + key + ' (' + actual +
'%) does not meet ' + name + ' threshold (' + threshold + '%)');
}
}
});
}
check("global", thresholds.global, globalResults);
Object.keys(eachResults).forEach(function (key) {
check("per-file" + " (" + key + ") ", thresholds.each, eachResults[key]);
});
return callback(errors.length === 0 ? null : errors.join("\n"));
});
}
});
module.exports = CheckCoverageCommand;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var runWithCover = require('./common/run-with-cover'),
util = require('util'),
Command = require('./index');
function CoverCommand() {
Command.call(this);
}
CoverCommand.TYPE = 'cover';
util.inherits(CoverCommand, Command);
Command.mix(CoverCommand, {
synopsis: function () {
return "transparently adds coverage information to a node command. Saves coverage.json and reports at the end of execution";
},
usage: function () {
runWithCover.usage(this.toolName(), this.type());
},
run: function (args, callback) {
runWithCover.run(args, this.type(), true, callback);
}
});
module.exports = CoverCommand;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var Command = require('./index.js'),
util = require('util'),
formatOption = require('../util/help-formatter').formatOption,
VERSION = require('../../index').VERSION,
configuration = require('../config'),
yaml = require('js-yaml'),
formatPara = require('../util/help-formatter').formatPara;
function showConfigHelp(toolName) {
console.error('\nConfiguring ' + toolName);
console.error('====================');
console.error('\n' +
formatPara(toolName + ' can be configured globally using a .istanbul.yml YAML file ' +
'at the root of your source tree. Every command also accepts a --config=<config-file> argument to ' +
'customize its location per command. The alternate config file can be in YAML, JSON or node.js ' +
'(exporting the config object).'));
console.error('\n' +
formatPara('The config file currently has four sections for instrumentation, reporting, hooks, ' +
'and checking. Note that certain commands (like `cover`) use information from multiple sections.'));
console.error('\n' +
formatPara('Keys in the config file usually correspond to command line parameters with the same name. ' +
'The verbose option for every command shows you the exact configuration used. See the api ' +
'docs for an explanation of each key.'));
console.error('\n' +
formatPara('You only need to specify the keys that you want to override. Your overrides will be merged ' +
'with the default config.'));
console.error('\nThe default configuration is as follows:\n');
console.error(yaml.safeDump(configuration.defaultConfig(), { indent: 4, flowLevel: 3 }));
console.error('\n' +
formatPara('The `watermarks` section does not have a command line equivalent. It allows you to set up ' +
'low and high watermark percentages for reporting. These are honored by all reporters that colorize ' +
'their output based on low/ medium/ high coverage.'));
console.error('\n' +
formatPara('The `reportConfig` section allows you to configure each report format independently ' +
'and has no command-line equivalent either.'));
console.error('\n' +
formatPara('The `check` section configures minimum threshold enforcement for coverage results. ' +
'`global` applies to all files together and `each` on a per-file basis. A list of files can ' +
'be excluded from enforcement relative to root via the `exclude` property.'));
console.error('');
}
function HelpCommand() {
Command.call(this);
}
HelpCommand.TYPE = 'help';
util.inherits(HelpCommand, Command);
Command.mix(HelpCommand, {
synopsis: function () {
return "shows help";
},
usage: function () {
console.error('\nUsage: ' + this.toolName() + ' ' + this.type() + ' config | <command>\n');
console.error('`config` provides help with istanbul configuration\n');
console.error('Available commands are:\n');
var commandObj;
Command.getCommandList().forEach(function (cmd) {
commandObj = Command.create(cmd);
console.error(formatOption(cmd, commandObj.synopsis()));
console.error("\n");
});
console.error("Command names can be abbreviated as long as the abbreviation is unambiguous");
console.error(this.toolName() + ' version:' + VERSION);
console.error("\n");
},
run: function (args, callback) {
var command;
if (args.length === 0) {
this.usage();
} else {
if (args[0] === 'config') {
showConfigHelp(this.toolName());
} else {
try {
command = Command.create(args[0]);
command.usage('istanbul', Command.resolveCommandName(args[0]));
} catch (ex) {
console.error('Invalid command: ' + args[0]);
this.usage();
}
}
}
return callback();
}
});
module.exports = HelpCommand;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var Factory = require('../util/factory'),
factory = new Factory('command', __dirname, true);
function Command() {}
// add register, create, mix, loadAll, getCommandList, resolveCommandName to the Command object
factory.bindClassMethods(Command);
Command.prototype = {
toolName: function () {
return require('../util/meta').NAME;
},
type: function () {
return this.constructor.TYPE;
},
synopsis: /* istanbul ignore next: base method */ function () {
return "the developer has not written a one-line summary of the " + this.type() + " command";
},
usage: /* istanbul ignore next: base method */ function () {
console.error("the developer has not provided a usage for the " + this.type() + " command");
},
run: /* istanbul ignore next: abstract method */ function (args, callback) {
return callback(new Error("run: must be overridden for the " + this.type() + " command"));
}
};
module.exports = Command;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var nopt = require('nopt'),
Report = require('../report'),
Reporter = require('../reporter'),
path = require('path'),
fs = require('fs'),
Collector = require('../collector'),
helpFormatter = require('../util/help-formatter'),
formatOption = helpFormatter.formatOption,
formatPara = helpFormatter.formatPara,
filesFor = require('../util/file-matcher').filesFor,
util = require('util'),
Command = require('./index'),
configuration = require('../config');
function ReportCommand() {
Command.call(this);
}
ReportCommand.TYPE = 'report';
util.inherits(ReportCommand, Command);
function printDeprecationMessage(pat, fmt) {
console.error('**********************************************************************');
console.error('DEPRECATION WARNING! You are probably using the old format of the report command');
console.error('This will stop working soon, see `istanbul help report` for the new command format');
console.error('Assuming you meant: istanbul report --include=' + pat + ' ' + fmt);
console.error('**********************************************************************');
}
Command.mix(ReportCommand, {
synopsis: function () {
return "writes reports for coverage JSON objects produced in a previous run";
},
usage: function () {
console.error('\nUsage: ' + this.toolName() + ' ' + this.type() + ' <options> [ <format> ... ]\n\nOptions are:\n\n' +
[
formatOption('--config <path-to-config>', 'the configuration file to use, defaults to .istanbul.yml'),
formatOption('--root <input-directory>', 'The input root directory for finding coverage files'),
formatOption('--dir <report-directory>', 'The output directory where files will be written. This defaults to ./coverage/'),
formatOption('--include <glob>', 'The glob pattern to select one or more coverage files, defaults to **/coverage*.json'),
formatOption('--verbose, -v', 'verbose mode')
].join('\n\n'));
console.error('\n');
console.error('<format> is one of ');
Report.getReportList().forEach(function (name) {
console.error(formatOption(name, Report.create(name).synopsis()));
});
console.error("");
console.error(formatPara([
'Default format is lcov unless otherwise specified in the config file.',
'In addition you can tweak the file names for various reports using the config file.',
'Type `istanbul help config` to see what can be tweaked.'
].join(' ')));
console.error('\n');
},
run: function (args, callback) {
var template = {
config: path,
root: path,
dir: path,
include: String,
verbose: Boolean
},
opts = nopt(template, { v : '--verbose' }, args, 0),
includePattern = opts.include || '**/coverage*.json',
root,
collector = new Collector(),
config = configuration.loadFile(opts.config, {
verbose: opts.verbose,
reporting: {
dir: opts.dir
}
}),
formats = opts.argv.remain,
reporter = new Reporter(config);
// Start: backward compatible processing
if (formats.length === 2 &&
Report.getReportList().indexOf(formats[1]) < 0) {
includePattern = formats[1];
formats = [ formats[0] ];
printDeprecationMessage(includePattern, formats[0]);
}
// End: backward compatible processing
if (formats.length === 0) {
formats = config.reporting.reports();
}
if (formats.length === 0) {
formats = [ 'lcov' ];
}
reporter.addAll(formats);
root = opts.root || process.cwd();
filesFor({
root: root,
includes: [ includePattern ]
}, function (err, files) {
if (err) { throw err; }
files.forEach(function (file) {
var coverageObject = JSON.parse(fs.readFileSync(file, 'utf8'));
collector.add(coverageObject);
});
reporter.write(collector, false, function (err) {
console.log('Done');
return callback(err);
});
});
}
});
module.exports = ReportCommand;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var runWithCover = require('./common/run-with-cover'),
util = require('util'),
Command = require('./index');
function TestCommand() {
Command.call(this);
}
TestCommand.TYPE = 'test';
util.inherits(TestCommand, Command);
Command.mix(TestCommand, {
synopsis: function () {
return "cover a node command only when npm_config_coverage is set. Use in an `npm test` script for conditional coverage";
},
usage: function () {
runWithCover.usage(this.toolName(), this.type());
},
run: function (args, callback) {
runWithCover.run(args, this.type(), !!process.env.npm_config_coverage, callback);
}
});
module.exports = TestCommand;
This diff is collapsed. Click to expand it.
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
/**
* provides a mechanism to transform code in the scope of `require` or `vm.createScript`.
* This mechanism is general and relies on a user-supplied `matcher` function that determines when transformations should be
* performed and a user-supplied `transformer` function that performs the actual transform.
* Instrumenting code for coverage is one specific example of useful hooking.
*
* Note that both the `matcher` and `transformer` must execute synchronously.
*
* For the common case of matching filesystem paths based on inclusion/ exclusion patterns, use the `matcherFor`
* function in the istanbul API to get a matcher.
*
* It is up to the transformer to perform processing with side-effects, such as caching, storing the original
* source code to disk in case of dynamically generated scripts etc. The `Store` class can help you with this.
*
* Usage
* -----
*
* var hook = require('istanbul').hook,
* myMatcher = function (file) { return file.match(/foo/); },
* myTransformer = function (code, file) { return 'console.log("' + file + '");' + code; };
*
* hook.hookRequire(myMatcher, myTransformer);
*
* var foo = require('foo'); //will now print foo's module path to console
*
* @class Hook
* @module main
*/
var path = require('path'),
fs = require('fs'),
Module = require('module'),
vm = require('vm'),
originalLoaders = {},
originalCreateScript = vm.createScript,
originalRunInThisContext = vm.runInThisContext;
function transformFn(matcher, transformer, verbose) {
return function (code, filename) {
var shouldHook = typeof filename === 'string' && matcher(path.resolve(filename)),
transformed,
changed = false;
if (shouldHook) {
if (verbose) {
console.error('Module load hook: transform [' + filename + ']');
}
try {
transformed = transformer(code, filename);
changed = true;
} catch (ex) {
console.error('Transformation error; return original code');
console.error(ex);
transformed = code;
}
} else {
transformed = code;
}
return { code: transformed, changed: changed };
};
}
function unloadRequireCache(matcher) {
if (matcher && typeof require !== 'undefined' && require && require.cache) {
Object.keys(require.cache).forEach(function (filename) {
if (matcher(filename)) {
delete require.cache[filename];
}
});
}
}
/**
* hooks `require` to return transformed code to the node module loader.
* Exceptions in the transform result in the original code being used instead.
* @method hookRequire
* @static
* @param matcher {Function(filePath)} a function that is called with the absolute path to the file being
* `require`-d. Should return a truthy value when transformations need to be applied to the code, a falsy value otherwise
* @param transformer {Function(code, filePath)} a function called with the original code and the associated path of the file
* from where the code was loaded. Should return the transformed code.
* @param options {Object} options Optional.
* @param {Boolean} [options.verbose] write a line to standard error every time the transformer is called
* @param {Function} [options.postLoadHook] a function that is called with the name of the file being
* required. This is called after the require is processed irrespective of whether it was transformed.
*/
function hookRequire(matcher, transformer, options) {
options = options || {};
var extensions,
fn = transformFn(matcher, transformer, options.verbose),
postLoadHook = options.postLoadHook &&
typeof options.postLoadHook === 'function' ? options.postLoadHook : null;
extensions = options.extensions || ['.js'];
extensions.forEach(function(ext){
if (!(ext in originalLoaders)) {
originalLoaders[ext] = Module._extensions[ext] || Module._extensions['.js'];
}
Module._extensions[ext] = function (module, filename) {
var ret = fn(fs.readFileSync(filename, 'utf8'), filename);
if (ret.changed) {
module._compile(ret.code, filename);
} else {
originalLoaders[ext](module, filename);
}
if (postLoadHook) {
postLoadHook(filename);
}
};
});
}
/**
* unhook `require` to restore it to its original state.
* @method unhookRequire
* @static
*/
function unhookRequire() {
Object.keys(originalLoaders).forEach(function(ext) {
Module._extensions[ext] = originalLoaders[ext];
});
}
/**
* hooks `vm.createScript` to return transformed code out of which a `Script` object will be created.
* Exceptions in the transform result in the original code being used instead.
* @method hookCreateScript
* @static
* @param matcher {Function(filePath)} a function that is called with the filename passed to `vm.createScript`
* Should return a truthy value when transformations need to be applied to the code, a falsy value otherwise
* @param transformer {Function(code, filePath)} a function called with the original code and the filename passed to
* `vm.createScript`. Should return the transformed code.
* @param options {Object} options Optional.
* @param {Boolean} [options.verbose] write a line to standard error every time the transformer is called
*/
function hookCreateScript(matcher, transformer, opts) {
opts = opts || {};
var fn = transformFn(matcher, transformer, opts.verbose);
vm.createScript = function (code, file) {
var ret = fn(code, file);
return originalCreateScript(ret.code, file);
};
}
/**
* unhooks vm.createScript, restoring it to its original state.
* @method unhookCreateScript
* @static
*/
function unhookCreateScript() {
vm.createScript = originalCreateScript;
}
/**
* hooks `vm.runInThisContext` to return transformed code.
* @method hookRunInThisContext
* @static
* @param matcher {Function(filePath)} a function that is called with the filename passed to `vm.createScript`
* Should return a truthy value when transformations need to be applied to the code, a falsy value otherwise
* @param transformer {Function(code, filePath)} a function called with the original code and the filename passed to
* `vm.createScript`. Should return the transformed code.
* @param options {Object} options Optional.
* @param {Boolean} [options.verbose] write a line to standard error every time the transformer is called
*/
function hookRunInThisContext(matcher, transformer, opts) {
opts = opts || {};
var fn = transformFn(matcher, transformer, opts.verbose);
vm.runInThisContext = function (code, file) {
var ret = fn(code, file);
return originalRunInThisContext(ret.code, file);
};
}
/**
* unhooks vm.runInThisContext, restoring it to its original state.
* @method unhookRunInThisContext
* @static
*/
function unhookRunInThisContext() {
vm.runInThisContext = originalRunInThisContext;
}
module.exports = {
hookRequire: hookRequire,
unhookRequire: unhookRequire,
hookCreateScript: hookCreateScript,
unhookCreateScript: unhookCreateScript,
hookRunInThisContext : hookRunInThisContext,
unhookRunInThisContext : unhookRunInThisContext,
unloadRequireCache: unloadRequireCache
};
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var Store = require('./store'),
Report = require('./report'),
Command = require('./command');
Store.loadAll();
Report.loadAll();
Command.loadAll();
var path = require('path'),
util = require('util'),
Report = require('./index'),
FileWriter = require('../util/file-writer'),
TreeSummarizer = require('../util/tree-summarizer'),
utils = require('../object-utils');
/**
* a `Report` implementation that produces a clover-style XML file.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('clover');
*
* @class CloverReport
* @module report
* @extends Report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to the clover.xml will be written
* @param {String} [opts.file] the file name, defaulted to config attribute or 'clover.xml'
*/
function CloverReport(opts) {
Report.call(this);
opts = opts || {};
this.projectRoot = process.cwd();
this.dir = opts.dir || this.projectRoot;
this.file = opts.file || this.getDefaultConfig().file;
this.opts = opts;
}
CloverReport.TYPE = 'clover';
util.inherits(CloverReport, Report);
function asJavaPackage(node) {
return node.displayShortName().
replace(/\//g, '.').
replace(/\\/g, '.').
replace(/\.$/, '');
}
function asClassName(node) {
/*jslint regexp: true */
return node.fullPath().replace(/.*[\\\/]/, '');
}
function quote(thing) {
return '"' + thing + '"';
}
function attr(n, v) {
return ' ' + n + '=' + quote(v) + ' ';
}
function branchCoverageByLine(fileCoverage) {
var branchMap = fileCoverage.branchMap,
branches = fileCoverage.b,
ret = {};
Object.keys(branchMap).forEach(function (k) {
var line = branchMap[k].line,
branchData = branches[k];
ret[line] = ret[line] || [];
ret[line].push.apply(ret[line], branchData);
});
Object.keys(ret).forEach(function (k) {
var dataArray = ret[k],
covered = dataArray.filter(function (item) { return item > 0; }),
coverage = covered.length / dataArray.length * 100;
ret[k] = { covered: covered.length, total: dataArray.length, coverage: coverage };
});
return ret;
}
function addClassStats(node, fileCoverage, writer) {
fileCoverage = utils.incrementIgnoredTotals(fileCoverage);
var metrics = node.metrics,
branchByLine = branchCoverageByLine(fileCoverage),
fnMap,
lines;
writer.println('\t\t\t<file' +
attr('name', asClassName(node)) +
attr('path', node.fullPath()) +
'>');
writer.println('\t\t\t\t<metrics' +
attr('statements', metrics.lines.total) +
attr('coveredstatements', metrics.lines.covered) +
attr('conditionals', metrics.branches.total) +
attr('coveredconditionals', metrics.branches.covered) +
attr('methods', metrics.functions.total) +
attr('coveredmethods', metrics.functions.covered) +
'/>');
fnMap = fileCoverage.fnMap;
lines = fileCoverage.l;
Object.keys(lines).forEach(function (k) {
var str = '\t\t\t\t<line' +
attr('num', k) +
attr('count', lines[k]),
branchDetail = branchByLine[k];
if (!branchDetail) {
str += ' type="stmt" ';
} else {
str += ' type="cond" ' +
attr('truecount', branchDetail.covered) +
attr('falsecount', (branchDetail.total - branchDetail.covered));
}
writer.println(str + '/>');
});
writer.println('\t\t\t</file>');
}
function walk(node, collector, writer, level, projectRoot) {
var metrics,
totalFiles = 0,
totalPackages = 0,
totalLines = 0,
tempLines = 0;
if (level === 0) {
metrics = node.metrics;
writer.println('<?xml version="1.0" encoding="UTF-8"?>');
writer.println('<coverage' +
attr('generated', Date.now()) +
'clover="3.2.0">');
writer.println('\t<project' +
attr('timestamp', Date.now()) +
attr('name', 'All Files') +
'>');
node.children.filter(function (child) { return child.kind === 'dir'; }).
forEach(function (child) {
totalPackages += 1;
child.children.filter(function (child) { return child.kind !== 'dir'; }).
forEach(function (child) {
Object.keys(collector.fileCoverageFor(child.fullPath()).l).forEach(function (k){
tempLines = k;
});
totalLines += Number(tempLines);
totalFiles += 1;
});
});
console.log(metrics.lines, 'metrics.lines 数据')
writer.println('\t\t<metrics' +
attr('statements', metrics.lines.total) +
attr('coveredstatements', metrics.lines.covered) +
attr('conditionals', metrics.branches.total) +
attr('coveredconditionals', metrics.branches.covered) +
attr('methods', metrics.functions.total) +
attr('coveredmethods', metrics.functions.covered) +
attr('elements', metrics.lines.total + metrics.branches.total + metrics.functions.total) +
attr('coveredelements', metrics.lines.covered + metrics.branches.covered + metrics.functions.covered) +
attr('complexity', 0) +
attr('packages', totalPackages) +
attr('files', totalFiles) +
attr('classes', totalFiles) +
attr('loc', totalLines) +
attr('ncloc', totalLines) +
'/>');
}
if (node.packageMetrics) {
metrics = node.packageMetrics;
writer.println('\t\t<package' +
attr('name', asJavaPackage(node)) +
'>');
writer.println('\t\t\t<metrics' +
attr('statements', metrics.lines.total) +
attr('coveredstatements', metrics.lines.covered) +
attr('conditionals', metrics.branches.total) +
attr('coveredconditionals', metrics.branches.covered) +
attr('methods', metrics.functions.total) +
attr('coveredmethods', metrics.functions.covered) +
'/>');
node.children.filter(function (child) { return child.kind !== 'dir'; }).
forEach(function (child) {
addClassStats(child, collector.fileCoverageFor(child.fullPath()), writer);
});
writer.println('\t\t</package>');
}
node.children.filter(function (child) { return child.kind === 'dir'; }).
forEach(function (child) {
walk(child, collector, writer, level + 1, projectRoot);
});
if (level === 0) {
writer.println('\t</project>');
writer.println('</coverage>');
}
}
Report.mix(CloverReport, {
synopsis: function () {
return 'XML coverage report that can be consumed by the clover tool';
},
getDefaultConfig: function () {
return { file: 'clover.xml' };
},
writeReport: function (collector, sync) {
var summarizer = new TreeSummarizer(),
outputFile = path.join(this.dir, this.file),
writer = this.opts.writer || new FileWriter(sync),
projectRoot = this.projectRoot,
that = this,
tree,
root;
collector.files().forEach(function (key) {
summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key)));
});
tree = summarizer.getTreeSummary();
root = tree.root;
writer.on('done', function () { that.emit('done'); });
writer.writeFile(outputFile, function (contentWriter) {
walk(root, collector, contentWriter, 0, projectRoot);
writer.done();
});
}
});
module.exports = CloverReport;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
util = require('util'),
Report = require('./index'),
FileWriter = require('../util/file-writer'),
TreeSummarizer = require('../util/tree-summarizer'),
utils = require('../object-utils');
/**
* a `Report` implementation that produces a cobertura-style XML file that conforms to the
* http://cobertura.sourceforge.net/xml/coverage-04.dtd DTD.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('cobertura');
*
* @class CoberturaReport
* @module report
* @extends Report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to the cobertura-coverage.xml will be written
*/
function CoberturaReport(opts) {
Report.call(this);
opts = opts || {};
this.projectRoot = process.cwd();
this.dir = opts.dir || this.projectRoot;
this.file = opts.file || this.getDefaultConfig().file;
this.opts = opts;
}
CoberturaReport.TYPE = 'cobertura';
util.inherits(CoberturaReport, Report);
function asJavaPackage(node) {
return node.displayShortName().
replace(/\//g, '.').
replace(/\\/g, '.').
replace(/\.$/, '');
}
function asClassName(node) {
/*jslint regexp: true */
return node.fullPath().replace(/.*[\\\/]/, '');
}
function quote(thing) {
return '"' + thing + '"';
}
function attr(n, v) {
return ' ' + n + '=' + quote(v) + ' ';
}
function branchCoverageByLine(fileCoverage) {
var branchMap = fileCoverage.branchMap,
branches = fileCoverage.b,
ret = {};
Object.keys(branchMap).forEach(function (k) {
var line = branchMap[k].line,
branchData = branches[k];
ret[line] = ret[line] || [];
ret[line].push.apply(ret[line], branchData);
});
Object.keys(ret).forEach(function (k) {
var dataArray = ret[k],
covered = dataArray.filter(function (item) { return item > 0; }),
coverage = covered.length / dataArray.length * 100;
ret[k] = { covered: covered.length, total: dataArray.length, coverage: coverage };
});
return ret;
}
function addClassStats(node, fileCoverage, writer, projectRoot) {
fileCoverage = utils.incrementIgnoredTotals(fileCoverage);
var metrics = node.metrics,
branchByLine = branchCoverageByLine(fileCoverage),
fnMap,
lines;
writer.println('\t\t<class' +
attr('name', asClassName(node)) +
attr('filename', path.relative(projectRoot, node.fullPath())) +
attr('line-rate', metrics.lines.pct / 100.0) +
attr('branch-rate', metrics.branches.pct / 100.0) +
'>');
writer.println('\t\t<methods>');
fnMap = fileCoverage.fnMap;
Object.keys(fnMap).forEach(function (k) {
var name = fnMap[k].name,
hits = fileCoverage.f[k];
writer.println(
'\t\t\t<method' +
attr('name', name) +
attr('hits', hits) +
attr('signature', '()V') + //fake out a no-args void return
'>'
);
//Add the function definition line and hits so that jenkins cobertura plugin records method hits
writer.println(
'\t\t\t\t<lines>' +
'<line' +
attr('number', fnMap[k].line) +
attr('hits', fileCoverage.f[k]) +
'/>' +
'</lines>'
);
writer.println('\t\t\t</method>');
});
writer.println('\t\t</methods>');
writer.println('\t\t<lines>');
lines = fileCoverage.l;
Object.keys(lines).forEach(function (k) {
var str = '\t\t\t<line' +
attr('number', k) +
attr('hits', lines[k]),
branchDetail = branchByLine[k];
if (!branchDetail) {
str += attr('branch', false);
} else {
str += attr('branch', true) +
attr('condition-coverage', branchDetail.coverage +
'% (' + branchDetail.covered + '/' + branchDetail.total + ')');
}
writer.println(str + '/>');
});
writer.println('\t\t</lines>');
writer.println('\t\t</class>');
}
function walk(node, collector, writer, level, projectRoot) {
var metrics;
if (level === 0) {
metrics = node.metrics;
writer.println('<?xml version="1.0" ?>');
writer.println('<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">');
writer.println('<coverage' +
attr('lines-valid', metrics.lines.total) +
attr('lines-covered', metrics.lines.covered) +
attr('line-rate', metrics.lines.pct / 100.0) +
attr('branches-valid', metrics.branches.total) +
attr('branches-covered', metrics.branches.covered) +
attr('branch-rate', metrics.branches.pct / 100.0) +
attr('timestamp', Date.now()) +
'complexity="0" version="0.1">');
writer.println('<sources>');
writer.println('\t<source>' + projectRoot + '</source>');
writer.println('</sources>');
writer.println('<packages>');
}
if (node.packageMetrics) {
metrics = node.packageMetrics;
writer.println('\t<package' +
attr('name', asJavaPackage(node)) +
attr('line-rate', metrics.lines.pct / 100.0) +
attr('branch-rate', metrics.branches.pct / 100.0) +
'>');
writer.println('\t<classes>');
node.children.filter(function (child) { return child.kind !== 'dir'; }).
forEach(function (child) {
addClassStats(child, collector.fileCoverageFor(child.fullPath()), writer, projectRoot);
});
writer.println('\t</classes>');
writer.println('\t</package>');
}
node.children.filter(function (child) { return child.kind === 'dir'; }).
forEach(function (child) {
walk(child, collector, writer, level + 1, projectRoot);
});
if (level === 0) {
writer.println('</packages>');
writer.println('</coverage>');
}
}
Report.mix(CoberturaReport, {
synopsis: function () {
return 'XML coverage report that can be consumed by the cobertura tool';
},
getDefaultConfig: function () {
return { file: 'cobertura-coverage.xml' };
},
writeReport: function (collector, sync) {
var summarizer = new TreeSummarizer(),
outputFile = path.join(this.dir, this.file),
writer = this.opts.writer || new FileWriter(sync),
projectRoot = this.projectRoot,
that = this,
tree,
root;
collector.files().forEach(function (key) {
summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key)));
});
tree = summarizer.getTreeSummary();
root = tree.root;
writer.on('done', function () { that.emit('done'); });
writer.writeFile(outputFile, function (contentWriter) {
walk(root, collector, contentWriter, 0, projectRoot);
writer.done();
});
}
});
module.exports = CoberturaReport;
/*
Copyright (c) 2013, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var Report = require('../index');
var supportsColor = require('supports-color');
module.exports = {
watermarks: function () {
return {
statements: [ 50, 80 ],
lines: [ 50, 80 ],
functions: [ 50, 80],
branches: [ 50, 80 ]
};
},
classFor: function (type, metrics, watermarks) {
var mark = watermarks[type],
value = metrics[type].pct;
return value >= mark[1] ? 'high' : value >= mark[0] ? 'medium' : 'low';
},
colorize: function (str, clazz) {
/* istanbul ignore if: untestable in batch mode */
var colors = {
low: '31;1',
medium: '33;1',
high: '32;1'
};
if (supportsColor && colors[clazz]) {
return '\u001b[' + colors[clazz] + 'm' + str + '\u001b[0m';
}
return str;
},
defaultReportConfig: function () {
var cfg = {};
Report.getReportList().forEach(function (type) {
var rpt = Report.create(type),
c = rpt.getDefaultConfig();
if (c) {
cfg[type] = c;
}
});
return cfg;
}
};
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var util = require('util'),
EventEmitter = require('events').EventEmitter,
Factory = require('../util/factory'),
factory = new Factory('report', __dirname, false);
/**
* An abstraction for producing coverage reports.
* This class is both the base class as well as a factory for `Report` implementations.
* All reports are event emitters and are expected to emit a `done` event when
* the report writing is complete.
*
* See also the `Reporter` class for easily producing multiple coverage reports
* with a single call.
*
* Usage
* -----
*
* var Report = require('istanbul').Report,
* report = Report.create('html'),
* collector = new require('istanbul').Collector;
*
* collector.add(coverageObject);
* report.on('done', function () { console.log('done'); });
* report.writeReport(collector);
*
* @class Report
* @module report
* @main report
* @constructor
* @protected
* @param {Object} options Optional. The options supported by a specific store implementation.
*/
function Report(/* options */) {
EventEmitter.call(this);
}
util.inherits(Report, EventEmitter);
//add register, create, mix, loadAll, getReportList as class methods
factory.bindClassMethods(Report);
/**
* registers a new report implementation.
* @method register
* @static
* @param {Function} constructor the constructor function for the report. This function must have a
* `TYPE` property of type String, that will be used in `Report.create()`
*/
/**
* returns a report implementation of the specified type.
* @method create
* @static
* @param {String} type the type of report to create
* @param {Object} opts Optional. Options specific to the report implementation
* @return {Report} a new store of the specified type
*/
/**
* returns the list of available reports as an array of strings
* @method getReportList
* @static
* @return an array of supported report formats
*/
var proto = {
/**
* returns a one-line summary of the report
* @method synopsis
* @return {String} a description of what the report is about
*/
synopsis: function () {
throw new Error('synopsis must be overridden');
},
/**
* returns a config object that has override-able keys settable via config
* @method getDefaultConfig
* @return {Object|null} an object representing keys that can be overridden via
* the istanbul configuration where the values are the defaults used when
* not specified. A null return implies no config attributes
*/
getDefaultConfig: function () {
return null;
},
/**
* writes the report for a set of coverage objects added to a collector.
* @method writeReport
* @param {Collector} collector the collector for getting the set of files and coverage
* @param {Boolean} sync true if reports must be written synchronously, false if they can be written using asynchronous means (e.g. stream.write)
*/
writeReport: function (/* collector, sync */) {
throw new Error('writeReport: must be overridden');
}
};
Object.keys(proto).forEach(function (k) {
Report.prototype[k] = proto[k];
});
module.exports = Report;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
objectUtils = require('../object-utils'),
Writer = require('../util/file-writer'),
util = require('util'),
Report = require('./index');
/**
* a `Report` implementation that produces a coverage JSON object with summary info only.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('json-summary');
*
*
* @class JsonSummaryReport
* @extends Report
* @module report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to write the `coverage-summary.json` file. Defaults to `process.cwd()`
*/
function JsonSummaryReport(opts) {
this.opts = opts || {};
this.opts.dir = this.opts.dir || process.cwd();
this.opts.file = this.opts.file || this.getDefaultConfig().file;
this.opts.writer = this.opts.writer || null;
}
JsonSummaryReport.TYPE = 'json-summary';
util.inherits(JsonSummaryReport, Report);
Report.mix(JsonSummaryReport, {
synopsis: function () {
return 'prints a summary coverage object as JSON to a file';
},
getDefaultConfig: function () {
return {
file: 'coverage-summary.json'
};
},
writeReport: function (collector, sync) {
var outputFile = path.resolve(this.opts.dir, this.opts.file),
writer = this.opts.writer || new Writer(sync),
that = this;
var summaries = [],
finalSummary;
collector.files().forEach(function (file) {
summaries.push(objectUtils.summarizeFileCoverage(collector.fileCoverageFor(file)));
});
finalSummary = objectUtils.mergeSummaryObjects.apply(null, summaries);
writer.on('done', function () { that.emit('done'); });
writer.writeFile(outputFile, function (contentWriter) {
contentWriter.println("{");
contentWriter.write('"total":');
contentWriter.write(JSON.stringify(finalSummary));
collector.files().forEach(function (key) {
contentWriter.println(",");
contentWriter.write(JSON.stringify(key));
contentWriter.write(":");
contentWriter.write(JSON.stringify(objectUtils.summarizeFileCoverage(collector.fileCoverageFor(key))));
});
contentWriter.println("}");
});
writer.done();
}
});
module.exports = JsonSummaryReport;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
Writer = require('../util/file-writer'),
util = require('util'),
Report = require('./index');
/**
* a `Report` implementation that produces a coverage JSON object.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('json');
*
*
* @class JsonReport
* @extends Report
* @module report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to write the `coverage-final.json` file. Defaults to `process.cwd()`
*/
function JsonReport(opts) {
this.opts = opts || {};
this.opts.dir = this.opts.dir || process.cwd();
this.opts.file = this.opts.file || this.getDefaultConfig().file;
this.opts.writer = this.opts.writer || null;
}
JsonReport.TYPE = 'json';
util.inherits(JsonReport, Report);
Report.mix(JsonReport, {
synopsis: function () {
return 'prints the coverage object as JSON to a file';
},
getDefaultConfig: function () {
return {
file: 'coverage-final.json'
};
},
writeReport: function (collector, sync) {
var outputFile = path.resolve(this.opts.dir, this.opts.file),
writer = this.opts.writer || new Writer(sync),
that = this;
writer.on('done', function () { that.emit('done'); });
writer.writeFile(outputFile, function (contentWriter) {
var first = true;
contentWriter.println("{");
collector.files().forEach(function (key) {
if (first) {
first = false;
} else {
contentWriter.println(",");
}
contentWriter.write(JSON.stringify(key));
contentWriter.write(":");
contentWriter.write(JSON.stringify(collector.fileCoverageFor(key)));
});
contentWriter.println("}");
});
writer.done();
}
});
module.exports = JsonReport;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
util = require('util'),
mkdirp = require('mkdirp'),
Report = require('./index'),
LcovOnlyReport = require('./lcovonly'),
HtmlReport = require('./html');
/**
* a `Report` implementation that produces an LCOV coverage file and an associated HTML report from coverage objects.
* The name and behavior of this report is designed to ease migration for projects that currently use `yuitest_coverage`
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('lcov');
*
*
* @class LcovReport
* @extends Report
* @module report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to the `lcov.info` file.
* HTML files are written in a subdirectory called `lcov-report`. Defaults to `process.cwd()`
*/
function LcovReport(opts) {
Report.call(this);
opts = opts || {};
var baseDir = path.resolve(opts.dir || process.cwd()),
htmlDir = path.resolve(baseDir, 'lcov-report');
mkdirp.sync(baseDir);
this.lcov = new LcovOnlyReport({ dir: baseDir, watermarks: opts.watermarks });
this.html = new HtmlReport({ dir: htmlDir, watermarks: opts.watermarks, sourceStore: opts.sourceStore});
}
LcovReport.TYPE = 'lcov';
util.inherits(LcovReport, Report);
Report.mix(LcovReport, {
synopsis: function () {
return 'combined lcovonly and html report that generates an lcov.info file as well as HTML';
},
writeReport: function (collector, sync) {
var handler = this.handleDone.bind(this);
this.inProgress = 2;
this.lcov.on('done', handler);
this.html.on('done', handler);
this.lcov.writeReport(collector, sync);
this.html.writeReport(collector, sync);
},
handleDone: function () {
this.inProgress -= 1;
if (this.inProgress === 0) {
this.emit('done');
}
}
});
module.exports = LcovReport;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
Writer = require('../util/file-writer'),
util = require('util'),
Report = require('./index'),
utils = require('../object-utils');
/**
* a `Report` implementation that produces an LCOV coverage file from coverage objects.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('lcovonly');
*
*
* @class LcovOnlyReport
* @extends Report
* @module report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to the `lcov.info` file. Defaults to `process.cwd()`
*/
function LcovOnlyReport(opts) {
this.opts = opts || {};
this.opts.dir = this.opts.dir || process.cwd();
this.opts.file = this.opts.file || this.getDefaultConfig().file;
this.opts.writer = this.opts.writer || null;
}
LcovOnlyReport.TYPE = 'lcovonly';
util.inherits(LcovOnlyReport, Report);
Report.mix(LcovOnlyReport, {
synopsis: function () {
return 'lcov coverage report that can be consumed by the lcov tool';
},
getDefaultConfig: function () {
return { file: 'lcov.info' };
},
writeFileCoverage: function (writer, fc) {
var functions = fc.f,
functionMap = fc.fnMap,
lines = fc.l,
branches = fc.b,
branchMap = fc.branchMap,
summary = utils.summarizeFileCoverage(fc);
writer.println('TN:'); //no test name
writer.println('SF:' + fc.path);
Object.keys(functions).forEach(function (key) {
var meta = functionMap[key];
writer.println('FN:' + [ meta.line, meta.name ].join(','));
});
writer.println('FNF:' + summary.functions.total);
writer.println('FNH:' + summary.functions.covered);
Object.keys(functions).forEach(function (key) {
var stats = functions[key],
meta = functionMap[key];
writer.println('FNDA:' + [ stats, meta.name ].join(','));
});
Object.keys(lines).forEach(function (key) {
var stat = lines[key];
writer.println('DA:' + [ key, stat ].join(','));
});
writer.println('LF:' + summary.lines.total);
writer.println('LH:' + summary.lines.covered);
Object.keys(branches).forEach(function (key) {
var branchArray = branches[key],
meta = branchMap[key],
line = meta.line,
i = 0;
branchArray.forEach(function (b) {
writer.println('BRDA:' + [line, key, i, b].join(','));
i += 1;
});
});
writer.println('BRF:' + summary.branches.total);
writer.println('BRH:' + summary.branches.covered);
writer.println('end_of_record');
},
writeReport: function (collector, sync) {
var outputFile = path.resolve(this.opts.dir, this.opts.file),
writer = this.opts.writer || new Writer(sync),
that = this;
writer.on('done', function () { that.emit('done'); });
writer.writeFile(outputFile, function (contentWriter) {
collector.files().forEach(function (key) {
that.writeFileCoverage(contentWriter, collector.fileCoverageFor(key));
});
});
writer.done();
}
});
module.exports = LcovOnlyReport;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var util = require('util'),
Report = require('./index');
/**
* a `Report` implementation that does nothing. Use to specify that no reporting
* is needed.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('none');
*
*
* @class NoneReport
* @extends Report
* @module report
* @constructor
*/
function NoneReport() {
Report.call(this);
}
NoneReport.TYPE = 'none';
util.inherits(NoneReport, Report);
Report.mix(NoneReport, {
synopsis: function () {
return 'Does nothing. Useful to override default behavior and suppress reporting entirely';
},
writeReport: function (/* collector, sync */) {
//noop
this.emit('done');
}
});
module.exports = NoneReport;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
util = require('util'),
mkdirp = require('mkdirp'),
fs = require('fs'),
utils = require('../object-utils'),
Report = require('./index');
/**
* a `Report` implementation that produces system messages interpretable by TeamCity.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('teamcity');
*
* @class TeamcityReport
* @extends Report
* @module report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to the text coverage report will be written, when writing to a file
* @param {String} [opts.file] the filename for the report. When omitted, the report is written to console
*/
function TeamcityReport(opts) {
Report.call(this);
opts = opts || {};
this.dir = opts.dir || process.cwd();
this.file = opts.file;
this.blockName = opts.blockName || this.getDefaultConfig().blockName;
}
TeamcityReport.TYPE = 'teamcity';
util.inherits(TeamcityReport, Report);
function lineForKey(value, teamcityVar) {
return '##teamcity[buildStatisticValue key=\'' + teamcityVar + '\' value=\'' + value + '\']';
}
Report.mix(TeamcityReport, {
synopsis: function () {
return 'report with system messages that can be interpreted with TeamCity';
},
getDefaultConfig: function () {
return { file: null , blockName: 'Code Coverage Summary'};
},
writeReport: function (collector /*, sync */) {
var summaries = [],
finalSummary,
lines = [],
text;
collector.files().forEach(function (file) {
summaries.push(utils.summarizeFileCoverage(collector.fileCoverageFor(file)));
});
finalSummary = utils.mergeSummaryObjects.apply(null, summaries);
lines.push('');
lines.push('##teamcity[blockOpened name=\''+ this.blockName +'\']');
//Statements Covered
lines.push(lineForKey(finalSummary.statements.pct, 'CodeCoverageB'));
//Methods Covered
lines.push(lineForKey(finalSummary.functions.covered, 'CodeCoverageAbsMCovered'));
lines.push(lineForKey(finalSummary.functions.total, 'CodeCoverageAbsMTotal'));
lines.push(lineForKey(finalSummary.functions.pct, 'CodeCoverageM'));
//Lines Covered
lines.push(lineForKey(finalSummary.lines.covered, 'CodeCoverageAbsLCovered'));
lines.push(lineForKey(finalSummary.lines.total, 'CodeCoverageAbsLTotal'));
lines.push(lineForKey(finalSummary.lines.pct, 'CodeCoverageL'));
lines.push('##teamcity[blockClosed name=\''+ this.blockName +'\']');
text = lines.join('\n');
if (this.file) {
mkdirp.sync(this.dir);
fs.writeFileSync(path.join(this.dir, this.file), text, 'utf8');
} else {
console.log(text);
}
this.emit('done');
}
});
module.exports = TeamcityReport;
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage
generated by <a href="http://istanbul-js.org/" target="_blank">istanbul</a> at {{datetime}}
</div>
</div>
{{#if prettify}}
<script src="{{prettify.js}}"></script>
<script>
window.onload = function () {
if (typeof prettyPrint === 'function') {
prettyPrint();
}
};
</script>
{{/if}}
<script src="{{sorter.js}}"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for {{entity}}</title>
<meta charset="utf-8" />
{{#if prettify}}
<link rel="stylesheet" href="{{prettify.css}}" />
{{/if}}
<link rel="stylesheet" href="{{base.css}}" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type='text/css'>
.coverage-summary .sorter {
background-image: url({{sorter.image}});
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1>
{{{pathHtml}}}
</h1>
<div class='clearfix'>
{{#with metrics.statements}}
<div class='fl pad1y space-right2'>
<span class="strong">{{pct}}% </span>
<span class="quiet">Statements</span>
<span class='fraction'>{{covered}}/{{total}}</span>
</div>
{{/with}}
{{#with metrics.branches}}
<div class='fl pad1y space-right2'>
<span class="strong">{{pct}}% </span>
<span class="quiet">Branches</span>
<span class='fraction'>{{covered}}/{{total}}</span>
</div>
{{/with}}
{{#with metrics.functions}}
<div class='fl pad1y space-right2'>
<span class="strong">{{pct}}% </span>
<span class="quiet">Functions</span>
<span class='fraction'>{{covered}}/{{total}}</span>
</div>
{{/with}}
{{#with metrics.lines}}
<div class='fl pad1y space-right2'>
<span class="strong">{{pct}}% </span>
<span class="quiet">Lines</span>
<span class='fraction'>{{covered}}/{{total}}</span>
</div>
{{/with}}
{{#if_has_ignores metrics}}
<div class='fl pad1y'>
<span class="strong">{{#show_ignores metrics}}{{/show_ignores}}</span>
<span class="quiet">Ignored</span> &nbsp;&nbsp;&nbsp;&nbsp;
</div>
{{/if_has_ignores}}
</div>
</div>
<div class='status-line {{reportClass}}'></div>
var LcovOnly = require('./lcovonly'),
util = require('util');
/**
* a `Report` implementation that produces an LCOV coverage and prints it
* to standard out.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('text-lcov');
*
* @class TextLcov
* @module report
* @extends LcovOnly
* @constructor
* @param {Object} opts optional
* @param {String} [opts.log] the method used to log to console.
*/
function TextLcov(opts) {
var that = this;
LcovOnly.call(this);
this.opts = opts || {};
this.opts.log = this.opts.log || console.log;
this.opts.writer = {
println: function (ln) {
that.opts.log(ln);
}
};
}
TextLcov.TYPE = 'text-lcov';
util.inherits(TextLcov, LcovOnly);
LcovOnly.super_.mix(TextLcov, {
writeReport: function (collector) {
var that = this,
writer = this.opts.writer;
collector.files().forEach(function (key) {
that.writeFileCoverage(writer, collector.fileCoverageFor(key));
});
this.emit('done');
}
});
module.exports = TextLcov;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
util = require('util'),
mkdirp = require('mkdirp'),
defaults = require('./common/defaults'),
fs = require('fs'),
utils = require('../object-utils'),
Report = require('./index');
/**
* a `Report` implementation that produces text output for overall coverage in summary format.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('text-summary');
*
* @class TextSummaryReport
* @extends Report
* @module report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to the text coverage report will be written, when writing to a file
* @param {String} [opts.file] the filename for the report. When omitted, the report is written to console
*/
function TextSummaryReport(opts) {
Report.call(this);
opts = opts || {};
this.dir = opts.dir || process.cwd();
this.file = opts.file;
this.watermarks = opts.watermarks || defaults.watermarks();
}
TextSummaryReport.TYPE = 'text-summary';
util.inherits(TextSummaryReport, Report);
function lineForKey(summary, key, watermarks) {
var metrics = summary[key],
skipped,
result,
clazz = defaults.classFor(key, summary, watermarks);
key = key.substring(0, 1).toUpperCase() + key.substring(1);
if (key.length < 12) { key += ' '.substring(0, 12 - key.length); }
result = [ key , ':', metrics.pct + '%', '(', metrics.covered + '/' + metrics.total, ')'].join(' ');
skipped = metrics.skipped;
if (skipped > 0) {
result += ', ' + skipped + ' ignored';
}
return defaults.colorize(result, clazz);
}
Report.mix(TextSummaryReport, {
synopsis: function () {
return 'text report that prints a coverage summary across all files, typically to console';
},
getDefaultConfig: function () {
return { file: null };
},
writeReport: function (collector /*, sync */) {
var summaries = [],
finalSummary,
lines = [],
watermarks = this.watermarks,
text;
collector.files().forEach(function (file) {
summaries.push(utils.summarizeFileCoverage(collector.fileCoverageFor(file)));
});
finalSummary = utils.mergeSummaryObjects.apply(null, summaries);
lines.push('');
lines.push('=============================== Coverage summary ===============================');
lines.push.apply(lines, [
lineForKey(finalSummary, 'statements', watermarks),
lineForKey(finalSummary, 'branches', watermarks),
lineForKey(finalSummary, 'functions', watermarks),
lineForKey(finalSummary, 'lines', watermarks)
]);
lines.push('================================================================================');
text = lines.join('\n');
if (this.file) {
mkdirp.sync(this.dir);
fs.writeFileSync(path.join(this.dir, this.file), text, 'utf8');
} else {
console.log(text);
}
this.emit('done');
}
});
module.exports = TextSummaryReport;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
mkdirp = require('mkdirp'),
util = require('util'),
fs = require('fs'),
defaults = require('./common/defaults'),
Report = require('./index'),
TreeSummarizer = require('../util/tree-summarizer'),
utils = require('../object-utils'),
PCT_COLS = 9,
MISSING_COL = 15,
TAB_SIZE = 1,
DELIM = ' |',
COL_DELIM = '-|';
/**
* a `Report` implementation that produces text output in a detailed table.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('text');
*
* @class TextReport
* @extends Report
* @module report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to the text coverage report will be written, when writing to a file
* @param {String} [opts.file] the filename for the report. When omitted, the report is written to console
* @param {Number} [opts.maxCols] the max column width of the report. By default, the width of the report is adjusted based on the length of the paths
* to be reported.
*/
function TextReport(opts) {
Report.call(this);
opts = opts || {};
this.dir = opts.dir || process.cwd();
this.file = opts.file;
this.summary = opts.summary;
this.maxCols = opts.maxCols || 0;
this.watermarks = opts.watermarks || defaults.watermarks();
}
TextReport.TYPE = 'text';
util.inherits(TextReport, Report);
function padding(num, ch) {
var str = '',
i;
ch = ch || ' ';
for (i = 0; i < num; i += 1) {
str += ch;
}
return str;
}
function fill(str, width, right, tabs, clazz) {
tabs = tabs || 0;
str = String(str);
var leadingSpaces = tabs * TAB_SIZE,
remaining = width - leadingSpaces,
leader = padding(leadingSpaces),
fmtStr = '',
fillStr,
strlen = str.length;
if (remaining > 0) {
if (remaining >= strlen) {
fillStr = padding(remaining - strlen);
fmtStr = right ? fillStr + str : str + fillStr;
} else {
fmtStr = str.substring(strlen - remaining);
fmtStr = '... ' + fmtStr.substring(4);
}
}
fmtStr = defaults.colorize(fmtStr, clazz);
return leader + fmtStr;
}
function formatName(name, maxCols, level, clazz) {
return fill(name, maxCols, false, level, clazz);
}
function formatPct(pct, clazz, width) {
return fill(pct, width || PCT_COLS, true, 0, clazz);
}
function nodeName(node) {
return node.displayShortName() || 'All files';
}
function tableHeader(maxNameCols) {
var elements = [];
elements.push(formatName('File', maxNameCols, 0));
elements.push(formatPct('% Stmts'));
elements.push(formatPct('% Branch'));
elements.push(formatPct('% Funcs'));
elements.push(formatPct('% Lines'));
elements.push(formatPct('Uncovered Lines', undefined, MISSING_COL));
return elements.join(' |') + ' |';
}
function collectMissingLines(kind, linesCovered) {
var missingLines = [];
if (kind !== 'file') {
return [];
}
Object.keys(linesCovered).forEach(function (key) {
if (!linesCovered[key]) {
missingLines.push(key);
}
});
return missingLines;
}
function tableRow(node, maxNameCols, level, watermarks) {
var name = nodeName(node),
statements = node.metrics.statements.pct,
branches = node.metrics.branches.pct,
functions = node.metrics.functions.pct,
lines = node.metrics.lines.pct,
missingLines = collectMissingLines(node.kind, node.metrics.linesCovered),
elements = [];
elements.push(formatName(name, maxNameCols, level, defaults.classFor('statements', node.metrics, watermarks)));
elements.push(formatPct(statements, defaults.classFor('statements', node.metrics, watermarks)));
elements.push(formatPct(branches, defaults.classFor('branches', node.metrics, watermarks)));
elements.push(formatPct(functions, defaults.classFor('functions', node.metrics, watermarks)));
elements.push(formatPct(lines, defaults.classFor('lines', node.metrics, watermarks)));
elements.push(formatPct(missingLines.join(','), 'low', MISSING_COL));
return elements.join(DELIM) + DELIM;
}
function findNameWidth(node, level, last) {
last = last || 0;
level = level || 0;
var idealWidth = TAB_SIZE * level + nodeName(node).length;
if (idealWidth > last) {
last = idealWidth;
}
node.children.forEach(function (child) {
last = findNameWidth(child, level + 1, last);
});
return last;
}
function makeLine(nameWidth) {
var name = padding(nameWidth, '-'),
pct = padding(PCT_COLS, '-'),
elements = [];
elements.push(name);
elements.push(pct);
elements.push(pct);
elements.push(pct);
elements.push(pct);
elements.push(padding(MISSING_COL, '-'));
return elements.join(COL_DELIM) + COL_DELIM;
}
function walk(node, nameWidth, array, level, watermarks) {
var line;
if (level === 0) {
line = makeLine(nameWidth);
array.push(line);
array.push(tableHeader(nameWidth));
array.push(line);
} else {
array.push(tableRow(node, nameWidth, level, watermarks));
}
node.children.forEach(function (child) {
walk(child, nameWidth, array, level + 1, watermarks);
});
if (level === 0) {
array.push(line);
array.push(tableRow(node, nameWidth, level, watermarks));
array.push(line);
}
}
Report.mix(TextReport, {
synopsis: function () {
return 'text report that prints a coverage line for every file, typically to console';
},
getDefaultConfig: function () {
return { file: null, maxCols: 0 };
},
writeReport: function (collector /*, sync */) {
var summarizer = new TreeSummarizer(),
tree,
root,
nameWidth,
statsWidth = 4 * (PCT_COLS + 2) + MISSING_COL,
maxRemaining,
strings = [],
text;
collector.files().forEach(function (key) {
summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(
collector.fileCoverageFor(key)
));
});
tree = summarizer.getTreeSummary();
root = tree.root;
nameWidth = findNameWidth(root);
if (this.maxCols > 0) {
maxRemaining = this.maxCols - statsWidth - 2;
if (nameWidth > maxRemaining) {
nameWidth = maxRemaining;
}
}
walk(root, nameWidth, strings, 0, this.watermarks);
text = strings.join('\n') + '\n';
if (this.file) {
mkdirp.sync(this.dir);
fs.writeFileSync(path.join(this.dir, this.file), text, 'utf8');
} else {
console.log(text);
}
this.emit('done');
}
});
module.exports = TextReport;
/*
Copyright (c) 2014, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var Report = require('./report'),
configuration = require('./config'),
inputError = require('./util/input-error');
/**
* convenience mechanism to write one or more reports ensuring that config
* options are respected.
* Usage
* -----
*
* var fs = require('fs'),
* reporter = new require('istanbul').Reporter(),
* collector = new require('istanbul').Collector(),
* sync = true;
*
* collector.add(JSON.parse(fs.readFileSync('coverage.json', 'utf8')));
* reporter.add('lcovonly');
* reporter.addAll(['clover', 'cobertura']);
* reporter.write(collector, sync, function () { console.log('done'); });
*
* @class Reporter
* @param {Configuration} cfg the config object, a falsy value will load the
* default configuration instead
* @param {String} dir the directory in which to write the reports, may be falsy
* to use config or global defaults
* @constructor
* @module main
*/
function Reporter(cfg, dir) {
this.config = cfg || configuration.loadFile();
this.dir = dir || this.config.reporting.dir();
this.reports = {};
}
Reporter.prototype = {
/**
* adds a report to be generated. Must be one of the entries returned
* by `Report.getReportList()`
* @method add
* @param {String} fmt the format of the report to generate
*/
add: function (fmt) {
if (this.reports[fmt]) { // already added
return;
}
var config = this.config,
rptConfig = config.reporting.reportConfig()[fmt] || {};
rptConfig.verbose = config.verbose;
rptConfig.dir = this.dir;
rptConfig.watermarks = config.reporting.watermarks();
try {
this.reports[fmt] = Report.create(fmt, rptConfig);
} catch (ex) {
throw inputError.create('Invalid report format [' + fmt + ']');
}
},
/**
* adds an array of report formats to be generated
* @method addAll
* @param {Array} fmts an array of report formats
*/
addAll: function (fmts) {
var that = this;
fmts.forEach(function (f) {
that.add(f);
});
},
/**
* writes all reports added and calls the callback when done
* @method write
* @param {Collector} collector the collector having the coverage data
* @param {Boolean} sync true to write reports synchronously
* @param {Function} callback the callback to call when done. When `sync`
* is true, the callback will be called in the same process tick.
*/
write: function (collector, sync, callback) {
var reports = this.reports,
verbose = this.config.verbose,
handler = this.handleDone.bind(this, callback);
this.inProgress = Object.keys(reports).length;
Object.keys(reports).forEach(function (name) {
var report = reports[name];
if (verbose) {
console.error('Write report: ' + name);
}
report.on('done', handler);
report.writeReport(collector, sync);
});
},
/*
* handles listening on all reports to be completed before calling the callback
* @method handleDone
* @private
* @param {Function} callback the callback to call when all reports are
* written
*/
handleDone: function (callback) {
this.inProgress -= 1;
if (this.inProgress === 0) {
return callback();
}
}
};
module.exports = Reporter;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var util = require('util'),
fs = require('fs'),
Store = require('./index');
/**
* a `Store` implementation that doesn't actually store anything. It assumes that keys
* are absolute file paths, and contents are contents of those files.
* Thus, `set` for this store is no-op, `get` returns the
* contents of the filename that the key represents, `hasKey` returns true if the key
* supplied is a valid file path and `keys` always returns an empty array.
*
* Usage
* -----
*
* var store = require('istanbul').Store.create('fslookup');
*
*
* @class LookupStore
* @extends Store
* @module store
* @constructor
*/
function LookupStore(opts) {
Store.call(this, opts);
}
LookupStore.TYPE = 'fslookup';
util.inherits(LookupStore, Store);
Store.mix(LookupStore, {
keys: function () {
return [];
},
get: function (key) {
return fs.readFileSync(key, 'utf8');
},
hasKey: function (key) {
var stats;
try {
stats = fs.statSync(key);
return stats.isFile();
} catch (ex) {
return false;
}
},
set: function (key /*, contents */) {
if (!this.hasKey(key)) {
throw new Error('Attempt to set contents for non-existent file [' + key + '] on a fslookup store');
}
return key;
}
});
module.exports = LookupStore;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var Factory = require('../util/factory'),
factory = new Factory('store', __dirname, false);
/**
* An abstraction for keeping track of content against some keys (e.g.
* original source, instrumented source, coverage objects against file names).
* This class is both the base class as well as a factory for `Store` implementations.
*
* Usage
* -----
*
* var Store = require('istanbul').Store,
* store = Store.create('memory');
*
* //basic use
* store.set('foo', 'foo-content');
* var content = store.get('foo');
*
* //keys and values
* store.keys().forEach(function (key) {
* console.log(key + ':\n' + store.get(key);
* });
* if (store.hasKey('bar') { console.log(store.get('bar'); }
*
*
* //syntactic sugar
* store.setObject('foo', { foo: true });
* console.log(store.getObject('foo').foo);
*
* store.dispose();
*
* @class Store
* @constructor
* @module store
* @param {Object} options Optional. The options supported by a specific store implementation.
* @main store
*/
function Store(/* options */) {}
//add register, create, mix, loadAll, getStoreList as class methods
factory.bindClassMethods(Store);
/**
* registers a new store implementation.
* @method register
* @static
* @param {Function} constructor the constructor function for the store. This function must have a
* `TYPE` property of type String, that will be used in `Store.create()`
*/
/**
* returns a store implementation of the specified type.
* @method create
* @static
* @param {String} type the type of store to create
* @param {Object} opts Optional. Options specific to the store implementation
* @return {Store} a new store of the specified type
*/
Store.prototype = {
/**
* sets some content associated with a specific key. The manner in which
* duplicate keys are handled for multiple `set()` calls with the same
* key is implementation-specific.
*
* @method set
* @param {String} key the key for the content
* @param {String} contents the contents for the key
*/
set: function (/* key, contents */) { throw new Error("set: must be overridden"); },
/**
* returns the content associated to a specific key or throws if the key
* was not `set`
* @method get
* @param {String} key the key for which to get the content
* @return {String} the content for the specified key
*/
get: function (/* key */) { throw new Error("get: must be overridden"); },
/**
* returns a list of all known keys
* @method keys
* @return {Array} an array of seen keys
*/
keys: function () { throw new Error("keys: must be overridden"); },
/**
* returns true if the key is one for which a `get()` call would work.
* @method hasKey
* @param {String} key
* @return true if the key is valid for this store, false otherwise
*/
hasKey: function (/* key */) { throw new Error("hasKey: must be overridden"); },
/**
* lifecycle method to dispose temporary resources associated with the store
* @method dispose
*/
dispose: function () {},
/**
* sugar method to return an object associated with a specific key. Throws
* if the content set against the key was not a valid JSON string.
* @method getObject
* @param {String} key the key for which to return the associated object
* @return {Object} the object corresponding to the key
*/
getObject: function (key) {
return JSON.parse(this.get(key));
},
/**
* sugar method to set an object against a specific key.
* @method setObject
* @param {String} key the key for the object
* @param {Object} object the object to be stored
*/
setObject: function (key, object) {
return this.set(key, JSON.stringify(object));
}
};
module.exports = Store;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var util = require('util'),
Store = require('./index');
/**
* a `Store` implementation using an in-memory object.
*
* Usage
* -----
*
* var store = require('istanbul').Store.create('memory');
*
*
* @class MemoryStore
* @extends Store
* @module store
* @constructor
*/
function MemoryStore() {
Store.call(this);
this.map = {};
}
MemoryStore.TYPE = 'memory';
util.inherits(MemoryStore, Store);
Store.mix(MemoryStore, {
set: function (key, contents) {
this.map[key] = contents;
},
get: function (key) {
if (!this.hasKey(key)) {
throw new Error('Unable to find entry for [' + key + ']');
}
return this.map[key];
},
hasKey: function (key) {
return this.map.hasOwnProperty(key);
},
keys: function () {
return Object.keys(this.map);
},
dispose: function () {
this.map = {};
}
});
module.exports = MemoryStore;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var util = require('util'),
path = require('path'),
os = require('os'),
fs = require('fs'),
mkdirp = require('mkdirp'),
Store = require('./index');
function makeTempDir() {
var dir = path.join(os.tmpdir ? os.tmpdir() : /* istanbul ignore next */ (process.env.TMPDIR || '/tmp'), 'ts' + new Date().getTime());
mkdirp.sync(dir);
return dir;
}
/**
* a `Store` implementation using temporary files.
*
* Usage
* -----
*
* var store = require('istanbul').Store.create('tmp');
*
*
* @class TmpStore
* @extends Store
* @module store
* @param {Object} opts Optional.
* @param {String} [opts.tmp] a pre-existing directory to use as the `tmp` directory. When not specified, a random directory
* is created under `os.tmpdir()`
* @constructor
*/
function TmpStore(opts) {
opts = opts || {};
this.tmp = opts.tmp || makeTempDir();
this.map = {};
this.seq = 0;
this.prefix = 't' + new Date().getTime() + '-';
}
TmpStore.TYPE = 'tmp';
util.inherits(TmpStore, Store);
Store.mix(TmpStore, {
generateTmpFileName: function () {
this.seq += 1;
return path.join(this.tmp, this.prefix + this.seq + '.tmp');
},
set: function (key, contents) {
var tmpFile = this.generateTmpFileName();
fs.writeFileSync(tmpFile, contents, 'utf8');
this.map[key] = tmpFile;
},
get: function (key) {
var tmpFile = this.map[key];
if (!tmpFile) { throw new Error('Unable to find tmp entry for [' + tmpFile + ']'); }
return fs.readFileSync(tmpFile, 'utf8');
},
hasKey: function (key) {
return !!this.map[key];
},
keys: function () {
return Object.keys(this.map);
},
dispose: function () {
var map = this.map;
Object.keys(map).forEach(function (key) {
fs.unlinkSync(map[key]);
});
this.map = {};
}
});
module.exports = TmpStore;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var util = require('util'),
path = require('path'),
fs = require('fs'),
abbrev = require('abbrev');
function Factory(kind, dir, allowAbbreviations) {
this.kind = kind;
this.dir = dir;
this.allowAbbreviations = allowAbbreviations;
this.classMap = {};
this.abbreviations = null;
}
Factory.prototype = {
knownTypes: function () {
var keys = Object.keys(this.classMap);
keys.sort();
return keys;
},
resolve: function (abbreviatedType) {
if (!this.abbreviations) {
this.abbreviations = abbrev(this.knownTypes());
}
return this.abbreviations[abbreviatedType];
},
register: function (constructor) {
var type = constructor.TYPE;
if (!type) { throw new Error('Could not register ' + this.kind + ' constructor [no TYPE property]: ' + util.inspect(constructor)); }
this.classMap[type] = constructor;
this.abbreviations = null;
},
create: function (type, opts) {
var allowAbbrev = this.allowAbbreviations,
realType = allowAbbrev ? this.resolve(type) : type,
Cons;
Cons = realType ? this.classMap[realType] : null;
if (!Cons) { throw new Error('Invalid ' + this.kind + ' [' + type + '], allowed values are ' + this.knownTypes().join(', ')); }
return new Cons(opts);
},
loadStandard: function (dir) {
var that = this;
fs.readdirSync(dir).forEach(function (file) {
if (file !== 'index.js' && file.indexOf('.js') === file.length - 3) {
try {
that.register(require(path.resolve(dir, file)));
} catch (ex) {
console.error(ex.message);
console.error(ex.stack);
throw new Error('Could not register ' + that.kind + ' from file ' + file);
}
}
});
},
bindClassMethods: function (Cons) {
var tmpKind = this.kind.charAt(0).toUpperCase() + this.kind.substring(1), //ucfirst
allowAbbrev = this.allowAbbreviations;
Cons.mix = Factory.mix;
Cons.register = this.register.bind(this);
Cons.create = this.create.bind(this);
Cons.loadAll = this.loadStandard.bind(this, this.dir);
Cons['get' + tmpKind + 'List'] = this.knownTypes.bind(this);
if (allowAbbrev) {
Cons['resolve' + tmpKind + 'Name'] = this.resolve.bind(this);
}
}
};
Factory.mix = function (cons, proto) {
Object.keys(proto).forEach(function (key) {
cons.prototype[key] = proto[key];
});
};
module.exports = Factory;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var async = require('async'),
glob = require('glob'),
fs = require('fs'),
path = require('path'),
seq = 0;
function filesFor(options, callback) {
if (!callback && typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
var root = options.root,
includes = options.includes,
excludes = options.excludes,
realpath = options.realpath,
relative = options.relative,
opts;
root = root || process.cwd();
includes = includes && Array.isArray(includes) ? includes : [ '**/*.js' ];
excludes = excludes && Array.isArray(excludes) ? excludes : [ '**/node_modules/**' ];
opts = { cwd: root, nodir: true, ignore: excludes };
seq += 1;
opts['x' + seq + new Date().getTime()] = true; //cache buster for minimatch cache bug
glob(includes.join(' '), opts, function (err, files) {
if (err) { return callback(err); }
if (relative) { return callback(err, files); }
if (!realpath) {
files = files.map(function (file) { return path.resolve(root, file); });
return callback(err, files);
}
var realPathCache = module.constructor._realpathCache || {};
async.map(files, function (file, done) {
fs.realpath(path.resolve(root, file), realPathCache, done);
}, callback);
});
}
function matcherFor(options, callback) {
if (!callback && typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
options.relative = false; //force absolute paths
options.realpath = true; //force real paths (to match Node.js module paths)
filesFor(options, function (err, files) {
var fileMap = {},
matchFn;
if (err) { return callback(err); }
files.forEach(function (file) { fileMap[file] = true; });
matchFn = function (file) { return fileMap[file]; };
matchFn.files = Object.keys(fileMap);
return callback(null, matchFn);
});
}
module.exports = {
filesFor: filesFor,
matcherFor: matcherFor
};
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
util = require('util'),
fs = require('fs'),
async = require('async'),
mkdirp = require('mkdirp'),
writer = require('./writer'),
Writer = writer.Writer,
ContentWriter = writer.ContentWriter;
function extend(cons, proto) {
Object.keys(proto).forEach(function (k) {
cons.prototype[k] = proto[k];
});
}
function BufferedContentWriter() {
ContentWriter.call(this);
this.content = '';
}
util.inherits(BufferedContentWriter, ContentWriter);
extend(BufferedContentWriter, {
write: function (str) {
this.content += str;
},
getContent: function () {
return this.content;
}
});
function StreamContentWriter(stream) {
ContentWriter.call(this);
this.stream = stream;
}
util.inherits(StreamContentWriter, ContentWriter);
extend(StreamContentWriter, {
write: function (str) {
this.stream.write(str);
}
});
function SyncFileWriter() {
Writer.call(this);
}
util.inherits(SyncFileWriter, Writer);
extend(SyncFileWriter, {
writeFile: function (file, callback) {
mkdirp.sync(path.dirname(file));
var cw = new BufferedContentWriter();
callback(cw);
fs.writeFileSync(file, cw.getContent(), 'utf8');
},
done: function () {
this.emit('done'); //everything already done
}
});
function AsyncFileWriter() {
this.queue = async.queue(this.processFile.bind(this), 20);
this.openFileMap = {};
}
util.inherits(AsyncFileWriter, Writer);
extend(AsyncFileWriter, {
writeFile: function (file, callback) {
this.openFileMap[file] = true;
this.queue.push({ file: file, callback: callback });
},
processFile: function (task, cb) {
var file = task.file,
userCallback = task.callback,
that = this,
stream,
contentWriter;
mkdirp.sync(path.dirname(file));
stream = fs.createWriteStream(file);
stream.on('close', function () {
delete that.openFileMap[file];
cb();
that.checkDone();
});
stream.on('error', function (err) { that.emit('error', err); });
contentWriter = new StreamContentWriter(stream);
userCallback(contentWriter);
stream.end();
},
done: function () {
this.doneCalled = true;
this.checkDone();
},
checkDone: function () {
if (!this.doneCalled) { return; }
if (Object.keys(this.openFileMap).length === 0) {
this.emit('done');
}
}
});
/**
* a concrete writer implementation that can write files synchronously or
* asynchronously based on the constructor argument passed to it.
*
* Usage
* -----
*
* var sync = true,
* fileWriter = new require('istanbul').FileWriter(sync);
*
* fileWriter.on('done', function () { console.log('done'); });
* fileWriter.copyFile('/foo/bar.jpg', '/baz/bar.jpg');
* fileWriter.writeFile('/foo/index.html', function (contentWriter) {
* contentWriter.println('<html>');
* contentWriter.println('</html>');
* });
* fileWriter.done(); // will emit the `done` event when all files are written
*
* @class FileWriter
* @extends Writer
* @module io
* @param sync
* @constructor
*/
function FileWriter(sync) {
Writer.call(this);
var that = this;
this.delegate = sync ? new SyncFileWriter() : new AsyncFileWriter();
this.delegate.on('error', function (err) { that.emit('error', err); });
this.delegate.on('done', function () { that.emit('done'); });
}
util.inherits(FileWriter, Writer);
extend(FileWriter, {
copyFile: function (source, dest) {
mkdirp.sync(path.dirname(dest));
fs.writeFileSync(dest, fs.readFileSync(source));
},
writeFile: function (file, callback) {
this.delegate.writeFile(file, callback);
},
done: function () {
this.delegate.done();
}
});
module.exports = FileWriter;
\ No newline at end of file
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var OPT_PREFIX = " ",
OPT_START = OPT_PREFIX.length,
TEXT_START = 14,
STOP = 80,
wrap = require('wordwrap')(TEXT_START, STOP),
paraWrap = require('wordwrap')(1, STOP);
function formatPara(text) {
return paraWrap(text);
}
function formatOption(option, helpText) {
var formattedText = wrap(helpText);
if (option.length > TEXT_START - OPT_START - 2) {
return OPT_PREFIX + option + '\n' + formattedText;
} else {
return OPT_PREFIX + option + formattedText.substring((OPT_PREFIX + option).length);
}
}
module.exports = {
formatPara: formatPara,
formatOption: formatOption
};
\ No newline at end of file
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
module.exports.create = function (message) {
var err = new Error(message);
err.inputError = true;
return err;
};
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
function InsertionText(text, consumeBlanks) {
this.text = text;
this.origLength = text.length;
this.offsets = [];
this.consumeBlanks = consumeBlanks;
this.startPos = this.findFirstNonBlank();
this.endPos = this.findLastNonBlank();
}
var WHITE_RE = /[ \f\n\r\t\v\u00A0\u2028\u2029]/;
InsertionText.prototype = {
findFirstNonBlank: function () {
var pos = -1,
text = this.text,
len = text.length,
i;
for (i = 0; i < len; i += 1) {
if (!text.charAt(i).match(WHITE_RE)) {
pos = i;
break;
}
}
return pos;
},
findLastNonBlank: function () {
var text = this.text,
len = text.length,
pos = text.length + 1,
i;
for (i = len - 1; i >= 0; i -= 1) {
if (!text.charAt(i).match(WHITE_RE)) {
pos = i;
break;
}
}
return pos;
},
originalLength: function () {
return this.origLength;
},
insertAt: function (col, str, insertBefore, consumeBlanks) {
consumeBlanks = typeof consumeBlanks === 'undefined' ? this.consumeBlanks : consumeBlanks;
col = col > this.originalLength() ? this.originalLength() : col;
col = col < 0 ? 0 : col;
if (consumeBlanks) {
if (col <= this.startPos) {
col = 0;
}
if (col > this.endPos) {
col = this.origLength;
}
}
var len = str.length,
offset = this.findOffset(col, len, insertBefore),
realPos = col + offset,
text = this.text;
this.text = text.substring(0, realPos) + str + text.substring(realPos);
return this;
},
findOffset: function (pos, len, insertBefore) {
var offsets = this.offsets,
offsetObj,
cumulativeOffset = 0,
i;
for (i = 0; i < offsets.length; i += 1) {
offsetObj = offsets[i];
if (offsetObj.pos < pos || (offsetObj.pos === pos && !insertBefore)) {
cumulativeOffset += offsetObj.len;
}
if (offsetObj.pos >= pos) {
break;
}
}
if (offsetObj && offsetObj.pos === pos) {
offsetObj.len += len;
} else {
offsets.splice(i, 0, { pos: pos, len: len });
}
return cumulativeOffset;
},
wrap: function (startPos, startText, endPos, endText, consumeBlanks) {
this.insertAt(startPos, startText, true, consumeBlanks);
this.insertAt(endPos, endText, false, consumeBlanks);
return this;
},
wrapLine: function (startText, endText) {
this.wrap(0, startText, this.originalLength(), endText);
},
toString: function () {
return this.text;
}
};
module.exports = InsertionText;
\ No newline at end of file
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
fs = require('fs'),
pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', '..', 'package.json'), 'utf8'));
module.exports = {
NAME: pkg.name,
VERSION: pkg.version
};
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
SEP = path.sep || '/',
utils = require('../object-utils');
function commonArrayPrefix(first, second) {
var len = first.length < second.length ? first.length : second.length,
i,
ret = [];
for (i = 0; i < len; i += 1) {
if (first[i] === second[i]) {
ret.push(first[i]);
} else {
break;
}
}
return ret;
}
function findCommonArrayPrefix(args) {
if (args.length === 0) {
return [];
}
var separated = args.map(function (arg) { return arg.split(SEP); }),
ret = separated.pop();
if (separated.length === 0) {
return ret.slice(0, ret.length - 1);
} else {
return separated.reduce(commonArrayPrefix, ret);
}
}
function Node(fullName, kind, metrics) {
this.name = fullName;
this.fullName = fullName;
this.kind = kind;
this.metrics = metrics || null;
this.parent = null;
this.children = [];
}
Node.prototype = {
displayShortName: function () {
return this.relativeName;
},
fullPath: function () {
return this.fullName;
},
addChild: function (child) {
this.children.push(child);
child.parent = this;
},
toJSON: function () {
return {
name: this.name,
relativeName: this.relativeName,
fullName: this.fullName,
kind: this.kind,
metrics: this.metrics,
parent: this.parent === null ? null : this.parent.name,
children: this.children.map(function (node) { return node.toJSON(); })
};
}
};
function TreeSummary(summaryMap, commonPrefix) {
this.prefix = commonPrefix;
this.convertToTree(summaryMap, commonPrefix);
}
TreeSummary.prototype = {
getNode: function (shortName) {
return this.map[shortName];
},
convertToTree: function (summaryMap, arrayPrefix) {
var nodes = [],
rootPath = arrayPrefix.join(SEP) + SEP,
root = new Node(rootPath, 'dir'),
tmp,
tmpChildren,
seen = {},
filesUnderRoot = false;
seen[rootPath] = root;
Object.keys(summaryMap).forEach(function (key) {
var metrics = summaryMap[key],
node,
parentPath,
parent;
node = new Node(key, 'file', metrics);
seen[key] = node;
nodes.push(node);
parentPath = path.dirname(key) + SEP;
if (parentPath === SEP + SEP || parentPath === '.' + SEP) {
parentPath = SEP + '__root__' + SEP;
}
parent = seen[parentPath];
if (!parent) {
parent = new Node(parentPath, 'dir');
root.addChild(parent);
seen[parentPath] = parent;
}
parent.addChild(node);
if (parent === root) { filesUnderRoot = true; }
});
if (filesUnderRoot && arrayPrefix.length > 0) {
arrayPrefix.pop(); //start at one level above
tmp = root;
tmpChildren = tmp.children;
tmp.children = [];
root = new Node(arrayPrefix.join(SEP) + SEP, 'dir');
root.addChild(tmp);
tmpChildren.forEach(function (child) {
if (child.kind === 'dir') {
root.addChild(child);
} else {
tmp.addChild(child);
}
});
}
this.fixupNodes(root, arrayPrefix.join(SEP) + SEP);
this.calculateMetrics(root);
this.root = root;
this.map = {};
this.indexAndSortTree(root, this.map);
},
fixupNodes: function (node, prefix, parent) {
var that = this;
if (node.name.indexOf(prefix) === 0) {
node.name = node.name.substring(prefix.length);
}
if (node.name.charAt(0) === SEP) {
node.name = node.name.substring(1);
}
if (parent) {
if (parent.name !== '__root__' + SEP) {
node.relativeName = node.name.substring(parent.name.length);
} else {
node.relativeName = node.name;
}
} else {
node.relativeName = node.name.substring(prefix.length);
}
node.children.forEach(function (child) {
that.fixupNodes(child, prefix, node);
});
},
calculateMetrics: function (entry) {
var that = this,
fileChildren;
if (entry.kind !== 'dir') {return; }
entry.children.forEach(function (child) {
that.calculateMetrics(child);
});
entry.metrics = utils.mergeSummaryObjects.apply(
null,
entry.children.map(function (child) { return child.metrics; })
);
// calclulate "java-style" package metrics where there is no hierarchy
// across packages
fileChildren = entry.children.filter(function (n) { return n.kind !== 'dir'; });
if (fileChildren.length > 0) {
entry.packageMetrics = utils.mergeSummaryObjects.apply(
null,
fileChildren.map(function (child) { return child.metrics; })
);
} else {
entry.packageMetrics = null;
}
},
indexAndSortTree: function (node, map) {
var that = this;
map[node.name] = node;
node.children.sort(function (a, b) {
a = a.relativeName;
b = b.relativeName;
return a < b ? -1 : a > b ? 1 : 0;
});
node.children.forEach(function (child) {
that.indexAndSortTree(child, map);
});
},
toJSON: function () {
return {
prefix: this.prefix,
root: this.root.toJSON()
};
}
};
function TreeSummarizer() {
this.summaryMap = {};
}
TreeSummarizer.prototype = {
addFileCoverageSummary: function (filePath, metrics) {
this.summaryMap[filePath] = metrics;
},
getTreeSummary: function () {
var commonArrayPrefix = findCommonArrayPrefix(Object.keys(this.summaryMap));
return new TreeSummary(this.summaryMap, commonArrayPrefix);
}
};
module.exports = TreeSummarizer;
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var util = require('util'),
EventEmitter = require('events').EventEmitter;
function extend(cons, proto) {
Object.keys(proto).forEach(function (k) {
cons.prototype[k] = proto[k];
});
}
/**
* abstract interfaces for writing content
* @class ContentWriter
* @module io
* @main io
* @constructor
*/
//abstract interface for writing content
function ContentWriter() {
}
ContentWriter.prototype = {
/**
* writes the specified string as-is
* @method write
* @param {String} str the string to write
*/
write: /* istanbul ignore next: abstract method */ function (/* str */) {
throw new Error('write: must be overridden');
},
/**
* writes the specified string with a newline at the end
* @method println
* @param {String} str the string to write
*/
println: function (str) { this.write(str + '\n'); }
};
/**
* abstract interface for writing files and assets. The caller is expected to
* call `done` on the writer after it has finished writing all the required
* files. The writer is an event-emitter that emits a `done` event when `done`
* is called on it *and* all files have successfully been written.
*
* @class Writer
* @constructor
*/
function Writer() {
EventEmitter.call(this);
}
util.inherits(Writer, EventEmitter);
extend(Writer, {
/**
* allows writing content to a file using a callback that is passed a content writer
* @method writeFile
* @param {String} file the name of the file to write
* @param {Function} callback the callback that is called as `callback(contentWriter)`
*/
writeFile: /* istanbul ignore next: abstract method */ function (/* file, callback */) {
throw new Error('writeFile: must be overridden');
},
/**
* copies a file from source to destination
* @method copyFile
* @param {String} source the file to copy, found on the file system
* @param {String} dest the destination path
*/
copyFile: /* istanbul ignore next: abstract method */ function (/* source, dest */) {
throw new Error('copyFile: must be overridden');
},
/**
* marker method to indicate that the caller is done with this writer object
* The writer is expected to emit a `done` event only after this method is called
* and it is truly done.
* @method done
*/
done: /* istanbul ignore next: abstract method */ function () {
throw new Error('done: must be overridden');
}
});
module.exports = {
Writer: Writer,
ContentWriter: ContentWriter
};
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
//EXPERIMENTAL code: do not rely on this in anyway until the docs say it is allowed
var path = require('path'),
yuiRegexp = /yui-nodejs\.js$/;
module.exports = function (matchFn, transformFn, verbose) {
return function (file) {
if (!file.match(yuiRegexp)) {
return;
}
var YMain = require(file),
YUI,
loaderFn,
origGet;
if (YMain.YUI) {
YUI = YMain.YUI;
loaderFn = YUI.Env && YUI.Env.mods && YUI.Env.mods['loader-base'] ? YUI.Env.mods['loader-base'].fn : null;
if (!loaderFn) { return; }
if (verbose) { console.log('Applying YUI load post-hook'); }
YUI.Env.mods['loader-base'].fn = function (Y) {
loaderFn.call(null, Y);
origGet = Y.Get._exec;
Y.Get._exec = function (data, url, cb) {
if (matchFn(url) || matchFn(path.resolve(url))) { //allow for relative paths as well
if (verbose) {
console.log('Transforming [' + url + ']');
}
try {
data = transformFn(data, url);
} catch (ex) {
console.error('Error transforming: ' + url + ' return original code');
console.error(ex.message || ex);
if (ex.stack) { console.error(ex.stack); }
}
}
return origGet.call(Y, data, url, cb);
};
return Y;
};
}
};
};
{
"name": "ryIstanbulIo",
"version": "0.0.1",
"description": "Yet another JS code coverage tool that computes statement, line, function and branch coverage with module loader hooks to transparently add coverage when running tests. Supports all JS coverage use cases including unit tests, server side functional tests and browser tests. Built for scale",
"keywords": [
"coverage",
"code coverage",
"JS code coverage",
"JS coverage"
],
"author": "Krishnan Anantheswaran <kananthmail-github@yahoo.com>",
"license": "BSD-3-Clause",
"contributors": [
"Reid Burke <me@reidburke.com>",
"Martin Cooper <mfncooper@gmail.com>",
"Dav Glass <davglass@gmail.com>",
"nowamasa <nowamasa@gmail.com>",
"Miller Medeiros @millermedeiros <contact@millermedeiros.com>",
"Daniel Perez Alvarez @unindented <unindented@gmail.com>",
"Mathias Bynens @mathiasbynens <mathias@qiwi.be>",
"Nathan Brown @nbrownus <nate@betable.com>",
"Brian Ng @existentialism <bng412@gmail.com>",
"John Morrison @jrgm",
"Tomaz Muraus @kami <tomaz+github@tomaz.me>",
"Joe @jhansche <jhansche@meetme.com>",
"Vojta Jina @vojtajina <vojta.jina@gmail.com>",
"Dmitry Shirokov @runk <deadrunk@gmail.com>",
"Chris Gladd @chrisgladd",
"Sergey Belov <peimei@ya.ru>",
"porneL @pornel <pornel@pornel.net>",
"@asifrc",
"Gergely Nemeth @gergelyke",
"@bixdeng",
"@mpderbec",
"@jxiaodev",
"Arpad Borsos @Swatinem <arpad.borsos@googlemail.com>",
"Ariya Hidayat @ariya",
"@markyen",
"Sam Saccone @samccone <sam@samx.it>",
"Jason Cheatham @jason0x43",
"@smikes",
"Yasyf Mohamedali @yasyf <yasyfm@gmail.com>",
"Fabio Crisci @piuccio <piuccio@gmail.com>",
"Ryan Roemer @ryan-roemer <ryan@loose-bits.com>",
"Douglas Christopher Wilson @dougwilson",
"Gustav Nikolaj @gustavnikolaj <gustavnikolaj@gmail.com>",
"Denis Sokolov @denis-sokolov <denis@sokolov.cc>",
"Yann Mainier @ymainier",
"Yiyu He @dead-horse <heyiyu.deadhorse@gmail.com>",
"Andrew Kelley @andrewrk <superjoe30@gmail.com>",
"Will LaBranche @wlabranche <will@labranche.io>",
"Mathieu Naouache @math-nao <math.nao@outlook.com>",
"Ron Korving @ronkorving",
"Rob McGuire-Dale @robatron <rob.mcguiredale@gmail.com>",
"Justin Johnson @booleangate",
"Juan Gabriel Jiménez @juangabreil <juangabreil@gmail.com>",
"Daniel Sabelnikov @dragn <dsabelnikov@gmail.com>",
"Tony Lukasavage @tonylukasavage <anthony.lukasavage@gmail.com>",
"Simon Ramsay @nexus-uw",
"Dominykas Blyžė @dominykas",
"Seth Pollack @sethpollack",
"Benjamin E. Coe @bcoe <ben@npmjs.com>",
"Yuren Ju <yurenju@gmail.com>",
"Aleksey Verkholantsev <alex-vee@yandex-team.ru>",
"Ed S <ejsanders@gmail.com>",
"Mordy Tikotzky <mordytk@gmail.com>",
"Haoliang Gao @popomore <sakura9515@gmail.com>",
"Roderick Hsiao @roderickhsiao",
"Nikita Gusakov @nkt",
"Alex Dunphy @alexdunphy <alexanderdunphy@gmail.com>",
"Artemy Tregubenko @arty-name <me@arty.name>",
"Arye Lukashevski @aryelu",
"@sterlinghw",
"Gord Tanner <gord@bithound.io>",
"Tom MacWright @tmcw <tom@macwright.org>",
"Kitson Kelly @kitsonk",
"@asa-git",
"@RoCat",
"Ian Page Hands @iphands <iphands@gmail.com>",
"Eddie Gurnee @pegurnee",
"Kevin Decker @kpdecker <kpdecker@gmail.com>",
"isaacs @isaacs <i@izs.me>",
"Steve Gray @steve-gray",
"Prayag Verma @pra85 <prayag.verma@gmail.com>",
"Abe Fehr @abejfehr <abe.fehr@gmail.com>",
"Brian Woodward @doowb <brian.woodward@gmail.com>",
"@Victorystick",
"@inversion",
"@JamesMGreene",
"@ChALkeR",
"@graingert"
],
"scripts": {
"pretest": "jshint index.js lib/ test/ && ./download-escodegen-browser.sh",
"test": "node --harmony test/run.js",
"posttest": "node ./lib/cli.js check-coverage --statements 95 --branches 80",
"docs": "npm install yuidocjs && node node_modules/yuidocjs/lib/cli.js ."
},
"bin": {
"istanbul": "./lib/cli.js"
},
"files": [
"index.js",
"lib/"
],
"dependencies": {
"abbrev": "1.0.x",
"async": "1.x",
"escodegen": "1.8.x",
"esprima": "2.7.x",
"glob": "^5.0.15",
"handlebars": "^4.0.1",
"js-yaml": "3.x",
"mkdirp": "0.5.x",
"nopt": "3.x",
"once": "1.x",
"resolve": "1.1.x",
"supports-color": "^3.1.0",
"which": "^1.1.1",
"wordwrap": "^1.0.0"
},
"devDependencies": {
"coveralls": "2.x",
"jshint": "^2.8.0",
"nodeunit": "0.9.x",
"requirejs": "2.x",
"rimraf": "^2.4.3"
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment