xunit-file
Version:
Basically the same reporter as mocha's xunit reporter, but writes the output in a file.
216 lines (177 loc) • 5.5 kB
JavaScript
/**
* Module dependencies.
*/
var mocha = require("mocha")
, Base = mocha.reporters.Base
, utils = mocha.utils
, escape = utils.escape
, config = require("../config.json")
, fs = require("fs")
, path = require("path")
, mkdirp = require("mkdirp")
, dateFormat = require('dateformat')
, filePathParser = require('./file-path-parser')(process, global.Date, dateFormat)
, filePath = filePathParser(process.env.XUNIT_FILE || config.file || "${cwd}/xunit.xml")
, consoleOutput = process.env.XUNIT_SILENT ? {} : config.consoleOutput || {};
/**
* Save timer references to avoid Sinon interfering (see GH-237).
*/
var Date = global.Date
, setTimeout = global.setTimeout
, setInterval = global.setInterval
, clearTimeout = global.clearTimeout
, clearInterval = global.clearInterval;
/**
* Expose `XUnitFile`.
*/
exports = module.exports = XUnitFile;
/**
* Initialize a new `XUnitFile` reporter.
*
* @param {Runner} runner
* @api public
*/
function XUnitFile(runner) {
Base.call(this, runner);
var stats = this.stats
, tests = []
, fd;
mkdirp.sync(path.dirname(filePath));
fd = fs.openSync(filePath, 'w', 0755);
runner.on('suite', function(suite){
if(consoleOutput.suite){
console.log(' ' + suite.title);
}
});
runner.on('test', function(test){
if(consoleOutput.test){
console.log(' ◦ ' + test.title);
}
});
runner.on('pass', function(test){
tests.push(test);
});
runner.on('fail', function(test){
if(consoleOutput.fail){
console.log(' - ' + test.title);
}
tests.push(test);
});
runner.on('pending', function(test) {
tests.push(test);
});
runner.on('end', function(){
var timestampStr = (new Date).toISOString().split('.', 1)[0];
appendLine(fd, tag('testsuite', {
name: process.env.SUITE_NAME || 'Mocha Tests'
, tests: stats.tests
, failures: stats.failures
, errors: 0
, skipped: stats.tests - stats.failures - stats.passes
, timestamp: timestampStr
, time: stats.duration / 1000
}, false));
if( process.env.XUNIT_LOG_ENV) {
logProperties(fd);
}
tests.forEach(function(test){
writeTest(fd, test);
});
appendLine(fd, '</testsuite>');
fs.closeSync(fd);
});
}
/**
* Inherit from `Base.prototype`.
*/
XUnitFile.prototype.__proto__ = Base.prototype;
/**
* Writes a list of process and environment variables to the <properties> section in the XML.
*/
function logProperties(fd) {
var attrs = new Object();
var properties = "\n";
properties += logProperty('process.arch', process.arch);
properties += logProperty('process.platform', process.platform);
properties += logProperty('process.memoryUsage', objectToString(process.memoryUsage()));
properties += logProperty('process.cwd', process.cwd());
properties += logProperty('process.execPath', process.execPath)
properties += logProperty('process.execArgv', process.execArgv.join( ' '));
properties += logProperty('process.argv', process.argv.join( ' '));
properties += logProperty('process.version', process.version.replace('"',''));
properties += logProperty('process.versions', objectToString(process.versions));
properties += logProperty('process.env.PATH', process.env.PATH);
properties += logProperty('process.env.NODE_PATH', process.env.NODE_PATH);
properties += logProperty('process.env.SUITE_NAME', process.env.SUITE_NAME);
properties += logProperty('process.env.XUNIT_FILE', process.env.XUNIT_FILE);
properties += logProperty('process.env.LOG_XUNIT', process.env.LOG_XUNIT);
appendLine(fd, tag('properties', {}, false, properties));
}
/**
* Formats a single property value.
*/
function logProperty( name, value) {
return ' ' + tag('property', { name: name, value: value }, true) + '\n';
}
/**
* Simple utility to convert a flat Object to a readable string.
*/
function objectToString( obj) {
var arrayString = '';
if( obj) {
for (var prop in obj) {
var propValue = '' + obj[prop];
if( arrayString.length > 0) {
arrayString += ', ';
}
arrayString += prop + ": '" + propValue.replace( "'", "\\'") + "'";
}
}
return '[ ' + arrayString + ']';
}
/**
* Output tag for the given `test.`
*/
function writeTest(fd, test) {
var attrs = {
classname: test.parent.fullTitle()
, name: test.title
// , time: test.duration / 1000 //old
,time: test.duration ? test.duration / 1000 : 0 //new
};
if ('failed' == test.state) {
var err = test.err;
appendLine(fd, tag('testcase', attrs, false, tag('failure', { message: escape(err.message) }, false, cdata(err.stack))));
} else if (test.pending) {
delete attrs.time;
appendLine(fd, tag('testcase', attrs, false, tag('skipped', {}, true)));
} else {
appendLine(fd, tag('testcase', attrs, true) );
}
}
/**
* HTML tag helper.
*/
function tag(name, attrs, close, content) {
var end = close ? '/>' : '>'
, pairs = []
, result;
for (var key in attrs) {
pairs.push(key + '="' + escape(attrs[key]) + '"');
}
result = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end;
if (content) result += content + '</' + name + end;
return result;
}
/**
* Return CDATA `str`.
*/
function cdata(str) {
return '<![CDATA[' + str + ']]>';
}
function appendLine(fd, line) {
if (process.env.LOG_XUNIT) {
console.log(line);
}
fs.writeSync(fd, line + "\n", null, 'utf8');
}