Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Allow parts of code to be skipped for the purposes of coverage reporting

1. Coverage can be explicitly skipped using comments. There is no automatic pattern match of expressions to determine if they should be skipped for coverage.
2. A coverage skip hint looks like `/* istanbul ignore <word>[non-word] [optional-docs] */`
3. For `if` conditions you can say `/* istanbul ignore if */` or `/* istanbul ignore else */` and that will end up ignoring whichever path was required to be ignored.
4. For all other cases, the Swiss army knife `/* istanbul ignore next */` may be used which skips the "next thing" in the source code
5. The "next" thing may be, among other things:
  * A JS statement (including assignments, ifs, loops, switches, functions) in which case all of the the statement is ignored for all forms of coverage.
  * A switch case statement, in which case the particular case is ignored for branch coverage and its contents ignored for all forms
  * A conditional inside a ternary expression in which case the branch is ignored
  * A part of a logical expression in which case that part of the expression is ignored for branch coverage
6. It is up to the caller to scope this as narrowly as possible. For example, if you have a source file that is wrapped in a function expression, adding `/* istanbul ignore next */` at the top of the file will ignore the whole file!

When some part of the JS is considered skipped, nothing actually happens in terms of changes to the instrumentation. Everything is calculated as though nothing was skipped - all that changes is that there is a `skip` attribute added to the metadata of the statement, function or branch as applicable.

Coverage reporting however takes the `skip` attribute into account and artificially increments counts, when 0 and skipped to pretend that the thing in question was covered. The HTML report shows the coverage after taking skips into account but at the same time colors the skipped statements with a gray color for easy visual scan.

The HTML and text summary reports shows ignore counts for completeness.
  • Loading branch information...
