Skip to content

Commit

Permalink
Added support for multiple reporters.
Browse files Browse the repository at this point in the history
  • Loading branch information
santiagoaguiar committed Apr 25, 2016
1 parent c0f9be2 commit 054a1a6
Show file tree
Hide file tree
Showing 11 changed files with 375 additions and 108 deletions.
60 changes: 43 additions & 17 deletions bin/_mocha
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ program
.option('-c, --colors', 'force enabling of colors')
.option('-C, --no-colors', 'force disabling of colors')
.option('-G, --growl', 'enable growl notification support')
.option('-O, --reporter-options <k=v,k2=v2,...>', 'reporter-specific options')
.option('-R, --reporter <name>', 'specify the reporter to use', 'spec')
.option('-O, --reporter-options <k=v,k2=v2,...>', 'reporter-specific options, use name1:{k=v,k2=v2,...},name2:{...} for multiple reporters')
.option('-R, --reporter <name>', 'specify the reporter to use, or <name1>,<name2>,... for multiple reporters.', list, ['spec'])
.option('-S, --sort', "sort test files")
.option('-b, --bail', "bail after first test failure")
.option('-d, --debug', "enable node's debugger, synonym for node --debug")
Expand Down Expand Up @@ -190,16 +190,41 @@ Error.stackTraceLimit = Infinity; // TODO: config

