From 28e0a4f9abfdae15841ce01c89e5c59224f6fc2d Mon Sep 17 00:00:00 2001 From: Christopher Hiller Date: Sun, 5 Apr 2015 16:47:03 -0700 Subject: [PATCH 1/2] update contributors --- .mailmap | 12 ++- package.json | 245 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 250 insertions(+), 7 deletions(-) diff --git a/.mailmap b/.mailmap index 704013125b..93ad1bff98 100644 --- a/.mailmap +++ b/.mailmap @@ -1 +1,11 @@ -TJ Holowaychuk \ No newline at end of file +TJ Holowaychuk +Travis Jeffery +Travis Jeffery Dr. Travis Jeffery +Christopher Hiller Christopher Hiller +David da Silva Contín David da Silva +David da Silva Contín David da Silva +Ariel Mashraki Ariel Mashraki +Ariel Mashraki Ariel Mashraki +Forbes Lindesay Forbes Lindesay +Ben Bradley Ben Bradley <[ben.bradley@cigna.com|mailto:ben.bradley@cigna.com]> +Glen Mailer Glen Mailer diff --git a/package.json b/package.json index 28e16ffd42..d5571824ce 100644 --- a/package.json +++ b/package.json @@ -11,14 +11,247 @@ ], "author": "TJ Holowaychuk ", "contributors": [ - "Joshua Appelman ", - "Christoffer Hallas ", - "Christopher Hiller ", "Travis Jeffery ", - "Daniel St. Jules ", + "Christopher Hiller ", + "Joshua Appelman ", + "Guillermo Rauch ", "David da Silva Contín ", + "Daniel St. Jules ", "Ariel Mashraki ", - "Pawel Kozlowski " + "Attila Domokos ", + "John Firebaugh ", + "Nathan Rajlich ", + "Jo Liss ", + "Mike Pennisi ", + "Brendan Nee ", + "James Carr ", + "Ryunosuke SATO ", + "Aaron Heckmann ", + "Jonathan Ong ", + "Forbes Lindesay ", + "Raynos ", + "Xavier Antoviaque ", + "hokaccha ", + "Joshua Krall ", + "Domenic Denicola ", + "Glen Mailer ", + "Mathieu Desvé ", + "Cory Thomas ", + "Fredrik Enestad ", + "Ben Bradley ", + "Sindre Sorhus ", + "Jesse Dailey ", + "Ben Lindsey ", + "Maximilian Antoni ", + "Merrick Christensen ", + "Michael Demmer ", + "Tyson Tate ", + "Valentin Agachi ", + "Wil Moore III ", + "Benjie Gillam ", + "Nathan Bowser ", + "eiji.ienaga ", + "fool2fish ", + "Paul Miller ", + "Andreas Lind Petersen ", + "Timo Tijhof ", + "Nathan Alderson ", + "Ian Storm Taylor ", + "Arian Stolwijk ", + "Rico Sta. Cruz ", + "domenic ", + "Jacob Wejendorp ", + "fcrisci ", + "Simon Gaeremynck ", + "James Nylen ", + "Shawn Krisman ", + "Sean Lang ", + "David Henderson ", + "jsdevel ", + "Alexander Early ", + "Parker Moore ", + "Paul Armstrong ", + "monowerker ", + "Konstantin Käfer ", + "Justin DuJardin ", + "Juzer Ali ", + "Dominique Quatravaux ", + "Quang Van ", + "Quanlong He ", + "Vlad Magdalin ", + "Brian Beck ", + "Jonas Westerlund ", + "Michael Riley ", + "Buck Doyle ", + "FARKAS Máté ", + "Sune Simonsen ", + "Keith Cirkel ", + "Kent C. Dodds ", + "Kevin Conway ", + "Kevin Kirsche ", + "Kirill Korolyov ", + "Koen Punt ", + "Kyle Mitchell ", + "Laszlo Bacsi ", + "Liam Newman ", + "Linus Unnebäck ", + "László Bácsi ", + "Maciej Małecki ", + "Mal Graty ", + "Marc Kuo ", + "Marcello Bastea-Forte ", + "Martin Marko ", + "Matija Marohnić ", + "Matt Robenolt ", + "Matt Smith ", + "Matthew Shanley ", + "Mattias Tidlund ", + "Michael Jackson ", + "Michael Olson ", + "Michael Schoonmaker ", + "Michael Schoonmaker ", + "Michal Charemza ", + "Moshe Kolodny ", + "Nathan Black ", + "Nick Fitzgerald ", + "Nicolo Taddei ", + "Noshir Patel ", + "Panu Horsmalahti ", + "Pete Hawkins ", + "Pete Hawkins ", + "Phil Sung ", + "R56 ", + "Raynos <=>", + "Refael Ackermann ", + "Richard Dingwall ", + "Richard Knop ", + "Rob Wu ", + "Romain Prieto ", + "Roman Neuhauser ", + "Roman Shtylman ", + "Russ Bradberry ", + "Russell Munson ", + "Rustem Mustafin ", + "Ryan Hubbard ", + "Salehen Shovon Rahman ", + "Sam Mussell ", + "Sasha Koss ", + "Seiya Konno ", + "Shaine Hatch ", + "Simon Goumaz ", + "Standa Opichal ", + "Stephen Mathieson ", + "Steve Mason ", + "Stewart Taylor ", + "Tapiwa Kelvin ", + "Teddy Zeenny ", + "Tim Ehat ", + "Todd Agulnick ", + "Tom Coquereau ", + "Vadim Nikitin ", + "Victor Costan ", + "Will Langstroth ", + "Yanis Wang ", + "Yuest Wang ", + "Zsolt Takács ", + "abrkn ", + "airportyh ", + "badunk ", + "claudyus ", + "dasilvacontin ", + "fengmk2 ", + "gaye ", + "grasGendarme ", + "klaemo ", + "lakmeer ", + "lodr ", + "mrShturman ", + "nishigori ", + "omardelarosa ", + "qiuzuhui ", + "samuel goldszmidt ", + "sebv ", + "slyg ", + "startswithaj ", + "tgautier@yahoo.com ", + "traleig1 ", + "vlad ", + "yuitest ", + "zhiyelee ", + "Adam Crabtree ", + "Adam Gruber ", + "Andreas Brekken ", + "Andrew Nesbitt ", + "Andrey Popp <8mayday@gmail.com>", + "Andrii Shumada ", + "Anis Safine ", + "Arnaud Brousseau ", + "Atsuya Takagi ", + "Austin Birch ", + "Ben Noordhuis ", + "Benoît Zugmeyer ", + "Bjørge Næss ", + "Brian Lalor ", + "Brian M. Carlson ", + "Brian Moore ", + "Bryan Donovan ", + "C. Scott Ananian ", + "Casey Foster ", + "Chris Buckley ", + "ChrisWren ", + "Connor Dunn ", + "Corey Butler ", + "Daniel Stockman ", + "Dave McKenna ", + "Denis Bardadym ", + "Devin Weaver ", + "Di Wu ", + "Diogo Monteiro ", + "Dmitry Shirokov ", + "Dominic Barnes ", + "Douglas Christopher Wilson ", + "Fede Ramirez ", + "Fedor Indutny ", + "Florian Margaine ", + "Frederico Silva ", + "Fredrik Lindin ", + "Gareth Aye ", + "Gareth Murphy ", + "Gavin Mogan ", + "Giovanni Bassi ", + "Glen Huang ", + "Greg Perkins ", + "Harish ", + "Harry Brundage ", + "Herman Junge ", + "Ian Young ", + "Ian Zamojc ", + "Ivan ", + "JP Bochi ", + "Jaakko Salonen ", + "Jake Craige ", + "Jake Marsh ", + "Jakub Nešetřil ", + "James Bowes ", + "James Lal ", + "Jan Kopriva ", + "Jason Barry ", + "Javier Aranda ", + "Jean Ponchon ", + "Jeff Kunkle ", + "Jeff Schilling ", + "Jeremy Martin ", + "Jimmy Cuadra ", + "John Doty ", + "Johnathon Sanders ", + "Jonas Dohse ", + "Jonathan Creamer ", + "Jonathan Delgado ", + "Jonathan Park ", + "Jordan Sexton ", + "Jussi Virtanen ", + "Katie Gengler ", + "Kazuhito Hokamura " ], "license": "MIT", "repository": { @@ -72,4 +305,4 @@ "url": "https://raw.github.com/mochajs/mocha/master/LICENSE" } ] -} \ No newline at end of file +} From 4e8f0a7ad2db9de1059c749b85904d75496b49a3 Mon Sep 17 00:00:00 2001 From: Ben Vinegar Date: Tue, 30 Jun 2015 14:54:57 -0700 Subject: [PATCH 2/2] Implement support for multiple reporters --- bin/_mocha | 61 ++++++++++++++++----- lib/mocha.js | 107 +++++++++++++++++++++++++++---------- lib/reporters/base.js | 39 +++++++++++++- lib/reporters/json.js | 4 +- lib/reporters/xunit.js | 46 +++------------- test/reporters/json.js | 2 +- test/reporters/multiple.js | 97 +++++++++++++++++++++++++++++++++ 7 files changed, 274 insertions(+), 82 deletions(-) create mode 100644 test/reporters/multiple.js diff --git a/bin/_mocha b/bin/_mocha index aa999a28c3..023aef6691 100755 --- a/bin/_mocha +++ b/bin/_mocha @@ -63,7 +63,7 @@ program .option('-C, --no-colors', 'force disabling of colors') .option('-G, --growl', 'enable growl notification support') .option('-O, --reporter-options ', 'reporter-specific options') - .option('-R, --reporter ', 'specify the reporter to use', 'spec') + .option('-R, --reporter ,...', 'specify the reporters to use', 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") @@ -193,13 +193,44 @@ 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) { - throw new Error("invalid reporter option '" + opt + "'"); + + program.reporterOptions.split(",").forEach( + + // Two possible formats ... + /\w+{(\w+:\w+)+}/.test(program.reporterOptions) ? + + // per-reporter config: + // spec{a:1,b:2},dot{c:3,d:4} + function(opt) { + var m = opt.match(/(\w+){(\w+:\w+)+}/); + if (m.length !== 3) { + throw new("invalid reporter option '" + opt + "'"); + } + + var reporterName = m[1] + , reporterOptStr = m[2]; + + reporterOptStr.split(",").forEach(function (reporterOpt) { + var L = reporterOpt.split(':'); + if (L.length !== 2) { + throw new Error("invalid reporter option '" + opt + "'"); } - reporterOptions[L[0]] = L[1]; - }); + reporterOptions[reporterName] = reporterOptions[reporterName] || {}; + reporterOptions[reporterName][L[0]] = L[1]; + }); + } : + + // single reporter config: + // a=1,b=2 + 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 @@ -212,15 +243,19 @@ mocha.ui(program.ui); // load reporter -try { - Reporter = require('../lib/reporters/' + program.reporter); -} catch (err) { +program.reporter.forEach(function (reporterConfig) { + var reporterName; try { - Reporter = require(program.reporter); + reporterName = reporterConfig.split(':')[0]; + Reporter = require('../lib/reporters/' + reporterName); } catch (err) { - throw new Error('reporter "' + program.reporter + '" does not exist'); + try { + Reporter = require(reporterName); + } catch (err) { + throw new Error('reporter "' + reporterName + '" does not exist'); + } } -} +}); // --no-colors diff --git a/lib/mocha.js b/lib/mocha.js index 8a11124cb1..dd0c1c25b1 100644 --- a/lib/mocha.js +++ b/lib/mocha.js @@ -131,32 +131,63 @@ 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 TODO FIXME * @api public */ -Mocha.prototype.reporter = function(reporter, reporterOptions){ - if ('function' == typeof reporter) { - this._reporter = reporter; - } else { - reporter = reporter || 'spec'; - var _reporter; - try { _reporter = require('./reporters/' + reporter); } catch (err) {} - if (!_reporter) try { _reporter = require(reporter); } catch (err) { - err.message.indexOf('Cannot find module') !== -1 - ? console.warn('"' + reporter + '" reporter not found') - : console.warn('"' + reporter + '" reporter blew up with error:\n' + err.stack); + +Mocha.prototype.reporter = function(reporters, reporterOptions){ + // if no reporter is given as input, default to spec reporter + reporters = reporters || ['spec']; + reporterOptions = reporterOptions || {}; + + // If reporters argument is not a list, turn it into a list of reporter names + // or constructors that we'll iterate on right after to initialize them + if (!Array.isArray(reporters)) { + if (('string' == typeof reporters) || + ('function' == typeof reporters)) { + reporters = [reporters]; } - if (!_reporter && reporter === 'teamcity') - console.warn('The Teamcity reporter was moved to a package named ' + - 'mocha-teamcity-reporter ' + - '(https://npmjs.org/package/mocha-teamcity-reporter).'); - if (!_reporter) throw new Error('invalid reporter "' + reporter + '"'); - this._reporter = _reporter; } - this.options.reporterOptions = reporterOptions; + + // Load each reporter + this._reporters = reporters.map(function (reporterConfig) { + if ('function' == typeof reporterConfig) { + return { + fn: reporterConfig, + options: reporterOptions + }; + } else { + var reporterName + , path; + + reporterName = reporterConfig.split(':'); + if (reporterName.length > 1) { + path = reporterName[1]; + reporterName = reporterName[0]; + } + try { _reporter = require('./reporters/' + reporterName); } catch (err) {}; + if (!_reporter) try { _reporter = require(reporterName); } catch (err) { + err.message.indexOf('Cannot find module') !== -1 + ? console.warn('"' + reporterName + '" reporter not found') + : console.warn('"' + reporterName + '" reporter blew up with error:\n' + err.stack); + } + if (!_reporter && reporterName === 'teamcity') + console.warn('The Teamcity reporter was moved to a package named ' + + 'mocha-teamcity-reporter ' + + '(https://npmjs.org/package/mocha-teamcity-reporter).'); + if (!_reporter) throw new Error('invalid reporter "' + reporterName + '"'); + return { + fn: _reporter, + path: path, + options: reporterOptions[reporterName] || reporterOptions._default || {} + }; + } + }, this); + return this; }; @@ -424,23 +455,45 @@ 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 + , path = reporterConfig.path; + + options.reporterOptions = reporterConfig.options; + return new _reporter(runner, options, path); + }); runner.ignoreLeaks = false !== options.ignoreLeaks; runner.fullStackTrace = options.fullStackTrace; runner.asyncOnly = options.asyncOnly; if (options.grep) runner.grep(options.grep, options.invert); if (options.globals) runner.globals(options.globals); - if (options.growl) this._growl(runner, reporter); + + // 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, reporters[0]); + if (options.useColors !== undefined) { exports.reporters.Base.useColors = options.useColors; } + exports.reporters.Base.inlineDiffs = options.useInlineDiffs; - function done(failures) { + function runnerDone(failures) { + var remain = reporters.length + , reporterDone = function(failures) { + if (--remain === 0) fn && fn(failures); + }; + + reporters.forEach(function (reporter) { if (reporter.done) { - reporter.done(failures, fn); - } else fn && fn(failures); + reporter.done(failures, reporterDone); + } else { + reporterDone(failures); + } + }); } - return runner.run(done); + return runner.run(runnerDone); }; diff --git a/lib/reporters/base.js b/lib/reporters/base.js index 9b719a80e7..cdad6c1962 100644 --- a/lib/reporters/base.js +++ b/lib/reporters/base.js @@ -3,6 +3,7 @@ */ var tty = require('tty') + , fs = require('fs') , diff = require('diff') , ms = require('../ms') , utils = require('../utils') @@ -225,11 +226,21 @@ exports.list = function(failures){ * @api public */ -function Base(runner) { +function Base(runner, options, path) { var self = this , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } , failures = this.failures = []; + this.options = options; + this.path = path; + + if (path) { + if (!fs.createWriteStream) { + throw new Error('file output not supported in browser'); + } + this.fileStream = fs.createWriteStream(path); + } + if (!runner) return; this.runner = runner; @@ -323,6 +334,32 @@ Base.prototype.epilogue = function(){ console.log(); }; + +/** + * Write to reporter output stream + */ +Base.prototype.write = function(str){ + if (this.fileStream) { + this.fileStream.write(str); + } else { + process.stdout.write(str); + } +}; + +Base.prototype.writeLine = function(line) { + this.write(line + '\n'); +}; + +Base.prototype.done = function(failures, fn) { + if (this.fileStream) { + this.fileStream.end(function() { + fn(failures); + }); + } else { + fn(failures); + } +}; + /** * Pad the given `str` to `len`. * diff --git a/lib/reporters/json.js b/lib/reporters/json.js index f565506c6e..1e28afa50e 100644 --- a/lib/reporters/json.js +++ b/lib/reporters/json.js @@ -19,9 +19,9 @@ exports = module.exports = JSONReporter; * @api public */ -function JSONReporter(runner) { +function JSONReporter(runner, options, path) { var self = this; - Base.call(this, runner); + Base.call(this, runner, options, path); var tests = [] , pending = [] diff --git a/lib/reporters/xunit.js b/lib/reporters/xunit.js index 77cd347094..422b41fa77 100644 --- a/lib/reporters/xunit.js +++ b/lib/reporters/xunit.js @@ -30,19 +30,12 @@ exports = module.exports = XUnit; * @api public */ -function XUnit(runner, options) { - Base.call(this, runner); +function XUnit(runner, options, path) { + Base.call(this, runner, options, path); var stats = this.stats , tests = [] , self = this; - if (options.reporterOptions && options.reporterOptions.output) { - if (! fs.createWriteStream) { - throw new Error('file output not supported in browser'); - } - self.fileStream = fs.createWriteStream(options.reporterOptions.output); - } - runner.on('pending', function(test){ tests.push(test); }); @@ -56,7 +49,7 @@ function XUnit(runner, options) { }); runner.on('end', function(){ - self.write(tag('testsuite', { + self.writeLine(tag('testsuite', { name: 'Mocha Tests' , tests: stats.tests , failures: stats.failures @@ -67,40 +60,16 @@ function XUnit(runner, options) { }, false)); tests.forEach(function(t) { self.test(t); }); - self.write(''); + self.writeLine(''); }); } -/** - * Override done to close the stream (if it's a file). - */ -XUnit.prototype.done = function(failures, fn) { - if (this.fileStream) { - this.fileStream.end(function() { - fn(failures); - }); - } else { - fn(failures); - } -}; - /** * Inherit from `Base.prototype`. */ XUnit.prototype.__proto__ = Base.prototype; -/** - * Write out the given line - */ -XUnit.prototype.write = function(line) { - if (this.fileStream) { - this.fileStream.write(line + '\n'); - } else { - console.log(line); - } -}; - /** * Output tag for the given `test.` */ @@ -112,13 +81,14 @@ XUnit.prototype.test = function(test, ostream) { , time: (test.duration / 1000) || 0 }; + if ('failed' == test.state) { var err = test.err; - this.write(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + "\n" + err.stack)))); + this.writeLine(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + "\n" + err.stack)))); } else if (test.pending) { - this.write(tag('testcase', attrs, false, tag('skipped', {}, true))); + this.writeLine(tag('testcase', attrs, false, tag('skipped', {}, true))); } else { - this.write(tag('testcase', attrs, true) ); + this.writeLine(tag('testcase', attrs, true) ); } }; diff --git a/test/reporters/json.js b/test/reporters/json.js index e7b8955757..bb4e8648e9 100644 --- a/test/reporters/json.js +++ b/test/reporters/json.js @@ -12,7 +12,7 @@ describe('json reporter', function(){ }); suite = new Suite('JSON suite', 'root'); runner = new Runner(suite); - var mochaReporter = new mocha._reporter(runner); + var mochaReporter = new mocha._reporters[0].fn(runner); }); it('should have 1 test failure', function(done){ diff --git a/test/reporters/multiple.js b/test/reporters/multiple.js new file mode 100644 index 0000000000..6691c95e40 --- /dev/null +++ b/test/reporters/multiple.js @@ -0,0 +1,97 @@ + +var Mocha = require('../../') + , Suite = Mocha.Suite + , Runner = Mocha.Runner + , Test = Mocha.Test + , should = require('should'); + +describe('multiple reporters', function(){ + var suite, runner, specReporter, jsonReporter; + + it('should have 1 test failure', function(done){ + var mocha = new Mocha({ + reporter: ['spec', 'json'], + }); + suite = new Suite('Multiple reporters suite', 'root'); + runner = new Runner(suite); + specReporter = new mocha._reporters[0].fn(runner); + jsonReporter = new mocha._reporters[1].fn(runner); + + var testTitle = 'json test 1'; + var error = { message: 'oh shit' }; + + suite.addTest(new Test(testTitle, function (done) { + done(new Error(error.message)); + })); + + runner.run(function(failureCount) { + + // Verify that each reporter ran + specReporter.should.have.property('failures'); + specReporter.failures.should.be.an.instanceOf(Array); + specReporter.failures.should.have.a.lengthOf(1); + + jsonReporter.should.have.property('failures'); + jsonReporter.failures.should.be.an.instanceOf(Array); + jsonReporter.failures.should.have.a.lengthOf(1); + done(); + }); + }); + + it('should pass correct reporter options and path to each reporter', function (done) { + var mocha = new Mocha({ + reporter: [ + 'spec', + 'dot:/var/log/dot.txt', + 'json:json.json' + ], + reporterOptions: { + spec: { foo: 'bar' }, + json: { bar: 'baz' } + } + }); + + // specReporter + mocha._reporters[0].fn = function (runner, options, path) { + options.reporterOptions.should.have.property('foo', 'bar'); + should.equal(path, undefined); + } + + // dot (no options) + mocha._reporters[1].fn = function (runner, options, path) { + options.reporterOptions.should.eql({}); + path.should.equal('/var/log/dot.txt'); + } + + // json + mocha._reporters[2].fn = function (runner, options, path) { + options.reporterOptions.should.have.property('bar', 'baz'); + path.should.equal('json.json'); + done(); + } + + mocha.run(); + }); + + it('should pass _default reporter options to each reporter', function (done) { + var mocha = new Mocha({ + reporter: [ 'spec', 'json' ], + reporterOptions: { + _default: { 'foo': 'bar' } + } + }); + + // specReporter + mocha._reporters[0].fn = function (runner, options, path) { + options.reporterOptions.should.have.property('foo', 'bar'); + } + + // json + mocha._reporters[1].fn = function (runner, options, path) { + options.reporterOptions.should.have.property('foo', 'bar'); + done(); + } + + mocha.run(); + }); +});