/** * ESJS reporter for running and collecting mocha test results. * * @param {Runner} runner * @api public */ module.exports = JenkinsReporter; var Base = require('mocha/lib/reporters/base'); var _ = require('lodash'); var chalk = require('chalk'); var makeJUnitXml = require('./make_j_unit_xml'); var fs = require('fs'); var path = require('path'); var inspect = require('util').inspect; var log = (function() { var locked = _.bind(process.stdout.write, process.stdout); return function(str) { if (typeof str !== 'string') { str = inspect(str); } locked(str); }; })(); var integration = _.find(process.argv, function(arg) { return arg.indexOf('test/integration') > -1; }); var unit = _.find(process.argv, function(arg) { return arg.indexOf('test/unit') > -1; }); var output; if (unit) { output = path.join(__dirname, '../junit-node-unit.xml'); } else if (integration) { output = path.join(__dirname, '../junit-node-integration.xml'); } else { throw new Error('unable to detect unit or integration tests'); } function JenkinsReporter(runner) { Base.call(this, runner); var stats = this.stats; var rootSuite = { results: [], suites: [], }; var stack = [rootSuite]; function indt() { return new Array(stack.length + 1).join(' '); } runner.on('suite', function(suite) { if (suite.root) { return; } // suite suite = { name: suite.fullTitle(), results: [], start: Date.now(), stdout: '', stderr: '', }; // append to the previous stack leader if (!stack[0].suites) { stack[0].suites = []; } stack[0].suites.push(suite); // push the suite onto the top of the stack stack.unshift(suite); }); runner.on('suite end', function(suite) { if (suite.root) { return; } stack[0].time = Date.now() - stack[0].start; stack.shift(); }); runner.on('fail', function(test) { if (test.type === 'hook') { runner.emit('test end', test); } }); runner.on('test end', function(test) { if (test.state === 'passed') { log(chalk.green('.')); } else if (test.pending) { log(chalk.grey('.')); return; } else { log(chalk.red('x')); } var errMsg = void 0; if (test.err) { errMsg = test.err.stack || test.err.toString(); // FF / Opera do not add the message if (!~errMsg.indexOf(test.err.message)) { errMsg = test.err.message + '\n' + errMsg; } // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we // check for the result of the stringifying. if (errMsg === '[object Error]') { errMsg = test.err.message; } // Safari doesn't give you a stack. Let's at least provide a source line. if ( !test.err.stack && test.err.sourceURL && test.err.line !== undefined ) { errMsg += '\n(' + test.err.sourceURL + ':' + test.err.line + ')'; } console.error( _.map(errMsg.split('\n'), function(line) { return indt() + ' ' + line; }).join('\n') ); } if (stack[0]) { stack[0].results.push({ name: test.title, time: test.duration, pass: test.state === 'passed', test: test, stdout: stack[0].stdout, stderr: stack[0].stderr, }); stack[0].stdout = stack[0].stderr = ''; } }); runner.on('hook end', function(hook) { if ( hook.title.indexOf('"after each"') > -1 && stack[0] && stack[0].results.length ) { var result = _.last(stack[0].results); result.stdout += stack[0].stdout; result.stderr += stack[0].stderr; stack[0].stdout = stack[0].stderr = ''; } }); runner.on('end', function() { restoreStdio(); var xml = makeJUnitXml('node ' + process.version, { stats: stats, suites: _.map(rootSuite.suites, function removeElements(suite) { var s = { name: suite.name, start: suite.start, time: suite.time || 0, results: suite.results, stdout: suite.stdout, stderr: suite.stderr, }; if (suite.suites) { s.suites = _.map(suite.suites, removeElements); } return s; }), }); fs.writeFileSync(output, xml, 'utf8'); console.log( '\n' + [ 'tests complete in ' + Math.round(stats.duration / 10) / 100 + ' seconds', ' fail: ' + chalk.red(stats.failures), ' pass: ' + chalk.green(stats.passes), ' pending: ' + chalk.grey(stats.pending), ].join('\n') ); }); // overload the write methods on stdout and stderr ['stdout', 'stderr'].forEach(function(name) { var obj = process[name]; var orig = obj.write; obj.write = function(chunk) { if (stack[0]) { stack[0][name] = (stack[0][name] || '') + chunk; } // orig.apply(obj, arguments); }; obj.__restore = function() { this.write = orig; }; }); function restoreStdio() { process.stdout.__restore(); process.stderr.__restore(); } }