var reporterOptions = {};
if (program.reporterOptions !== undefined) {
program.reporterOptions.split(",").forEach(function(opt) {
var L = opt.split("=");
if (L.length > 2 || L.length === 0) {
throw new Error("invalid reporter option '" + opt + "'");
} else if (L.length === 2) {
reporterOptions[L[0]] = L[1];
} else {
reporterOptions[L[0]] = true;
var reporterOptionsParser;
var multipleReporterFormat = /([^,:]+):{([^}]+)}/g;
if (program.reporterOptions.match(multipleReporterFormat)) {
// multiple reporter config:
// spec:{a=1,b=2},dot:{c=3,d=4}
var match;
while((match = multipleReporterFormat.exec(program.reporterOptions))) {
if (match.length !== 3) {
throw new Error("invalid multiple reporter options format '" + program.reporterOptions + "'");
}

var reporterName = match[1];
var reporterOptionStr = match[2];

reporterOptionStr.split(',').forEach(function(reporterOpt) {
var split = reporterOpt.indexOf('=');
if (split === -1) {
throw new Error("invalid reporter option '" + reporterOpt + "'");
}
reporterOptions[reporterName] = reporterOptions[reporterName] || {};
reporterOptions[reporterName][reporterOpt.substr(0, split)] = reporterOpt.substr(split + 1);
});
};
} else {
// single reporter config:
// a=1,b=2
program.reporterOptions.split(',').forEach(function(opt) {
var L = opt.split('=');
if (L.length !== 2) {
throw new Error("invalid reporter option '" + opt + "'");
}
reporterOptions._default = reporterOptions._default || {};
reporterOptions._default[L[0]] = L[1];
});
}
}

// reporter
Expand All @@ -208,16 +233,17 @@ mocha.reporter(program.reporter, reporterOptions);

// load reporter

var Reporter = null;
try {
Reporter = require('../lib/reporters/' + program.reporter);
} catch (err) {
program.reporter.forEach(function(reporterName) {
try {
Reporter = require(program.reporter);
require('../lib/reporters/' + reporterName);
} catch (err) {
throw new Error('reporter "' + program.reporter + '" does not exist');
try {
require(reporterName);
} catch (err2) {
throw new Error('reporter "' + reporterName + '" does not exist');
}
}
}
});

// --no-colors

Expand Down
88 changes: 63 additions & 25 deletions lib/mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

var escapeRe = require('escape-string-regexp');
var path = require('path');
var reporters = require('./reporters');
var builtInReporters = require('./reporters');
var utils = require('./utils');

/**
Expand All @@ -34,7 +34,7 @@ if (!process.browser) {

exports.utils = utils;
exports.interfaces = require('./interfaces');
exports.reporters = reporters;
exports.reporters = builtInReporters;
exports.Runnable = require('./runnable');
exports.Context = require('./context');
exports.Runner = require('./runner');
Expand Down Expand Up @@ -143,23 +143,40 @@ Mocha.prototype.addFile = function(file) {
};

/**
* Set reporter to `reporter`, defaults to "spec".
* Set reporters to `reporters`, defaults to ['spec'].
*
* @param {String|Function} reporter name or constructor
* @param {Object} reporterOptions optional options
* @param {String|Function|Array of strings|Array of functions} reporter name as
* string, reporter constructor, or array of constructors or reporter names as
* strings.
* @param {Object} reporterOptions optional options, keyed by reporter name, or
* '_default' for options to use when per-name options are not given.
* @api public
* @param {string|Function} reporter name or constructor
* @param {Object} reporterOptions optional options
*/
Mocha.prototype.reporter = function(reporter, reporterOptions) {
if (typeof reporter === 'function') {
this._reporter = reporter;
} else {
reporter = reporter || 'spec';
Mocha.prototype.reporter = function(reporters, reporterOptions) {
// if no reporter is given as input, default to spec reporter
reporters = reporters || ['spec'];
reporterOptions = reporterOptions || {};

if (!Array.isArray(reporters)) {
if ((typeof reporters === 'string')
|| (typeof reporters === 'function')) {
reporters = [reporters];
}
}

// Load each reporter
this._reporters = reporters.map(function(reporter) {
if (typeof reporter === 'function') {
return {
fn: reporter,
options: reporterOptions
};
}

var _reporter;
// Try to load a built-in reporter.
if (reporters[reporter]) {
_reporter = reporters[reporter];
if (builtInReporters[reporter]) {
_reporter = builtInReporters[reporter];
}
// Try to load reporters from process.cwd() and node_modules
if (!_reporter) {
Expand All @@ -179,9 +196,12 @@ Mocha.prototype.reporter = function(reporter, reporterOptions) {
if (!_reporter) {
throw new Error('invalid reporter "' + reporter + '"');
}
this._reporter = _reporter;
}
this.options.reporterOptions = reporterOptions;
return {
fn: _reporter,
options: reporterOptions[reporter] || reporterOptions._default || {}
};
}, this);

return this;
};

Expand Down Expand Up @@ -471,7 +491,14 @@ Mocha.prototype.run = function(fn) {
var options = this.options;
options.files = this.files;
var runner = new exports.Runner(suite, options.delay);
var reporter = new this._reporter(runner, options);

// For each loaded reporter constructor, create
// an instance and initialize it with the runner
var reporters = this._reporters.map(function(reporterConfig) {
var _reporter = reporterConfig.fn;
options.reporterOptions = reporterConfig.options;
return new _reporter(runner, options);
});
runner.ignoreLeaks = options.ignoreLeaks !== false;
runner.fullStackTrace = options.fullStackTrace;
runner.asyncOnly = options.asyncOnly;
Expand All @@ -482,21 +509,32 @@ Mocha.prototype.run = function(fn) {
if (options.globals) {
runner.globals(options.globals);
}
// Use only the first reporter for growl, since we don't want to
// send several notifications for the same test suite
if (options.growl) {
this._growl(runner, reporter);
this._growl(runner, reporters[0]);
}
if (options.useColors !== undefined) {
exports.reporters.Base.useColors = options.useColors;
}
exports.reporters.Base.inlineDiffs = options.useInlineDiffs;

function done(failures) {
if (reporter.done) {
reporter.done(failures, fn);
} else {
fn && fn(failures);
function runnerDone(failures) {
function reporterDone(failures) {
if (--remain === 0) {
fn && fn(failures);
}
}

var remain = reporters.length;
reporters.forEach(function(reporter) {
if (reporter.done) {
reporter.done(failures, reporterDone);
} else {
reporterDone(failures);
}
});
}

return runner.run(done);
return runner.run(runnerDone);
};
70 changes: 69 additions & 1 deletion lib/reporters/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
*/

var tty = require('tty');
var fs = require('fs');
var mkdirp = require('mkdirp');
var path = require('path');
var EOL = require('os').EOL;
var diff = require('diff');
var ms = require('../ms');
var utils = require('../utils');
Expand Down Expand Up @@ -231,13 +235,16 @@ exports.list = function(failures) {
* of tests passed / failed etc.
*
* @param {Runner} runner
* @param {Object} options runner optional options
* @api public
*/

function Base(runner) {
function Base(runner, options) {
var stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 };
var failures = this.failures = [];

this.reporterOptions = options ? options.reporterOptions : null;

if (!runner) {
return;
}
Expand Down Expand Up @@ -332,6 +339,67 @@ Base.prototype.epilogue = function() {
console.log();
};

/**
* Opens for writing the file referenced on the `optionName` option
* of the reporter options, if any.
* Call this method to support writing to this file when calling
* write or writeLine.
*
* @param {string} optionName the name of the option, defaults to 'output'.
* @api public
*/
Base.prototype.openOutput = function(optionName) {
var output = this.reporterOptions && this.reporterOptions[optionName || 'output'];
if (output) {
if (!fs.createWriteStream) {
throw new Error('file output not supported in browser');
}

mkdirp.sync(path.dirname(output));
this.fileStream = fs.createWriteStream(output);
}
};

/**
* Write to reporter output stream.
*
* @param {string} str
* @api public
*/
Base.prototype.write = function(str) {
if (this.fileStream) {
this.fileStream.write(str);
} else {
process.stdout.write(str);
}
};

/**
* Write to reporter output stream, adding an EOL at the end.
*
* @param {string} line
* @api public
*/
Base.prototype.writeLine = function(line) {
this.write(line + EOL);
};

/**
* Close the output stream and callback with failures.
*
* @param failures
* @param {Function} fn
*/
Base.prototype.done = function(failures, fn) {
if (this.fileStream) {
this.fileStream.end(function() {
fn(failures);
});
} else {
fn(failures);
}
};

/**
* Pad the given `str` to `len`.
*
Expand Down
15 changes: 11 additions & 4 deletions lib/reporters/html-cov.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

var JSONCov = require('./json-cov');
var inherits = require('../utils').inherits;
var readFileSync = require('fs').readFileSync;
var join = require('path').join;

Expand All @@ -17,24 +18,30 @@ exports = module.exports = HTMLCov;
*
* @api public
* @param {Runner} runner
* @param {Object} options Runner optional options
*/
function HTMLCov(runner) {
function HTMLCov(runner, options) {
JSONCov.call(this, runner, options, false);

var jade = require('jade');
var file = join(__dirname, '/templates/coverage.jade');
var str = readFileSync(file, 'utf8');
var fn = jade.compile(str, { filename: file });
var self = this;

JSONCov.call(this, runner, false);

runner.on('end', function() {
process.stdout.write(fn({
self.write(fn({
cov: self.cov,
coverageClass: coverageClass
}));
});
}

/**
* Inherit from `JSONCov.prototype`.
*/
inherits(HTMLCov, JSONCov);

/**
* Return coverage class for a given coverage percentage.
*
Expand Down
Loading

0 comments on commit 054a1a6

Please sign in to comment.