find-node-modules
Version:
Return an array of all parent node_modules directories
326 lines (275 loc) • 9 kB
JavaScript
'use-strict';
var xml = require('xml');
var Base = require('mocha').reporters.Base;
var fs = require('fs');
var path = require('path');
var debug = require('debug')('mocha-junit-reporter');
var mkdirp = require('mkdirp');
var md5 = require('md5');
var stripAnsi = require('strip-ansi');
module.exports = MochaJUnitReporter;
// A subset of invalid characters as defined in http://www.w3.org/TR/xml/#charsets that can occur in e.g. stacktraces
var INVALID_CHARACTERS = ['\u001b'];
function configureDefaults(options) {
debug(options);
options = options || {};
options = options.reporterOptions || {};
options.mochaFile = options.mochaFile || process.env.MOCHA_FILE || 'test-results.xml';
options.properties = options.properties || parsePropertiesFromEnv(process.env.PROPERTIES) || null;
options.toConsole = !!options.toConsole;
options.testCaseSwitchClassnameAndName = options.testCaseSwitchClassnameAndName || false;
options.suiteTitleSeparedBy = options.suiteTitleSeparedBy || ' ';
options.suiteTitleSeparatedBy = options.suiteTitleSeparatedBy || options.suiteTitleSeparedBy || ' ';
options.rootSuiteTitle = options.rootSuiteTitle || 'Root Suite';
options.testsuitesTitle = options.testsuitesTitle || 'Mocha Tests';
return options;
}
function defaultSuiteTitle(suite) {
if (suite.root && suite.title === '') {
return stripAnsi(this._options.rootSuiteTitle);
}
return stripAnsi(suite.title);
}
function fullSuiteTitle(suite) {
var parent = suite.parent;
var title = [ suite.title ];
while (parent) {
if (parent.root && parent.title === '') {
title.unshift(this._options.rootSuiteTitle);
} else {
title.unshift(parent.title);
}
parent = parent.parent;
}
return stripAnsi(title.join(this._options.suiteTitleSeparatedBy));
}
function isInvalidSuite(suite) {
return (!suite.root && suite.title === '') || (suite.tests.length === 0 && suite.suites.length === 0);
}
function parsePropertiesFromEnv(envValue) {
var properties = null;
if (envValue) {
properties = {};
var propertiesArray = envValue.split(',');
for (var i = 0; i < propertiesArray.length; i++) {
var propertyArgs = propertiesArray[i].split(':');
properties[propertyArgs[0]] = propertyArgs[1];
}
}
return properties;
}
function generateProperties(options) {
var properties = [];
for (var propertyName in options.properties) {
if (options.properties.hasOwnProperty(propertyName)) {
properties.push({
property: {
_attr: {
name: propertyName,
value: options.properties[propertyName]
}
}
});
}
}
return properties;
}
/**
* JUnit reporter for mocha.js.
* @module mocha-junit-reporter
* @param {EventEmitter} runner - the test runner
* @param {Object} options - mocha options
*/
function MochaJUnitReporter(runner, options) {
this._options = configureDefaults(options);
this._runner = runner;
this._generateSuiteTitle = this._options.useFullSuiteTitle ? fullSuiteTitle : defaultSuiteTitle;
var testsuites = [];
function lastSuite() {
return testsuites[testsuites.length - 1].testsuite;
}
// get functionality from the Base reporter
Base.call(this, runner);
// remove old results
this._runner.on('start', function() {
if (fs.existsSync(this._options.mochaFile)) {
debug('removing report file', this._options.mochaFile);
fs.unlinkSync(this._options.mochaFile);
}
}.bind(this));
this._runner.on('suite', function(suite) {
if (!isInvalidSuite(suite)) {
testsuites.push(this.getTestsuiteData(suite));
}
}.bind(this));
this._runner.on('pass', function(test) {
lastSuite().push(this.getTestcaseData(test));
}.bind(this));
this._runner.on('fail', function(test, err) {
lastSuite().push(this.getTestcaseData(test, err));
}.bind(this));
if (this._options.includePending) {
this._runner.on('pending', function(test) {
var testcase = this.getTestcaseData(test);
testcase.testcase.push({ skipped: null });
lastSuite().push(testcase);
}.bind(this));
}
this._runner.on('end', function(){
this.flush(testsuites);
}.bind(this));
}
/**
* Produces an xml node for a test suite
* @param {Object} suite - a test suite
* @return {Object} - an object representing the xml node
*/
MochaJUnitReporter.prototype.getTestsuiteData = function(suite) {
var testSuite = {
testsuite: [
{
_attr: {
name: this._generateSuiteTitle(suite),
timestamp: new Date().toISOString().slice(0,-5),
tests: suite.tests.length
}
}
]
};
if(suite.file) {
testSuite.testsuite[0]._attr.file = suite.file;
}
var properties = generateProperties(this._options);
if (properties.length) {
testSuite.testsuite.push({
properties: properties
});
}
return testSuite;
};
/**
* Produces an xml config for a given test case.
* @param {object} test - test case
* @param {object} err - if test failed, the failure object
* @returns {object}
*/
MochaJUnitReporter.prototype.getTestcaseData = function(test, err) {
var flipClassAndName = this._options.testCaseSwitchClassnameAndName;
var name = stripAnsi(test.fullTitle());
var classname = stripAnsi(test.title)
var config = {
testcase: [{
_attr: {
name: flipClassAndName ? classname : name,
time: (typeof test.duration === 'undefined') ? 0 : test.duration / 1000,
classname: flipClassAndName ? name : classname
}
}]
};
if (err) {
var message;
if (err.message && typeof err.message.toString === 'function') {
message = err.message + '';
} else if (typeof err.inspect === 'function') {
message = err.inspect() + '';
} else {
message = '';
}
var failureMessage = err.stack || message;
var failureElement = {
_attr: {
message: err.message || '',
type: err.name || ''
},
_cdata: this.removeInvalidCharacters(failureMessage)
};
config.testcase.push({failure: failureElement});
}
return config;
};
/**
* @param {string} input
* @returns {string} without invalid characters
*/
MochaJUnitReporter.prototype.removeInvalidCharacters = function(input){
return INVALID_CHARACTERS.reduce(function (text, invalidCharacter) {
return text.replace(new RegExp(invalidCharacter, 'g'), '');
}, input);
};
/**
* Writes xml to disk and ouputs content if "toConsole" is set to true.
* @param {Array.<Object>} testsuites - a list of xml configs
*/
MochaJUnitReporter.prototype.flush = function(testsuites){
var xml = this.getXml(testsuites);
this.writeXmlToDisk(xml, this._options.mochaFile);
if (this._options.toConsole === true) {
console.log(xml); // eslint-disable-line no-console
}
};
/**
* Produces an XML string from the given test data.
* @param {Array.<Object>} testsuites - a list of xml configs
* @returns {string}
*/
MochaJUnitReporter.prototype.getXml = function(testsuites) {
var totalSuitesTime = 0;
var totalTests = 0;
var stats = this._runner.stats;
var hasProperties = !!this._options.properties;
testsuites.forEach(function(suite) {
var _suiteAttr = suite.testsuite[0]._attr;
// properties are added before test cases so we want to make sure that we are grabbing test cases
// at the correct index
var _casesIndex = hasProperties ? 2 : 1;
var _cases = suite.testsuite.slice(_casesIndex);
_suiteAttr.failures = 0;
_suiteAttr.time = 0;
_suiteAttr.skipped = 0;
_cases.forEach(function(testcase) {
var lastNode = testcase.testcase[testcase.testcase.length - 1];
_suiteAttr.skipped += Number('skipped' in lastNode);
_suiteAttr.failures += Number('failure' in lastNode);
_suiteAttr.time += testcase.testcase[0]._attr.time;
});
if (!_suiteAttr.skipped) {
delete _suiteAttr.skipped;
}
totalSuitesTime += _suiteAttr.time;
totalTests += _suiteAttr.tests;
});
var rootSuite = {
_attr: {
name: this._options.testsuitesTitle,
time: totalSuitesTime,
tests: totalTests,
failures: stats.failures
}
};
if (stats.pending) {
rootSuite._attr.skipped = stats.pending;
}
return xml({
testsuites: [ rootSuite ].concat(testsuites)
}, { declaration: true, indent: ' ' });
};
/**
* Writes a JUnit test report XML document.
* @param {string} xml - xml string
* @param {string} filePath - path to output file
*/
MochaJUnitReporter.prototype.writeXmlToDisk = function(xml, filePath){
if (filePath) {
if (filePath.indexOf('[hash]') !== -1) {
filePath = filePath.replace('[hash]', md5(xml));
}
debug('writing file to', filePath);
mkdirp.sync(path.dirname(filePath));
try {
fs.writeFileSync(filePath, xml, 'utf-8');
} catch (exc) {
debug('problem writing results: ' + exc);
}
debug('results written successfully');
}
};