1 | 'use-strict';
|
2 |
|
3 | var xml = require('xml');
|
4 | var Base = require('mocha').reporters.Base;
|
5 | var fs = require('fs');
|
6 | var path = require('path');
|
7 | var debug = require('debug')('mocha-junit-reporter');
|
8 | var mkdirp = require('mkdirp');
|
9 | var md5 = require('md5');
|
10 |
|
11 | module.exports = MochaJUnitReporter;
|
12 |
|
13 |
|
14 | var INVALID_CHARACTERS = ['\u001b'];
|
15 |
|
16 | function configureDefaults(options) {
|
17 | debug(options);
|
18 | options = options || {};
|
19 | options = options.reporterOptions || {};
|
20 | options.mochaFile = options.mochaFile || process.env.MOCHA_FILE || 'test-results.xml';
|
21 | options.properties = options.properties || parsePropertiesFromEnv(process.env.PROPERTIES) || null;
|
22 | options.toConsole = !!options.toConsole;
|
23 | options.suiteTitleSeparedBy = options.suiteTitleSeparedBy || ' ';
|
24 | options.rootSuiteTitle = 'Root Suite';
|
25 |
|
26 | return options;
|
27 | }
|
28 |
|
29 | function defaultSuiteTitle(suite) {
|
30 | if (suite.root && suite.title === '') {
|
31 | return this._options.rootSuiteTitle;
|
32 | }
|
33 | return suite.title;
|
34 | }
|
35 |
|
36 | function fullSuiteTitle(suite) {
|
37 | var parent = suite.parent;
|
38 | var title = [ suite.title ];
|
39 |
|
40 | while (parent) {
|
41 | if (parent.root && parent.title === '') {
|
42 | title.unshift(this._options.rootSuiteTitle);
|
43 | } else {
|
44 | title.unshift(parent.title);
|
45 | }
|
46 | parent = parent.parent;
|
47 | }
|
48 |
|
49 | return title.join(this._options.suiteTitleSeparedBy);
|
50 | }
|
51 |
|
52 | function isInvalidSuite(suite) {
|
53 | return (!suite.root && suite.title === '') || (suite.tests.length === 0 && suite.suites.length === 0);
|
54 | }
|
55 |
|
56 | function parsePropertiesFromEnv(envValue) {
|
57 | var properties = null;
|
58 |
|
59 | if (process.env.PROPERTIES) {
|
60 | properties = {}
|
61 | var propertiesArray = process.env.PROPERTIES.split(',');
|
62 | for (var i = 0; i < propertiesArray.length; i++) {
|
63 | var propertyArgs = propertiesArray[i].split(':');
|
64 | properties[propertyArgs[0]] = propertyArgs[1];
|
65 | }
|
66 | }
|
67 |
|
68 | return properties;
|
69 | }
|
70 |
|
71 | function generateProperties(options) {
|
72 | var properties = [];
|
73 | for (var propertyName in options.properties) {
|
74 | if (options.properties.hasOwnProperty(propertyName)) {
|
75 | properties.push({
|
76 | property: {
|
77 | _attr: {
|
78 | name: propertyName,
|
79 | value: options.properties[propertyName]
|
80 | }
|
81 | }
|
82 | })
|
83 | }
|
84 | }
|
85 | return properties;
|
86 | }
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 | function MochaJUnitReporter(runner, options) {
|
95 | this._options = configureDefaults(options);
|
96 | this._runner = runner;
|
97 | this._generateSuiteTitle = this._options.useFullSuiteTitle ? fullSuiteTitle : defaultSuiteTitle;
|
98 |
|
99 | var testsuites = [];
|
100 |
|
101 | function lastSuite() {
|
102 | return testsuites[testsuites.length - 1].testsuite;
|
103 | }
|
104 |
|
105 |
|
106 | Base.call(this, runner);
|
107 |
|
108 |
|
109 | this._runner.on('start', function() {
|
110 | if (fs.existsSync(this._options.mochaFile)) {
|
111 | debug('removing report file', this._options.mochaFile);
|
112 | fs.unlinkSync(this._options.mochaFile);
|
113 | }
|
114 | }.bind(this));
|
115 |
|
116 | this._runner.on('suite', function(suite) {
|
117 | if (!isInvalidSuite(suite)) {
|
118 | testsuites.push(this.getTestsuiteData(suite));
|
119 | }
|
120 | }.bind(this));
|
121 |
|
122 | this._runner.on('pass', function(test) {
|
123 | lastSuite().push(this.getTestcaseData(test));
|
124 | }.bind(this));
|
125 |
|
126 | this._runner.on('fail', function(test, err) {
|
127 | lastSuite().push(this.getTestcaseData(test, err));
|
128 | }.bind(this));
|
129 |
|
130 | if (this._options.includePending) {
|
131 | this._runner.on('pending', function(test) {
|
132 | var testcase = this.getTestcaseData(test);
|
133 |
|
134 | testcase.testcase.push({ skipped: null });
|
135 | lastSuite().push(testcase);
|
136 | }.bind(this));
|
137 | }
|
138 |
|
139 | this._runner.on('end', function(){
|
140 | this.flush(testsuites);
|
141 | }.bind(this));
|
142 | }
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 | MochaJUnitReporter.prototype.getTestsuiteData = function(suite) {
|
150 | var testSuite = {
|
151 | testsuite: [
|
152 | {
|
153 | _attr: {
|
154 | name: this._generateSuiteTitle(suite),
|
155 | timestamp: new Date().toISOString().slice(0,-5),
|
156 | tests: suite.tests.length
|
157 | }
|
158 | }
|
159 | ]
|
160 | }
|
161 |
|
162 | var properties = generateProperties(this._options);
|
163 | if (properties.length) {
|
164 | testSuite.testsuite.push({
|
165 | properties: properties
|
166 | });
|
167 | }
|
168 |
|
169 | return testSuite;
|
170 | };
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 | MochaJUnitReporter.prototype.getTestcaseData = function(test, err) {
|
179 | var config = {
|
180 | testcase: [{
|
181 | _attr: {
|
182 | name: test.fullTitle(),
|
183 | time: (typeof test.duration === 'undefined') ? 0 : test.duration / 1000,
|
184 | classname: test.title
|
185 | }
|
186 | }]
|
187 | };
|
188 | if (err) {
|
189 | var message;
|
190 | if (err.message && typeof err.message.toString === 'function') {
|
191 | message = err.message + '';
|
192 | } else if (typeof err.inspect === 'function') {
|
193 | message = err.inspect() + '';
|
194 | } else {
|
195 | message = '';
|
196 | }
|
197 | var failureMessage = err.stack || message;
|
198 | var failureElement = {
|
199 | _cdata: this.removeInvalidCharacters(failureMessage)
|
200 | };
|
201 |
|
202 | config.testcase.push({failure: failureElement});
|
203 | }
|
204 | return config;
|
205 | };
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | MochaJUnitReporter.prototype.removeInvalidCharacters = function(input){
|
212 | return INVALID_CHARACTERS.reduce(function (text, invalidCharacter) {
|
213 | return text.replace(new RegExp(invalidCharacter, 'g'), '');
|
214 | }, input);
|
215 | };
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 | MochaJUnitReporter.prototype.flush = function(testsuites){
|
222 | var xml = this.getXml(testsuites);
|
223 |
|
224 | this.writeXmlToDisk(xml, this._options.mochaFile);
|
225 |
|
226 | if (this._options.toConsole === true) {
|
227 | console.log(xml);
|
228 | }
|
229 | };
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 | MochaJUnitReporter.prototype.getXml = function(testsuites) {
|
238 | var totalSuitesTime = 0;
|
239 | var totalTests = 0;
|
240 | var stats = this._runner.stats;
|
241 | var hasProperties = !!this._options.properties;
|
242 |
|
243 | testsuites.forEach(function(suite) {
|
244 | var _suiteAttr = suite.testsuite[0]._attr;
|
245 |
|
246 |
|
247 | var _casesIndex = hasProperties ? 2 : 1;
|
248 | var _cases = suite.testsuite.slice(_casesIndex);
|
249 |
|
250 | _suiteAttr.failures = 0;
|
251 | _suiteAttr.time = 0;
|
252 | _suiteAttr.skipped = 0;
|
253 |
|
254 | _cases.forEach(function(testcase) {
|
255 | var lastNode = testcase.testcase[testcase.testcase.length - 1];
|
256 |
|
257 | _suiteAttr.skipped += Number('skipped' in lastNode);
|
258 | _suiteAttr.failures += Number('failure' in lastNode);
|
259 | _suiteAttr.time += testcase.testcase[0]._attr.time;
|
260 | });
|
261 |
|
262 | if (!_suiteAttr.skipped) {
|
263 | delete _suiteAttr.skipped;
|
264 | }
|
265 |
|
266 | totalSuitesTime += _suiteAttr.time;
|
267 | totalTests += _suiteAttr.tests;
|
268 | });
|
269 |
|
270 | var rootSuite = {
|
271 | _attr: {
|
272 | name: 'Mocha Tests',
|
273 | time: totalSuitesTime,
|
274 | tests: totalTests,
|
275 | failures: stats.failures
|
276 | }
|
277 | };
|
278 |
|
279 | if (stats.pending) {
|
280 | rootSuite._attr.skipped = stats.pending;
|
281 | }
|
282 |
|
283 | return xml({
|
284 | testsuites: [ rootSuite ].concat(testsuites)
|
285 | }, { declaration: true, indent: ' ' });
|
286 | };
|
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 | MochaJUnitReporter.prototype.writeXmlToDisk = function(xml, filePath){
|
294 | if (filePath) {
|
295 | if (filePath.indexOf('[hash]') !== -1) {
|
296 | filePath = filePath.replace('[hash]', md5(xml));
|
297 | }
|
298 |
|
299 | debug('writing file to', filePath);
|
300 | mkdirp.sync(path.dirname(filePath));
|
301 |
|
302 | fs.writeFileSync(filePath, xml, 'utf-8');
|
303 | debug('results written successfully');
|
304 | }
|
305 | };
|