commit bc59131055abb04981ce5c2b934785bae7d81c1f 1 parent 3c92b79
Krishnan Anantheswaran authored
6 lib/command/index.js
View
@@ -18,13 +18,13 @@ Command.prototype = {
type: function () {
return this.constructor.TYPE;
},
- synopsis: function () {
+ synopsis: /* istanbul ignore next: base method */ function () {
return "the developer has not written a one-line summary of the " + this.type() + " command";
},
- usage: function () {
+ usage: /* istanbul ignore next: base method */ function () {
console.error("the developer has not provided a usage for the " + this.type() + " command");
},
- run: function (args, callback) {
+ run: /* istanbul ignore next: abstract method */ function (args, callback) {
return callback(new Error("run: must be overridden for the " + this.type() + " command"));
}
};
213 lib/instrumenter.js
View
@@ -13,11 +13,13 @@
crypto = isNode ? require('crypto') : null,
LEADER_WRAP = '(function () { ',
TRAILER_WRAP = '\n}());',
+ COMMENT_RE = /^\s*istanbul\s+ignore\s+(if|else|next)(?=\W|$)/,
astgen,
preconditions,
cond,
isArray = Array.isArray;
+ /* istanbul ignore if: untestable */
if (!isArray) {
isArray = function (thing) { return thing && Object.prototype.toString.call(thing) === '[object Array]'; };
}
@@ -30,6 +32,7 @@
'Array does not implement push': [].push,
'Array does not implement unshift': [].unshift
};
+ /* istanbul ignore next: untestable */
for (cond in preconditions) {
if (preconditions.hasOwnProperty(cond)) {
if (!preconditions[cond]) { throw new Error(cond); }
@@ -107,6 +110,7 @@
};
for (nodeType in SYNTAX) {
+ /* istanbul ignore else: has own property */
if (SYNTAX.hasOwnProperty(nodeType)) {
SYNTAX[nodeType] = { name: nodeType, children: SYNTAX[nodeType] };
}
@@ -123,8 +127,9 @@
sequence: function (one, two) { return { type: SYNTAX.SequenceExpression.name, expressions: [one, two] }; }
};
- function Walker(walkMap, scope, debug) {
+ function Walker(walkMap, preprocessor, scope, debug) {
this.walkMap = walkMap;
+ this.preprocessor = preprocessor;
this.scope = scope;
this.debug = debug;
if (this.debug) {
@@ -136,6 +141,8 @@
function defaultWalker(node, walker) {
var type = node.type,
+ preprocessor,
+ postprocessor,
children = SYNTAX[type].children,
// don't run generated nodes thru custom walks otherwise we will attempt to instrument the instrumentation code :)
applyCustomWalker = !!node.loc || node.type === SYNTAX.Program.name,
@@ -145,26 +152,37 @@
walkFnIndex,
childType,
childNode,
- ret = node,
+ ret,
childArray,
childElement,
pathElement,
assignNode,
isLast;
- if (node.walking) { throw new Error('Infinite regress: Custom walkers may NOT call walker.walk(node)'); }
+ /* istanbul ignore if: guard */
+ if (node.walking) { throw new Error('Infinite regress: Custom walkers may NOT call walker.apply(node)'); }
node.walking = true;
+
+ ret = walker.apply(node, walker.preprocessor);
+
+ preprocessor = ret.preprocessor;
+ if (preprocessor) {
+ delete ret.preprocessor;
+ ret = walker.apply(node, preprocessor);
+ }
+
if (isArray(walkerFn)) {
for (walkFnIndex = 0; walkFnIndex < walkerFn.length; walkFnIndex += 1) {
isLast = walkFnIndex === walkerFn.length - 1;
- ret = walker.walk(ret, walkerFn[walkFnIndex]) || ret;
+ ret = walker.apply(ret, walkerFn[walkFnIndex]);
+ /*istanbul ignore next: paranoid check */
if (ret.type !== type && !isLast) {
throw new Error('Only the last walker is allowed to change the node type: [type was: ' + type + ' ]');
}
}
} else {
if (walkerFn) {
- ret = walker.walk(node, walkerFn) || ret;
+ ret = walker.apply(node, walkerFn);
}
}
@@ -179,7 +197,7 @@
childElement = childNode[j];
pathElement.index = j;
if (childElement) {
- assignNode = walker.walk(childElement, null, pathElement) || childElement;
+ assignNode = walker.apply(childElement, null, pathElement);
if (isArray(assignNode.prepend)) {
pushAll(childArray, assignNode.prepend);
delete assignNode.prepend;
@@ -189,7 +207,8 @@
}
node[childType] = childArray;
} else {
- assignNode = walker.walk(childNode, null, pathElement) || childNode;
+ assignNode = walker.apply(childNode, null, pathElement);
+ /*istanbul ignore if: paranoid check */
if (isArray(assignNode.prepend)) {
throw new Error('Internal error: attempt to prepend statements in disallowed (non-array) context');
/* if this should be allowed, this is how to solve it
@@ -206,17 +225,24 @@
}
}
+ postprocessor = ret.postprocessor;
+ if (postprocessor) {
+ delete ret.postprocessor;
+ ret = walker.apply(ret, postprocessor);
+ }
+
delete node.walking;
+
return ret;
}
Walker.prototype = {
startWalk: function (node) {
this.path = [];
- this.walk(node);
+ this.apply(node);
},
- walk: function (node, walkFn, pathElement) {
+ apply: function (node, walkFn, pathElement) {
var ret, i, seq, prefix;
walkFn = walkFn || defaultWalker;
@@ -235,15 +261,15 @@
this.level -= 1;
console.log(prefix + 'Return (' + seq + '):' + node.type);
}
- return ret;
+ return ret || node;
},
startLineForNode: function (node) {
- return node && node.loc && node.loc.start ? node.loc.start.line : null;
+ return node && node.loc && node.loc.start ? node.loc.start.line : /* istanbul ignore next: guard */ null;
},
ancestor: function (n) {
- return this.path.length > n - 1 ? this.path[this.path.length - n] : null;
+ return this.path.length > n - 1 ? this.path[this.path.length - n] : /* istanbul ignore next: guard */ null;
},
parent: function () {
@@ -328,12 +354,13 @@
ThrowStatement: this.coverStatement,
TryStatement: this.coverStatement,
VariableDeclaration: this.coverStatement,
- IfStatement: [ this.ifBlockConverter, this.ifBranchInjector, this.coverStatement ],
+ IfStatement: [ this.ifBlockConverter, this.coverStatement, this.ifBranchInjector ],
ForStatement: [ this.skipInit, this.loopBlockConverter, this.coverStatement ],
ForInStatement: [ this.skipLeft, this.loopBlockConverter, this.coverStatement ],
WhileStatement: [ this.loopBlockConverter, this.coverStatement ],
DoWhileStatement: [ this.loopBlockConverter, this.coverStatement ],
- SwitchStatement: [ this.switchBranchInjector, this.coverStatement ],
+ SwitchStatement: [ this.coverStatement, this.switchBranchInjector ],
+ SwitchCase: [ this.switchCaseInjector ],
WithStatement: this.coverStatement,
FunctionDeclaration: [ this.coverFunction, this.coverStatement ],
FunctionExpression: this.coverFunction,
@@ -341,7 +368,7 @@
ConditionalExpression: this.conditionalBranchInjector,
LogicalExpression: this.logicalExpressionBranchInjector,
ObjectExpression: this.maybeAddType
- }, this, this.opts.walkDebug);
+ }, this.extractCurrentHint, this, this.opts.walkDebug);
//unit testing purposes only
if (this.opts.backdoor && this.opts.backdoor.omitTrackerSuffix) {
@@ -372,18 +399,60 @@
}
program = ESP.parse(code, {
loc: true,
- range: this.opts.preserveComments,
+ range: true,
tokens: this.opts.preserveComments,
- comment: this.opts.preserveComments
+ comment: true
});
if (this.opts.preserveComments) {
program = ESPGEN.attachComments(program, program.comments, program.tokens);
}
if (!this.opts.noAutoWrap) {
- program = { type: SYNTAX.Program.name, body: program.body[0].expression.callee.body.body };
+ program = {
+ type: SYNTAX.Program.name,
+ body: program.body[0].expression.callee.body.body,
+ comments: program.comments
+ };
}
return this.instrumentASTSync(program, filename, code);
},
+ filterHints: function (comments) {
+ var ret = [],
+ i,
+ comment,
+ groups;
+ if (!(comments && isArray(comments))) {
+ return ret;
+ }
+ for (i = 0; i < comments.length; i += 1) {
+ comment = comments[i];
+ /* istanbul ignore else: paranoid check */
+ if (comment && comment.value && comment.range && isArray(comment.range)) {
+ groups = String(comment.value).match(COMMENT_RE);
+ if (groups) {
+ ret.push({ type: groups[1], start: comment.range[0], end: comment.range[1] });
+ }
+ }
+ }
+ return ret;
+ },
+ extractCurrentHint: function (node) {
+ if (!node.range) { return; }
+ var i = this.currentState.lastHintPosition + 1,
+ hints = this.currentState.hints,
+ nodeStart = node.range[0],
+ hint;
+ this.currentState.currentHint = null;
+ while (i < hints.length) {
+ hint = hints[i];
+ if (hint.end < nodeStart) {
+ this.currentState.currentHint = hint;
+ this.currentState.lastHintPosition = i;
+ i += 1;
+ } else {
+ break;
+ }
+ }
+ },
/**
* synchronous instrumentation method that instruments an AST instead.
* @method instrumentASTSync
@@ -413,7 +482,11 @@
func: 0,
branch: 0,
variable: 0,
- statement: 0
+ statement: 0,
+ hints: this.filterHints(program.comments),
+ currentHint: null,
+ lastHintPosition: -1,
+ ignoring: 0
};
if (program.body && program.body.length > 0 && this.isUseStrictExpression(program.body[0])) {
//nuke it
@@ -481,14 +554,17 @@
obj = coverState.statementMap;
for (k in obj) {
+ /* istanbul ignore else: has own property */
if (obj.hasOwnProperty(k)) { fixer(obj[k]); }
}
obj = coverState.fnMap;
for (k in obj) {
+ /* istanbul ignore else: has own property */
if (obj.hasOwnProperty(k)) { fixer(obj[k].loc); }
}
obj = coverState.branchMap;
for (k in obj) {
+ /* istanbul ignore else: has own property */
if (obj.hasOwnProperty(k)) {
locations = obj[k].locations;
for (i = 0; i < locations.length; i += 1) {
@@ -535,6 +611,14 @@
return code;
},
+ startIgnore: function () {
+ this.currentState.ignoring += 1;
+ },
+
+ endIgnore: function () {
+ this.currentState.ignoring -= 1;
+ },
+
convertToBlock: function (node) {
if (!node) {
return { type: 'BlockStatement', body: [] };
@@ -555,8 +639,11 @@
},
statementName: function (location, initValue) {
+ var sName,
+ ignoring = !!this.currentState.ignoring;
+
+ location.skip = ignoring || undefined;
initValue = initValue || 0;
- var sName;
this.currentState.statement += 1;
sName = this.currentState.statement;
this.coverState.statementMap[sName] = location;
@@ -579,13 +666,30 @@
node.expression && node.expression.type === SYNTAX.Literal.name &&
node.expression.value === 'use strict';
},
+
+ maybeSkipNode: function (node, type) {
+ var alreadyIgnoring = !!this.currentState.ignoring,
+ hint = this.currentState.currentHint,
+ ignoreThis = !alreadyIgnoring && hint && hint.type === type;
+
+ if (ignoreThis) {
+ this.startIgnore();
+ node.postprocessor = this.endIgnore;
+ return true;
+ }
+ return false;
+ },
+
coverStatement: function (node, walker) {
var sName,
incrStatementCount,
grandParent;
+ this.maybeSkipNode(node, 'next');
+
if (this.isUseStrictExpression(node)) {
grandParent = walker.ancestor(2);
+ /* istanbul ignore else: difficult to test */
if (grandParent) {
if ((grandParent.node.type === SYNTAX.FunctionExpression.name ||
grandParent.node.type === SYNTAX.FunctionDeclaration.name) &&
@@ -619,21 +723,26 @@
functionName: function (node, line, location) {
this.currentState.func += 1;
var id = this.currentState.func,
+ ignoring = !!this.currentState.ignoring,
name = node.id ? node.id.name : '(anonymous_' + id + ')';
- this.coverState.fnMap[id] = { name: name, line: line, loc: location };
+ this.coverState.fnMap[id] = { name: name, line: line, loc: location, skip: ignoring || undefined };
this.coverState.f[id] = 0;
return id;
},
coverFunction: function (node, walker) {
- var id = this.functionName(node, walker.startLineForNode(node), {
- start: node.loc.start,
- end: { line: node.body.loc.start.line, column: node.body.loc.start.column }
- }),
+ var id,
body = node.body,
blockBody = body.body,
popped;
+ this.maybeSkipNode(node, 'next');
+
+ id = this.functionName(node, walker.startLineForNode(node), {
+ start: node.loc.start,
+ end: { line: node.body.loc.start.line, column: node.body.loc.start.column }
+ });
+
if (blockBody.length > 0 && this.isUseStrictExpression(blockBody[0])) {
popped = blockBody.shift();
}
@@ -656,12 +765,14 @@
var bName,
paths = [],
locations = [],
- i;
+ i,
+ ignoring = !!this.currentState.ignoring;
this.currentState.branch += 1;
bName = this.currentState.branch;
for (i = 0; i < pathLocations.length; i += 1) {
- paths.push(0);
+ pathLocations[i].skip = pathLocations[i].skip || ignoring || undefined;
locations.push(pathLocations[i]);
+ paths.push(0);
}
this.coverState.b[bName] = paths;
this.coverState.branchMap[bName] = { line: startLine, type: type, locations: locations };
@@ -692,18 +803,29 @@
},
ifBranchInjector: function (node, walker) {
- var line = node.loc.start.line,
+ var alreadyIgnoring = !!this.currentState.ignoring,
+ hint = this.currentState.currentHint,
+ ignoreThen = !alreadyIgnoring && hint && hint.type === 'if',
+ ignoreElse = !alreadyIgnoring && hint && hint.type === 'else',
+ line = node.loc.start.line,
col = node.loc.start.column,
start = { line: line, column: col },
end = { line: line, column: col },
bName = this.branchName('if', walker.startLineForNode(node), [
- { start: start, end: end },
- { start: start, end: end }
+ { start: start, end: end, skip: ignoreThen || undefined },
+ { start: start, end: end, skip: ignoreElse || undefined }
]),
thenBody = node.consequent.body,
- elseBody = node.alternate.body;
+ elseBody = node.alternate.body,
+ child;
thenBody.unshift(astgen.statement(this.branchIncrementExprAst(bName, 0)));
elseBody.unshift(astgen.statement(this.branchIncrementExprAst(bName, 1)));
+ if (ignoreThen) { child = node.consequent; child.preprocessor = this.startIgnore; child.postprocessor = this.endIgnore; }
+ if (ignoreElse) { child = node.alternate; child.preprocessor = this.startIgnore; child.postprocessor = this.endIgnore; }
+ },
+
+ branchLocationFor: function (name, index) {
+ return this.coverState.branchMap[name].locations[index];
},
switchBranchInjector: function (node, walker) {
@@ -716,19 +838,45 @@
}
bName = this.branchName('switch', walker.startLineForNode(node), this.locationsForNodes(cases));
for (i = 0; i < cases.length; i += 1) {
+ cases[i].branchLocation = this.branchLocationFor(bName, i);
cases[i].consequent.unshift(astgen.statement(this.branchIncrementExprAst(bName, i)));
}
},
+ switchCaseInjector: function (node) {
+ var location = node.branchLocation;
+ delete node.branchLocation;
+ if (this.maybeSkipNode(node, 'next')) {
+ location.skip = true;
+ }
+ },
+
conditionalBranchInjector: function (node, walker) {
var bName = this.branchName('cond-expr', walker.startLineForNode(node), this.locationsForNodes([ node.consequent, node.alternate ])),
ast1 = this.branchIncrementExprAst(bName, 0),
ast2 = this.branchIncrementExprAst(bName, 1);
+ node.consequent.preprocessor = this.maybeAddSkip(this.branchLocationFor(bName, 0));
+ node.alternate.preprocessor = this.maybeAddSkip(this.branchLocationFor(bName, 1));
node.consequent = astgen.sequence(ast1, node.consequent);
node.alternate = astgen.sequence(ast2, node.alternate);
},
+ maybeAddSkip: function (branchLocation) {
+ return function (node) {
+ var alreadyIgnoring = !!this.currentState.ignoring,
+ hint = this.currentState.currentHint,
+ ignoreThis = !alreadyIgnoring && hint && hint.type === 'next';
+ if (ignoreThis) {
+ this.startIgnore();
+ node.postprocessor = this.endIgnore;
+ }
+ if (ignoreThis || alreadyIgnoring) {
+ branchLocation.skip = true;
+ }
+ };
+ },
+
logicalExpressionBranchInjector: function (node, walker) {
var parent = walker.parent(),
leaves = [],
@@ -736,6 +884,8 @@
tuple,
i;
+ this.maybeSkipNode(node, 'next');
+
if (parent && parent.node.type === SYNTAX.LogicalExpression.name) {
//already covered
return;
@@ -749,6 +899,7 @@
for (i = 0; i < leaves.length; i += 1) {
tuple = leaves[i];
tuple.parent[tuple.property] = astgen.sequence(this.branchIncrementExprAst(bName, i), tuple.node);
+ tuple.node.preprocessor = this.maybeAddSkip(this.branchLocationFor(bName, i));
}
},
42 lib/object-utils.js
View
@@ -58,6 +58,7 @@
var line = statementMap[st].start.line,
count = statements[st],
prevVal = lineMap[line];
+ if (count === 0 && statementMap[st].skip) { count = 1; }
if (typeof prevVal === 'undefined' || prevVal < count) {
lineMap[line] = count;
}
@@ -98,15 +99,21 @@
}
}
- function computeSimpleTotals(fileCoverage, property) {
+ function computeSimpleTotals(fileCoverage, property, mapProperty) {
var stats = fileCoverage[property],
- ret = { total: 0, covered: 0 };
+ map = mapProperty ? fileCoverage[mapProperty] : null,
+ ret = { total: 0, covered: 0, skipped: 0 };
Object.keys(stats).forEach(function (key) {
+ var covered = !!stats[key],
+ skipped = map && map[key].skip;
ret.total += 1;
- if (stats[key]) {
+ if (covered || skipped) {
ret.covered += 1;
}
+ if (!covered && skipped) {
+ ret.skipped += 1;
+ }
});
ret.pct = percent(ret.covered, ret.total);
return ret;
@@ -114,13 +121,26 @@
function computeBranchTotals(fileCoverage) {
var stats = fileCoverage.b,
- ret = { total: 0, covered: 0 };
+ branchMap = fileCoverage.branchMap,
+ ret = { total: 0, covered: 0, skipped: 0 };
Object.keys(stats).forEach(function (key) {
var branches = stats[key],
- covered = branches.filter(function (num) { return num > 0; });
+ map = branchMap[key],
+ covered,
+ skipped,
+ i;
+ for (i = 0; i < branches.length; i += 1) {
+ covered = branches[i] > 0;
+ skipped = map.locations && map.locations[i] && map.locations[i].skip;
+ if (covered || skipped) {
+ ret.covered += 1;
+ }
+ if (!covered && skipped) {
+ ret.skipped += 1;
+ }
+ }
ret.total += branches.length;
- ret.covered += covered.length;
});
ret.pct = percent(ret.covered, ret.total);
return ret;
@@ -153,21 +173,25 @@
lines: {
total: 0,
covered: 0,
+ skipped: 0,
pct: 'Unknown'
},
statements: {
total: 0,
covered: 0,
+ skipped: 0,
pct: 'Unknown'
},
functions: {
total: 0,
covered: 0,
+ skipped: 0,
pct: 'Unknown'
},
branches: {
total: 0,
covered: 0,
+ skipped: 0,
pct: 'Unknown'
}
};
@@ -185,8 +209,8 @@
var ret = blankSummary();
addDerivedInfoForFile(fileCoverage);
ret.lines = computeSimpleTotals(fileCoverage, 'l');
- ret.functions = computeSimpleTotals(fileCoverage, 'f');
- ret.statements = computeSimpleTotals(fileCoverage, 's');
+ ret.functions = computeSimpleTotals(fileCoverage, 'f', 'fnMap');
+ ret.statements = computeSimpleTotals(fileCoverage, 's', 'statementMap');
ret.branches = computeBranchTotals(fileCoverage);
return ret;
}
@@ -242,6 +266,7 @@
keys.forEach(function (key) {
ret[key].total += obj[key].total;
ret[key].covered += obj[key].covered;
+ ret[key].skipped += obj[key].skipped;
});
}
};
@@ -332,6 +357,7 @@
toYUICoverage: toYUICoverage
};
+ /* istanbul ignore else: windows */
if (isNode) {
module.exports = exportables;
} else {
30 lib/report/html.js
View
@@ -89,6 +89,24 @@ handlebars.registerHelper('show_picture', function (opts) {
}
});
+handlebars.registerHelper('show_ignores', function (metrics) {
+ var statements = metrics.statements.skipped,
+ functions = metrics.functions.skipped,
+ branches = metrics.branches.skipped,
+ result;
+
+ if (statements === 0 && functions === 0 && branches === 0) {
+ return '<span class="ignore-none">none</span>';
+ }
+
+ result = [];
+ if (statements >0) { result.push(statements === 1 ? '1 statement': statements + ' statements'); }
+ if (functions >0) { result.push(functions === 1 ? '1 function' : functions + ' functions'); }
+ if (branches >0) { result.push(branches === 1 ? '1 branch' : branches + ' branches'); }
+
+ return result.join(', ');
+});
+
handlebars.registerHelper('show_lines', function (opts) {
var maxLines = Number(opts.fn(this)),
i,
@@ -173,7 +191,7 @@ function annotateStatements(fileCoverage, structuredText) {
endCol = meta.end.column + 1,
startLine = meta.start.line,
endLine = meta.end.line,
- openSpan = lt + 'span class="cstat-no"' + title('statement not covered') + gt,
+ openSpan = lt + 'span class="' + (meta.skip ? 'cstat-skip' : 'cstat-no') + '"' + title('statement not covered') + gt,
closeSpan = lt + '/span' + gt,
text;
@@ -204,7 +222,7 @@ function annotateFunctions(fileCoverage, structuredText) {
endCol = meta.loc.end.column + 1,
startLine = meta.loc.start.line,
endLine = meta.loc.end.line,
- openSpan = lt + 'span class="fstat-no"' + title('function not covered') + gt,
+ openSpan = lt + 'span class="' + (meta.skip ? 'fstat-skip' : 'fstat-no') + '"' + title('function not covered') + gt,
closeSpan = lt + '/span' + gt,
text;
@@ -252,7 +270,7 @@ function annotateBranches(fileCoverage, structuredText) {
endCol = meta.end.column + 1;
startLine = meta.start.line;
endLine = meta.end.line;
- openSpan = lt + 'span class="branch-' + i + ' cbranch-no"' + title('branch not covered') + gt;
+ openSpan = lt + 'span class="branch-' + i + ' ' + (meta.skip ? 'cbranch-skip' : 'cbranch-no') + '"' + title('branch not covered') + gt;
closeSpan = lt + '/span' + gt;
if (count === 0) { //skip branches taken
@@ -262,8 +280,8 @@ function annotateBranches(fileCoverage, structuredText) {
}
text = structuredText[startLine].text;
if (branchMeta[branchName].type === 'if') { // and 'if' is a special case since the else branch might not be visible, being non-existent
- text.insertAt(startCol, lt + 'span class="missing-if-branch"' +
- title((i === 0 ? 'if' : 'else') + ' path not taken"') + gt +
+ text.insertAt(startCol, lt + 'span class="' + (meta.skip ? 'skip-if-branch' : 'missing-if-branch') + '"' +
+ title((i === 0 ? 'if' : 'else') + ' path not taken') + gt +
(i === 0 ? 'I' : 'E') + lt + '/span' + gt, true, false);
} else {
text.wrap(startCol,
@@ -355,7 +373,7 @@ Report.mix(HtmlReport, {
sourceStore = opts.sourceStore,
templateData = opts.templateData,
sourceText = fileCoverage.code && Array.isArray(fileCoverage.code) ?
- fileCoverage.code.join('\n') + '\n' : sourceStore.get(fileCoverage.path),
+ fileCoverage.code.join('\n') + '\n' : sourceStore.get(fileCoverage.path),
code = sourceText.split(/\r?\n/),
count = 0,
structured = code.map(function (str) { count += 1; return { line: count, covered: null, text: new InsertionText(str, true) }; }),
21 lib/report/templates/head.txt
View
@@ -100,6 +100,11 @@
.cstat-no { background: #fc8c84; color: #111; }
.fstat-no { background: #ffc520; color: #111 !important; }
.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; }
+
.missing-if-branch {
display: inline-block;
margin-right: 10px;
@@ -107,9 +112,18 @@
padding: 0 4px;
background: black;
color: yellow;
- xtext-decoration: line-through;
}
- .missing-if-branch .typ {
+
+ .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;
}
@@ -176,6 +190,8 @@
margin: 0 !important;
}
.com { color: #999 !important; }
+ .ignore-none { color: #999; font-weight: normal; }
+
</style>
</head>
<body>
@@ -194,6 +210,7 @@
{{#with metrics.lines}}
Lines: <span class="metric">{{pct}}% <small>({{covered}} / {{total}})</small></span> &nbsp;&nbsp;&nbsp;&nbsp;
{{/with}}
+ Ignored: <span class="metric">{{#show_ignores metrics}}{{/show_ignores}}</span> &nbsp;&nbsp;&nbsp;&nbsp;
</h2>
{{{pathHtml}}}
</div>
11 lib/report/text-summary.js
View
@@ -34,10 +34,17 @@ function TextSummaryReport(opts) {
TextSummaryReport.TYPE = 'text-summary';
function lineForKey(summary, key) {
- var metrics = summary[key];
+ var metrics = summary[key],
+ skipped,
+ result;
key = key.substring(0, 1).toUpperCase() + key.substring(1);
if (key.length < 12) { key += ' '.substring(0, 12 - key.length); }
- return [ key , ':', metrics.pct + '%', '(', metrics.covered + '/' + metrics.total, ')'].join(' ');
+ result = [ key , ':', metrics.pct + '%', '(', metrics.covered + '/' + metrics.total, ')'].join(' ');
+ skipped = metrics.skipped;
+ if (skipped > 0) {
+ result += ', ' + skipped + ' ignored';
+ }
+ return result;
}
Report.mix(TextSummaryReport, {
2  lib/store/tmp.js
View
@@ -11,7 +11,7 @@ var util = require('util'),
Store = require('./index');
function makeTempDir() {
- var dir = path.join(os.tmpDir ? os.tmpDir() : (process.env.TMPDIR || '/tmp'), 'ts' + new Date().getTime());
+ var dir = path.join(os.tmpDir ? os.tmpDir() : /* istanbul ignore next */ (process.env.TMPDIR || '/tmp'), 'ts' + new Date().getTime());
mkdirp.sync(dir);
return dir;
}
16 lib/util/writer.js
View
@@ -17,7 +17,9 @@ function ContentWriter() {
}
ContentWriter.prototype = {
- write: function (/* str */) { throw new Error('write: must be overridden'); },
+ write: /* istanbul ignore next: abstract method */ function (/* str */) {
+ throw new Error('write: must be overridden');
+ },
println: function (str) { this.write(str); this.write('\n'); }
};
@@ -34,19 +36,25 @@ extend(Writer, {
* @param file the name of the file to write
* @param callback the callback that is called as `callback(contentWriter)`
*/
- writeFile: function (/* file, callback */) { throw new Error('writeFile: must be overridden'); },
+ writeFile: /* istanbul ignore next: abstract method */ function (/* file, callback */) {
+ throw new Error('writeFile: must be overridden');
+ },
/**
* copies a file from source to destination
* @param source the file to copy, found on the file system
* @param dest the destination path
*/
- copyFile: function (/* source, dest */) { throw new Error('copyFile: must be overridden'); },
+ 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.
*/
- done: function () { throw new Error('done: must be overridden'); }
+ done: /* istanbul ignore next: abstract method */ function () {
+ throw new Error('done: must be overridden');
+ }
});
module.exports = {
5 test/helper.js
View
@@ -57,6 +57,11 @@ Verifier.prototype = {
return global[this.coverageVariable];
},
+ getFileCoverage: function () {
+ var cov = this.getCoverage();
+ return cov[Object.keys(cov)[0]];
+ },
+
verifyError: function (test) {
test.ok(this.err && typeof this.err === 'object', 'Error should be an object');
},
166 test/instrumentation/test-if-with-hints.js
View
@@ -0,0 +1,166 @@
+/*jslint nomen: true */
+var helper = require('../helper'),
+ code,
+ verifier;
+
+/*jshint maxlen: 500 */
+module.exports = {
+ "with a simple if": {
+ "as a statement": {
+ setUp: function (cb) {
+ code = [
+ 'output = -1;',
+ '/* hint */',
+ 'if (args[0] > args [1])',
+ ' output = args[0];'
+ ];
+ cb(null);
+ },
+ "should cover then path": function (test) {
+ code[1] = '/* istanbul ignore else */';
+ verifier = helper.verifier(__filename, code);
+ verifier.verify(test, [ 20, 10 ], 20, { lines: { 1: 1, 3: 1, 4: 1 }, branches: { '1': [ 1, 0 ] }, functions: {}, statements: { '1': 1, '2': 1, '3': 1 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.branchMap[1].locations[1].skip);
+ test.done();
+ },
+ "should cover else path": function (test) {
+ code[1] = '/* istanbul ignore if */';
+ verifier = helper.verifier(__filename, code);
+ verifier.verify(test, [ 10, 20 ], -1, { lines: { 1: 1, 3: 1, 4: 1 }, branches: { '1': [ 0, 1 ] }, functions: {}, statements: { '1': 1, '2': 1, '3': 0 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.branchMap[1].locations[0].skip);
+ test.equal(true, cov.statementMap[3].skip);
+ test.done();
+ }
+ },
+ "as a block": {
+ setUp: function (cb) {
+ code = [
+ 'output = -1;',
+ '/* hint */',
+ 'if (args[0] > args [1]) {',
+ ' output = args[0];',
+ '}'
+ ];
+ cb();
+ },
+ "should cover then path": function (test) {
+ code[1] = '/* istanbul ignore else */';
+ verifier = helper.verifier(__filename, code);
+ verifier.verify(test, [ 20, 10 ], 20, { lines: { 1: 1, 3: 1, 4: 1 }, branches: { '1': [ 1, 0 ] }, functions: {}, statements: { '1': 1, '2': 1, '3': 1 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.branchMap[1].locations[1].skip);
+ test.done();
+ },
+ "should cover else path": function (test) {
+ code[1] = '/* istanbul ignore if */';
+ verifier = helper.verifier(__filename, code);
+ verifier.verify(test, [ 10, 20 ], -1, { lines: { 1: 1, 3: 1, 4: 1 }, branches: { '1': [ 0, 1 ] }, functions: {}, statements: { '1': 1, '2': 1, '3': 0 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.branchMap[1].locations[0].skip);
+ test.equal(true, cov.statementMap[3].skip);
+ test.done();
+ }
+ },
+ "on a single line": {
+ "as statement": {
+ setUp: function (cb) {
+ code = [
+ 'output = -1;',
+ '/* hint */',
+ 'if (args[0] > args [1]) output = args[0];'
+ ];
+ cb();
+ },
+ "should cover then path": function (test) {
+ code[1] = '/* istanbul ignore else */';
+ verifier = helper.verifier(__filename, code);
+ verifier.verify(test, [ 20, 10 ], 20, { lines: { 1: 1, 3: 1 }, branches: { '1': [ 1, 0 ] }, functions: {}, statements: { '1': 1, '2': 1, '3': 1 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.branchMap[1].locations[1].skip);
+ test.done();
+ },
+ "should cover else path": function (test) {
+ code[1] = '/* istanbul ignore if */';
+ verifier = helper.verifier(__filename, code);
+ verifier.verify(test, [ 10, 20 ], -1, { lines: { 1: 1, 3: 1 }, branches: { '1': [ 0, 1 ] }, functions: {}, statements: { '1': 1, '2': 1, '3': 0 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.branchMap[1].locations[0].skip);
+ test.equal(true, cov.statementMap[3].skip);
+ test.done();
+ }
+ },
+ "as block": {
+ setUp: function (cb) {
+ code = [
+ 'output = -1;',
+ '/* hint */',
+ 'if (args[0] > args [1]) { output = args[0]; }'
+ ];
+ cb();
+ },
+ "should cover then path": function (test) {
+ code[1] = '/* istanbul ignore else */';
+ verifier = helper.verifier(__filename, code);
+ verifier.verify(test, [ 20, 10 ], 20, { lines: { 1: 1, 3: 1 }, branches: { '1': [ 1, 0 ] }, functions: {}, statements: { '1': 1, '2': 1, '3': 1 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.branchMap[1].locations[1].skip);
+ test.done();
+ },
+ "should cover else path": function (test) {
+ code[1] = '/* istanbul ignore if */';
+ verifier = helper.verifier(__filename, code);
+ verifier.verify(test, [ 10, 20 ], -1, { lines: { 1: 1, 3: 1 }, branches: { '1': [ 0, 1 ] }, functions: {}, statements: { '1': 1, '2': 1, '3': 0 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.branchMap[1].locations[0].skip);
+ test.equal(true, cov.statementMap[3].skip);
+ test.done();
+ },
+ "should skip if statement completely": function (test) {
+ code[1] = '/* istanbul ignore next */';
+ verifier = helper.verifier(__filename, code);
+ verifier.verify(test, [ 10, 20 ], -1, { lines: { 1: 1, 3: 1 }, branches: { '1': [ 0, 1 ] }, functions: {}, statements: { '1': 1, '2': 1, '3': 0 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.branchMap[1].locations[0].skip);
+ test.equal(true, cov.branchMap[1].locations[1].skip);
+ test.equal(true, cov.statementMap[3].skip);
+ test.done();
+ }
+ }
+ }
+ },
+ "with a simple if-else": {
+ "as a statement": {
+ setUp: function (cb) {
+ code = [
+ '// hint',
+ 'if (args[0] > args [1])',
+ ' output = args[0];',
+ 'else',
+ ' output = args[1];'
+ ];
+ cb(null);
+ },
+ "should cover then path": function (test) {
+ code[0] = '// istanbul ignore else';
+ verifier = helper.verifier(__filename, code);
+ verifier.verify(test, [ 20, 10 ], 20, { lines: { 2: 1, 3: 1, 5: 1 }, branches: { '1': [ 1, 0 ] }, functions: {}, statements: { '1': 1, '2': 1, '3': 0 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.branchMap[1].locations[1].skip);
+ test.equal(true, cov.statementMap[3].skip);
+ test.done();
+ },
+ "should cover else path": function (test) {
+ code[0] = '// istanbul ignore if ';
+ verifier = helper.verifier(__filename, code);
+ verifier.verify(test, [ 10, 20 ], 20, { lines: { 2: 1, 3: 1, 5: 1 }, branches: { '1': [ 0, 1 ] }, functions: {}, statements: { '1': 1, '2': 0, '3': 1 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.branchMap[1].locations[0].skip);
+ test.equal(true, cov.statementMap[2].skip);
+ test.done();
+ }
+ }
+ }
+};
+
226 test/instrumentation/test-statement-with-hints.js
View
@@ -0,0 +1,226 @@
+/*jslint nomen: true */
+var helper = require('../helper'),
+ code,
+ verifier;
+
+module.exports = {
+ "with a simple statement": {
+ setUp: function (cb) {
+ code = [
+ '/* istanbul ignore next */',
+ 'var x = args[0] > 5 ? args[0] : "undef";',
+ 'output = x;'
+ ];
+ verifier = helper.verifier(__filename, code);
+ cb();
+ },
+
+ "should cover line and one branch": function (test) {
+ verifier.verify(test, [ 10 ], 10, { lines: { 2: 1, 3: 1 }, branches: { 1: [1, 0 ]}, functions: {}, statements: { 1: 1, 2: 1 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.statementMap[1].skip);
+ test.equal(true, cov.branchMap[1].locations[0].skip);
+ test.equal(true, cov.branchMap[1].locations[1].skip);
+ test.done();
+ }
+ },
+ "with a function declaration": {
+ setUp: function (cb) {
+ code = [
+ '/* istanbul ignore next */',
+ 'function foo(x) { return x; }',
+ 'output = args[0];'
+ ];
+ verifier = helper.verifier(__filename, code);
+ cb();
+ },
+
+ "should not cover function call": function (test) {
+ verifier.verify(test, [ 10 ], 10, { lines: { 2: 1, 3: 1 }, branches: {}, functions: { 1: 0 }, statements: { 1: 1, 2: 0, 3: 1 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.statementMap[1].skip);
+ test.equal(true, cov.statementMap[2].skip);
+ test.equal(true, cov.fnMap[1].skip);
+ test.done();
+ }
+ },
+ "with a function expression": {
+ setUp: function (cb) {
+ code = [
+ '/* istanbul ignore next */',
+ '(function () { output = args[0]; })();'
+ ];
+ verifier = helper.verifier(__filename, code);
+ cb();
+ },
+
+ "should cover function call": function (test) {
+ verifier.verify(test, [ 10 ], 10, { lines: { 2: 1 }, branches: {}, functions: { 1: 1 }, statements: { 1: 1, 2: 1 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.statementMap[1].skip);
+ test.equal(true, cov.statementMap[2].skip);
+ test.equal(true, cov.fnMap[1].skip);
+ test.done();
+ }
+ },
+ "with a disabled switch statement": {
+ setUp: function (cb) {
+ code = [
+ '/* istanbul ignore next */',
+ 'switch (args[0]) {',
+ 'case "1": output = 2; break;',
+ 'default: output = 1;',
+ '}'
+ ];
+ verifier = helper.verifier(__filename, code);
+ cb();
+ },
+
+ "should ignore all branches": function (test) {
+ verifier.verify(test, [ "1" ], 2, { lines: { 2: 1, 3: 1, 4: 1 }, branches: { 1: [ 1, 0 ]},
+ functions: {}, statements: { 1: 1, 2: 1, 3: 1, 4: 0 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.statementMap[1].skip);
+ test.equal(true, cov.statementMap[2].skip);
+ test.equal(true, cov.statementMap[3].skip);
+ test.equal(true, cov.statementMap[4].skip);
+ test.equal(true, cov.branchMap[1].locations[0].skip);
+ test.equal(true, cov.branchMap[1].locations[1].skip);
+ test.done();
+ }
+ },
+ "with a disabled case statement": {
+ setUp: function (cb) {
+ code = [
+ 'switch (args[0]) {',
+ '/* istanbul ignore next */',
+ 'case "1": output = 2; break;',
+ 'default: output = 1;',
+ '}'
+ ];
+ verifier = helper.verifier(__filename, code);
+ cb();
+ },
+
+ "should ignore specific case": function (test) {
+ verifier.verify(test, [ "2" ], 1, { lines: { 1: 1, 3: 1, 4: 1 }, branches: { 1: [ 0, 1 ]},
+ functions: {}, statements: { 1: 1, 2: 0, 3: 0, 4: 1 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.branchMap[1].locations[0].skip);
+ test.equal(true, cov.statementMap[2].skip);
+ test.equal(true, cov.statementMap[3].skip);
+ test.done();
+ }
+ },
+
+ "with disabled conditional statement": {
+ setUp: function (cb) {
+ code = [
+ '/* istanbul ignore next */',
+ 'output = args[0] === 1 ? 1: 0;'
+ ];
+ verifier = helper.verifier(__filename, code);
+ cb();
+ },
+
+ "should ignore conditions": function (test) {
+ verifier.verify(test, [ 2 ], 0, { lines: { 2: 1 }, branches: { 1: [ 0, 1 ]},
+ functions: {}, statements: { 1: 1 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.branchMap[1].locations[0].skip);
+ test.equal(true, cov.branchMap[1].locations[1].skip);
+ test.equal(true, cov.statementMap[1].skip);
+ test.done();
+ }
+
+ },
+ "with disabled condition": {
+ setUp: function (cb) {
+ code = [
+ 'output = args[0] === 1 ? /* istanbul ignore next */ 1 : 0;'
+ ];
+ verifier = helper.verifier(__filename, code);
+ cb();
+ },
+
+ "should ignore conditions": function (test) {
+ verifier.verify(test, [ 2 ], 0, { lines: { 1: 1 }, branches: { 1: [ 0, 1 ]},
+ functions: {}, statements: { 1: 1 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.branchMap[1].locations[0].skip);
+ test.equal(undefined, cov.branchMap[1].locations[1].skip);
+ test.equal(undefined, cov.statementMap[1].skip);
+ test.done();
+ }
+ },
+
+ "with a simple logical expression": {
+ setUp: function (cb) {
+ code = [
+ 'if (args[0] === 1 || /* istanbul ignore next */ args[0] === 2 ) {',
+ ' output = args[0] + 10;',
+ '} else {',
+ ' output = 20;',
+ '}'
+ ];
+ verifier = helper.verifier(__filename, code);
+ cb();
+ },
+
+ "should ignore conditions": function (test) {
+ verifier.verify(test, [ 1 ], 11, { lines: { 1: 1, 2: 1, 4: 0 }, branches: { 1: [ 1, 0 ], 2: [ 1, 0 ] },
+ functions: {}, statements: { 1: 1, 2: 1, 3: 0 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(true, cov.branchMap[2].locations[1].skip);
+ test.done();
+ }
+ },
+
+ "with a slightly complicated logical expression": {
+ setUp: function (cb) {
+ code = [
+ 'if (args[0] === 1 || /* istanbul ignore next */ (args[0] === 2 || args[0] === 3)) {',
+ ' output = args[0] + 10;',
+ '} else {',
+ ' output = 20;',
+ '}'
+ ];
+ verifier = helper.verifier(__filename, code);
+ cb();
+ },
+
+ "should ignore conditions": function (test) {
+ verifier.verify(test, [ 1 ], 11, { lines: { 1: 1, 2: 1, 4: 0 }, branches: { 1: [ 1, 0 ], 2: [ 1, 0, 0 ] },
+ functions: {}, statements: { 1: 1, 2: 1, 3: 0 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(undefined, cov.branchMap[2].locations[0].skip);
+ test.equal(true, cov.branchMap[2].locations[1].skip);
+ test.equal(true, cov.branchMap[2].locations[2].skip);
+ test.done();
+ }
+ },
+
+ "with a complicated logical expression involving implied operator precedence": {
+ setUp: function (cb) {
+ code = [
+ 'if (args[0] === 1 || /* istanbul ignore next */ args[0] === 2 && args[1] === 2) {',
+ ' output = args[0] + 10;',
+ '} else {',
+ ' output = 20;',
+ '}'
+ ];
+ verifier = helper.verifier(__filename, code);
+ cb();
+ },
+
+ "should ignore conditions": function (test) {
+ verifier.verify(test, [ 1, 1 ], 11, { lines: { 1: 1, 2: 1, 4: 0 }, branches: { 1: [ 1, 0 ], 2: [ 1, 0, 0 ] },
+ functions: {}, statements: { 1: 1, 2: 1, 3: 0 } });
+ var cov = verifier.getFileCoverage();
+ test.equal(undefined, cov.branchMap[2].locations[0].skip);
+ test.equal(true, cov.branchMap[2].locations[1].skip);
+ test.equal(true, cov.branchMap[2].locations[2].skip);
+ test.done();
+ }
+ }
+};
48 test/other/test-object-utils.js
View
@@ -116,58 +116,58 @@ module.exports = {
},
"should calculate correct summary": function (test) {
var ret = utils.summarizeFileCoverage(it.foo);
- test.deepEqual({ total: 4, covered: 3, pct: 75 }, ret.lines);
- test.deepEqual({ total: 5, covered: 4, pct: 80 }, ret.statements);
- test.deepEqual({ total: 2, covered: 1, pct: 50 }, ret.functions);
- test.deepEqual({ total: 5, covered: 2, pct: 40 }, ret.branches);
+ test.deepEqual({ total: 4, covered: 3, pct: 75, skipped: 0 }, ret.lines);
+ test.deepEqual({ total: 5, covered: 4, pct: 80, skipped: 0 }, ret.statements);
+ test.deepEqual({ total: 2, covered: 1, pct: 50, skipped: 0 }, ret.functions);
+ test.deepEqual({ total: 5, covered: 2, pct: 40, skipped: 0 }, ret.branches);
test.done();
},
"should return a pct of 100 when nothing is available": function (test) {
it.foo.b = {};
var ret = utils.summarizeFileCoverage(it.foo);
- test.deepEqual({ total: 4, covered: 3, pct: 75 }, ret.lines);
- test.deepEqual({ total: 5, covered: 4, pct: 80 }, ret.statements);
- test.deepEqual({ total: 2, covered: 1, pct: 50 }, ret.functions);
- test.deepEqual({ total: 0, covered: 0, pct: 100 }, ret.branches);
+ test.deepEqual({ total: 4, covered: 3, pct: 75, skipped: 0 }, ret.lines);
+ test.deepEqual({ total: 5, covered: 4, pct: 80, skipped: 0 }, ret.statements);
+ test.deepEqual({ total: 2, covered: 1, pct: 50, skipped: 0 }, ret.functions);
+ test.deepEqual({ total: 0, covered: 0, pct: 100, skipped: 0 }, ret.branches);
test.done();
},
"should merge summary correctly": function (test) {
var s1 = utils.summarizeFileCoverage(it.foo),
s2 = utils.summarizeFileCoverage(it2.foo),
ret = utils.mergeSummaryObjects(s1, s2);
- test.deepEqual({ total: 8, covered: 6, pct: 75 }, ret.lines);
- test.deepEqual({ total: 10, covered: 8, pct: 80 }, ret.statements);
- test.deepEqual({ total: 4, covered: 2, pct: 50 }, ret.functions);
- test.deepEqual({ total: 10, covered: 4, pct: 40 }, ret.branches);
+ test.deepEqual({ total: 8, covered: 6, pct: 75, skipped: 0 }, ret.lines);
+ test.deepEqual({ total: 10, covered: 8, pct: 80, skipped: 0 }, ret.statements);
+ test.deepEqual({ total: 4, covered: 2, pct: 50, skipped: 0 }, ret.functions);
+ test.deepEqual({ total: 10, covered: 4, pct: 40, skipped: 0 }, ret.branches);
test.done();
},
"should merge summary correctly in one call": function (test) {
var coverage = { foo: it.foo, 'bar': it2.foo },
ret = utils.summarizeCoverage(coverage);
- test.deepEqual({ total: 8, covered: 6, pct: 75 }, ret.lines);
- test.deepEqual({ total: 10, covered: 8, pct: 80 }, ret.statements);
- test.deepEqual({ total: 4, covered: 2, pct: 50 }, ret.functions);
- test.deepEqual({ total: 10, covered: 4, pct: 40 }, ret.branches);
+ test.deepEqual({ total: 8, covered: 6, pct: 75, skipped: 0 }, ret.lines);
+ test.deepEqual({ total: 10, covered: 8, pct: 80, skipped: 0 }, ret.statements);
+ test.deepEqual({ total: 4, covered: 2, pct: 50, skipped: 0 }, ret.functions);
+ test.deepEqual({ total: 10, covered: 4, pct: 40, skipped: 0 }, ret.branches);
test.done();
},
"can merge with a blank object in first position": function (test) {
var s1 = null,
s2 = utils.summarizeFileCoverage(it2.foo),
ret = utils.mergeSummaryObjects(s1, s2);
- test.deepEqual({ total: 4, covered: 3, pct: 75 }, ret.lines);
- test.deepEqual({ total: 5, covered: 4, pct: 80 }, ret.statements);
- test.deepEqual({ total: 2, covered: 1, pct: 50 }, ret.functions);
- test.deepEqual({ total: 5, covered: 2, pct: 40 }, ret.branches);
+ test.deepEqual({ total: 4, covered: 3, pct: 75, skipped: 0 }, ret.lines);
+ test.deepEqual({ total: 5, covered: 4, pct: 80, skipped: 0 }, ret.statements);
+ test.deepEqual({ total: 2, covered: 1, pct: 50, skipped: 0 }, ret.functions);
+ test.deepEqual({ total: 5, covered: 2, pct: 40, skipped: 0 }, ret.branches);
test.done();
},
"can merge with a blank object in second position": function (test) {
var s1 = utils.summarizeFileCoverage(it2.foo),
s2 = null,
ret = utils.mergeSummaryObjects(s1, s2);
- test.deepEqual({ total: 4, covered: 3, pct: 75 }, ret.lines);
- test.deepEqual({ total: 5, covered: 4, pct: 80 }, ret.statements);
- test.deepEqual({ total: 2, covered: 1, pct: 50 }, ret.functions);
- test.deepEqual({ total: 5, covered: 2, pct: 40 }, ret.branches);
+ test.deepEqual({ total: 4, covered: 3, pct: 75, skipped: 0 }, ret.lines);
+ test.deepEqual({ total: 5, covered: 4, pct: 80, skipped: 0 }, ret.statements);
+ test.deepEqual({ total: 2, covered: 1, pct: 50, skipped: 0 }, ret.functions);
+ test.deepEqual({ total: 5, covered: 2, pct: 40, skipped: 0 }, ret.branches);
test.done();
},
"can turn it into a YUI coverage object": function (test) {
24 test/other/test-summarizer.js
View
@@ -10,22 +10,22 @@ module.exports = {
setUp: function (cb) {
summarizer = new TreeSummarizer();
s1 = {
- statements: { covered: 5, total: 10, pct: 50 },
- lines: { covered: 5, total: 10, pct: 50 },
- functions: { covered: 15, total: 20, pct: 75 },
- branches: { covered: 100, total: 200, pct: 50 }
+ statements: { covered: 5, total: 10, pct: 50, skipped: 0 },
+ lines: { covered: 5, total: 10, pct: 50, skipped: 0 },
+ functions: { covered: 15, total: 20, pct: 75, skipped: 0 },
+ branches: { covered: 100, total: 200, pct: 50, skipped: 0 }
};
s2 = {
- statements: { covered: 10, total: 20, pct: 50 },
- lines: { covered: 10, total: 20, pct: 50 },
- functions: { covered: 75, total: 100, pct: 75 },
- branches: { covered: 1, total: 2, pct: 50 }
+ statements: { covered: 10, total: 20, pct: 50, skipped: 0 },
+ lines: { covered: 10, total: 20, pct: 50, skipped: 0 },
+ functions: { covered: 75, total: 100, pct: 75, skipped: 0 },
+ branches: { covered: 1, total: 2, pct: 50, skipped: 0 }
};
s3 = {
- statements: { covered: 9, total: 10, pct: 90 },
- lines: { covered: 9, total: 10, pct: 90 },
- functions: { covered: 15, total: 15, pct: 100 },
- branches: { covered: 101, total: 101, pct: 100 }
+ statements: { covered: 9, total: 10, pct: 90, skipped: 0 },
+ lines: { covered: 9, total: 10, pct: 90, skipped: 0 },
+ functions: { covered: 15, total: 15, pct: 100, skipped: 0 },
+ branches: { covered: 101, total: 101, pct: 100, skipped: 0 }
};
cb();
},
Please sign in to comment.
Something went wrong with that request. Please try again.