| 'use strict'; |
| /** |
| * @module XUnit |
| */ |
| /** |
| * Module dependencies. |
| */ |
| |
| var Base = require('./base'); |
| var utils = require('../utils'); |
| var fs = require('fs'); |
| var mkdirp = require('mkdirp'); |
| var path = require('path'); |
| var errors = require('../errors'); |
| var createUnsupportedError = errors.createUnsupportedError; |
| var constants = require('../runner').constants; |
| var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; |
| var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; |
| var EVENT_RUN_END = constants.EVENT_RUN_END; |
| var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; |
| var STATE_FAILED = require('../runnable').constants.STATE_FAILED; |
| var inherits = utils.inherits; |
| var escape = utils.escape; |
| |
| /** |
| * Save timer references to avoid Sinon interfering (see GH-237). |
| */ |
| var Date = global.Date; |
| |
| /** |
| * Expose `XUnit`. |
| */ |
| |
| exports = module.exports = XUnit; |
| |
| /** |
| * Constructs a new `XUnit` reporter instance. |
| * |
| * @public |
| * @class |
| * @memberof Mocha.reporters |
| * @extends Mocha.reporters.Base |
| * @param {Runner} runner - Instance triggers reporter actions. |
| * @param {Object} [options] - runner options |
| */ |
| function XUnit(runner, options) { |
| Base.call(this, runner, options); |
| |
| var stats = this.stats; |
| var tests = []; |
| var self = this; |
| |
| // the name of the test suite, as it will appear in the resulting XML file |
| var suiteName; |
| |
| // the default name of the test suite if none is provided |
| var DEFAULT_SUITE_NAME = 'Mocha Tests'; |
| |
| if (options && options.reporterOptions) { |
| if (options.reporterOptions.output) { |
| if (!fs.createWriteStream) { |
| throw createUnsupportedError('file output not supported in browser'); |
| } |
| |
| mkdirp.sync(path.dirname(options.reporterOptions.output)); |
| self.fileStream = fs.createWriteStream(options.reporterOptions.output); |
| } |
| |
| // get the suite name from the reporter options (if provided) |
| suiteName = options.reporterOptions.suiteName; |
| } |
| |
| // fall back to the default suite name |
| suiteName = suiteName || DEFAULT_SUITE_NAME; |
| |
| runner.on(EVENT_TEST_PENDING, function(test) { |
| tests.push(test); |
| }); |
| |
| runner.on(EVENT_TEST_PASS, function(test) { |
| tests.push(test); |
| }); |
| |
| runner.on(EVENT_TEST_FAIL, function(test) { |
| tests.push(test); |
| }); |
| |
| runner.once(EVENT_RUN_END, function() { |
| self.write( |
| tag( |
| 'testsuite', |
| { |
| name: suiteName, |
| tests: stats.tests, |
| failures: 0, |
| errors: stats.failures, |
| skipped: stats.tests - stats.failures - stats.passes, |
| timestamp: new Date().toUTCString(), |
| time: stats.duration / 1000 || 0 |
| }, |
| false |
| ) |
| ); |
| |
| tests.forEach(function(t) { |
| self.test(t); |
| }); |
| |
| self.write('</testsuite>'); |
| }); |
| } |
| |
| /** |
| * Inherit from `Base.prototype`. |
| */ |
| inherits(XUnit, Base); |
| |
| /** |
| * Override done to close the stream (if it's a file). |
| * |
| * @param failures |
| * @param {Function} fn |
| */ |
| XUnit.prototype.done = function(failures, fn) { |
| if (this.fileStream) { |
| this.fileStream.end(function() { |
| fn(failures); |
| }); |
| } else { |
| fn(failures); |
| } |
| }; |
| |
| /** |
| * Write out the given line. |
| * |
| * @param {string} line |
| */ |
| XUnit.prototype.write = function(line) { |
| if (this.fileStream) { |
| this.fileStream.write(line + '\n'); |
| } else if (typeof process === 'object' && process.stdout) { |
| process.stdout.write(line + '\n'); |
| } else { |
| Base.consoleLog(line); |
| } |
| }; |
| |
| /** |
| * Output tag for the given `test.` |
| * |
| * @param {Test} test |
| */ |
| XUnit.prototype.test = function(test) { |
| Base.useColors = false; |
| |
| var attrs = { |
| classname: test.parent.fullTitle(), |
| name: test.title, |
| time: test.duration / 1000 || 0 |
| }; |
| |
| if (test.state === STATE_FAILED) { |
| var err = test.err; |
| var diff = |
| Base.hideDiff || !err.actual || !err.expected |
| ? '' |
| : '\n' + Base.generateDiff(err.actual, err.expected); |
| this.write( |
| tag( |
| 'testcase', |
| attrs, |
| false, |
| tag( |
| 'failure', |
| {}, |
| false, |
| escape(err.message) + escape(diff) + '\n' + escape(err.stack) |
| ) |
| ) |
| ); |
| } else if (test.isPending()) { |
| this.write(tag('testcase', attrs, false, tag('skipped', {}, true))); |
| } else { |
| this.write(tag('testcase', attrs, true)); |
| } |
| }; |
| |
| /** |
| * HTML tag helper. |
| * |
| * @param name |
| * @param attrs |
| * @param close |
| * @param content |
| * @return {string} |
| */ |
| function tag(name, attrs, close, content) { |
| var end = close ? '/>' : '>'; |
| var pairs = []; |
| var tag; |
| |
| for (var key in attrs) { |
| if (Object.prototype.hasOwnProperty.call(attrs, key)) { |
| pairs.push(key + '="' + escape(attrs[key]) + '"'); |
| } |
| } |
| |
| tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; |
| if (content) { |
| tag += content + '</' + name + end; |
| } |
| return tag; |
| } |
| |
| XUnit.description = 'XUnit-compatible XML output'; |