218 lines
4.7 KiB
JavaScript
218 lines
4.7 KiB
JavaScript
|
'use strict';
|
||
|
/**
|
||
|
* @module XUnit
|
||
|
*/
|
||
|
/**
|
||
|
* Module dependencies.
|
||
|
*/
|
||
|
|
||
|
var Base = require('./base');
|
||
|
var utils = require('../utils');
|
||
|
var fs = require('fs');
|
||
|
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');
|
||
|
}
|
||
|
|
||
|
fs.mkdirSync(path.dirname(options.reporterOptions.output), {
|
||
|
recursive: true
|
||
|
});
|
||
|
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 && Base.showDiff(err)
|
||
|
? '\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';
|