Coverage

39%
76
30
46

/home/ubuntu/src/github.com/dalekjs/dalek-reporter-junit/index.js

39%
76
30
46
LineHitsSource
1/*!
2 *
3 * Copyright (c) 2013 Sebastian Golasch
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 */
24
251'use strict';
26
27// ext. libs
281var fs = require('fs');
291var path = require('path');
301var jsonxml = require('jsontoxml');
31
32// int. global
331var reporter = null;
34
35/**
36 * The jUnit reporter can produce a jUnit compatible file with the results of your testrun,
37 * this reporter enables you to use daleks testresults within a CI environment like Jenkins.
38 *
39 * The reporter can be installed with the following command:
40 *
41 * ```bash
42 * $ npm install dalek-reporter-junit --save-dev
43 * ```
44 *
45 * The file will follow the jUnit XML format:
46 *
47 * ```html
48 * <?xml version="1.0" encoding="utf-8"?>
49 * <resource name="DalekJSTest">
50 * <testsuite start="1375125067" name="Click - DalekJS guinea pig [Phantomjs]" end="1375125067" totalTests="1">
51 * <testcase start="1375125067" name="Can click a select option (OK, jQuery style, no message)" end="1375125067" result="pass">
52 * <variation start="1375125067" name="val" end="1375125067">
53 * <severity>pass</severity>
54 * <description><![CDATA[David is the favourite]]></description>
55 * <resource>DalekJSTest</resource>
56 * </variation>
57 * <variation start="1375125067" name="val" end="1375125067">
58 * <severity>pass</severity>
59 * <description><![CDATA[Matt is now my favourite, bow ties are cool]]></description>
60 * <resource>DalekJSTest</resource>
61 * </variation>
62 * </testcase>
63 * </testsuite>
64 * </resource>
65 * ```
66 *
67 * By default the file will be written to `report/dalek.xml`,
68 * you can change this by adding a config option to the your Dalekfile
69 *
70 * ```javascript
71 * "junit-reporter": {
72 * "dest": "your/folder/your_file.xml"
73 * }
74 * ```
75 *
76 * If you would like to use the reporter (in addition to the std. console reporter),
77 * you can start dalek with a special command line argument
78 *
79 * ```bash
80 * $ dalek your_test.js -r console,junit
81 * ```
82 *
83 * or you can add it to your Dalekfile
84 *
85 * ```javascript
86 * "reporter": ["console", "junit"]
87 * ```
88 *
89 * @class Reporter
90 * @constructor
91 * @part JUnit
92 * @api
93 */
94
951function Reporter (opts) {
961 this.events = opts.events;
971 this.config = opts.config;
981 this.testCount = 0;
991 this.testIdx = -1;
1001 this.variationCount = -1;
1011 this.data = {};
1021 this.data.tests = [];
1031 this.browser = null;
104
1051 var defaultReportFolder = 'report';
1061 this.dest = this.config.get('junit-reporter') && this.config.get('junit-reporter').dest ? this.config.get('junit-reporter').dest : defaultReportFolder;
107
108 // prepare base xml
1091 this.xml = [
110 {
111 name: 'resource',
112 attrs: {
113 name:'DalekJSTest'
114 },
115 children: []
116 }
117 ];
118
1191 this.startListening();
120}
121
122/**
123 * @module Reporter
124 */
125
1261module.exports = function (opts) {
1271 if (reporter === null) {
1281 reporter = new Reporter(opts);
129 }
130
1311 return reporter;
132};
133
1341Reporter.prototype = {
135
136 /**
137 * Connects to all the event listeners
138 *
139 * @method startListening
140 * @chainable
141 */
142
143 startListening: function () {
1441 this.events.on('report:run:browser', this.runBrowser.bind(this));
1451 this.events.on('report:assertion', this.assertion.bind(this));
1461 this.events.on('report:test:started', this.testStarted.bind(this));
1471 this.events.on('report:test:finished', this.testFinished.bind(this));
1481 this.events.on('report:runner:finished', this.runnerFinished.bind(this));
1491 this.events.on('report:testsuite:started', this.testsuiteStarted.bind(this));
150 //this.events.on('report:testsuite:finished', this.testsuiteFinished.bind(this));
1511 return this;
152 },
153
154 /**
155 * Stores the current browser name
156 *
157 * @method runBrowser
158 * @param {string} browser Browser name
159 * @chainable
160 */
161
162 runBrowser: function (browser) {
1630 this.browser = browser;
1640 return this;
165 },
166
167 /**
168 * Generates XML skeleton for testsuites
169 *
170 * @method testsuiteStarted
171 * @param {string} name Testsuite name
172 * @chainable
173 */
174
175 testsuiteStarted: function (name) {
1760 this.testCount = 0;
1770 this.testIdx++;
1780 this.xml[0].children.push({
179 name: 'testsuite',
180 children: [],
181 attrs: {
182 name: name + ' [' + this.browser + ']',
183 }
184 });
1850 return this;
186 },
187
188 /**
189 * Finishes XML skeleton for testsuites
190 *
191 * @method testsuiteFinished
192 * @chainable
193 */
194
195 testsuiteFinished: function () {
196 // this.xml[0].children[this.testIdx].attrs.end = Math.round(new Date().getTime() / 1000);
1970 return this;
198 },
199
200 /**
201 * Generates XML skeleton for an assertion
202 *
203 * @method assertion
204 * @param {object} data Event data
205 * @chainable
206 */
207
208 assertion: function (data) {
2090 if (! data.success) {
210 //var timestamp = Math.round(new Date().getTime() / 1000);
2110 this.xml[0].children[this.testIdx].children[this.testCount].children.push({
212 name: 'failure',
213 attrs: {
214 name: data.type,
215 message: (data.message ? data.message : 'Expected: ' + data.expected + 'Actual: ' + data.value)
216 }
217 });
218
219 //if (this.variationCount > -1 && this.xml[0].children[this.testIdx].children[this.testCount].children[this.variationCount]) {
220 //this.xml[0].children[this.testIdx].children[this.testCount].children[this.variationCount].attrs.end = timestamp;
221 //}
222
2230 this.variationCount++;
224 }
225
2260 return this;
227 },
228
229 /**
230 * Generates XML skeleton for a testcase
231 *
232 * @method testStarted
233 * @param {object} data Event data
234 * @chainable
235 */
236
237 testStarted: function (data) {
2380 this.variationCount = -1;
2390 this.xml[0].children[this.testIdx].children.push({
240 name: 'testcase',
241 children: [],
242 attrs: {
243 classname: this.xml[0].children[this.testIdx].attrs.name,
244 name: data.name
245 }
246 });
247
2480 return this;
249 },
250
251 /**
252 * Finishes XML skeleton for a testcase
253 *
254 * @method testFinished
255 * @param {object} data Event data
256 * @chainable
257 */
258
259 testFinished: function () {
260 //var timestamp = Math.round(new Date().getTime() / 1000);
261
2620 if (this._checkNodeAttributes(this.testIdx, this.testCount)) {
2630 this.xml[0].children[this.testIdx].children[this.testCount].attrs = {};
264 }
265 //this.xml[0].children[this.testIdx].children[this.testCount].attrs.end = timestamp;
266 //this.xml[0].children[this.testIdx].children[this.testCount].attrs.result = data.status ? 'Passed' : 'Failed';
267
2680 if (this.variationCount > -1) {
2690 if (this._checkNodeAttributes(this.testIdx, this.testCount, this.variationCount)) {
2700 this.xml[0].children[this.testIdx].children[this.testCount].children[this.variationCount].attrs = {};
271 }
272 //this.xml[0].children[this.testIdx].children[this.testCount].children[this.variationCount].attrs.end = timestamp;
273 }
274
2750 this.testCount++;
2760 this.variationCount = -1;
2770 return this;
278 },
279
280 /**
281 * Finishes XML and writes file to the file system
282 *
283 * @method runnerFinished
284 * @param {object} data Event data
285 * @chainable
286 */
287
288 runnerFinished: function (data) {
2890 this.data.elapsedTime = data.elapsedTime;
2900 this.data.status = data.status;
2910 this.data.assertions = data.assertions;
2920 this.data.assertionsFailed = data.assertionsFailed;
2930 this.data.assertionsPassed = data.assertionsPassed;
294
2950 var contents = jsonxml(this.xml, {escape: true, removeIllegalNameCharacters: true, prettyPrint: true, xmlHeader: 'version="1.0" encoding="UTF-8"'});
296
2970 if (path.extname(this.dest) !== '.xml') {
2980 this.dest = this.dest + '/dalek.xml';
299 }
300
3010 this.events.emit('report:written', {type: 'junit', dest: this.dest});
3020 this._recursiveMakeDirSync(path.dirname(this.dest.replace(path.basename(this.dest, ''))));
3030 fs.writeFileSync(this.dest, contents, 'utf8');
304 },
305
306 /**
307 * Helper method to generate deeper nested directory structures
308 *
309 * @method _recursiveMakeDirSync
310 * @param {string} path PAth to create
311 */
312
313 _recursiveMakeDirSync: function (path) {
3140 var pathSep = require('path').sep;
3150 var dirs = path.split(pathSep);
3160 var root = '';
317
3180 while (dirs.length > 0) {
3190 var dir = dirs.shift();
3200 if (dir === '') {
3210 root = pathSep;
322 }
3230 if (!fs.existsSync(root + dir)) {
3240 fs.mkdirSync(root + dir);
325 }
3260 root += dir + pathSep;
327 }
328 },
329
330 /**
331 * Helper method to check if attributes should be set to an empty object literal
332 *
333 * @method _checkNodeAttributes
334 * @param {string} testIdx Id of the test node
335 * @param {string} testCount Id of the child node
336 * @param {string} variationCount Id of the testCount child node
337 */
338
339 _checkNodeAttributes: function (testIdx, testCount, variationCount) {
3400 if (variationCount === undefined) {
3410 return typeof this.xml[0].children[testIdx].children[testCount].attrs === 'undefined';
342 }
343
3440 return typeof this.xml[0].children[testIdx].children[testCount].children[variationCount].attrs === 'undefined';
345 }
346};
347