Coverage

39%
78
31
47

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

39%
78
31
47
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));
1501 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 start: Math.round(new Date().getTime() / 1000),
183 name: name + ' [' + this.browser + ']',
184 end: null,
185 totalTests: 0
186 }
187 });
1880 return this;
189 },
190
191 /**
192 * Finishes XML skeleton for testsuites
193 *
194 * @method testsuiteFinished
195 * @chainable
196 */
197
198 testsuiteFinished: function () {
1990 this.xml[0].children[this.testIdx].attrs.end = Math.round(new Date().getTime() / 1000);
2000 return this;
201 },
202
203 /**
204 * Generates XML skeleton for an assertion
205 *
206 * @method assertion
207 * @param {object} data Event data
208 * @chainable
209 */
210
211 assertion: function (data) {
2120 var timestamp = Math.round(new Date().getTime() / 1000);
2130 this.xml[0].children[this.testIdx].children[this.testCount].children.push({
214 name: 'variation',
215 attrs: {
216 start: timestamp,
217 name: data.type,
218 end: null
219 },
220 children: [
221 {name: 'severity', text: (data.success ? 'pass' : 'fail') },
222 {name: 'description', text: jsonxml.cdata((data.message ? data.message : 'Expected: ' + data.expected + 'Actual: ' + data.value)) },
223 {name: 'resource', text: 'DalekJSTest'}
224 ]
225 });
226
2270 if (this.variationCount > -1 && this.xml[0].children[this.testIdx].children[this.testCount].children[this.variationCount]) {
2280 this.xml[0].children[this.testIdx].children[this.testCount].children[this.variationCount].attrs.end = timestamp;
229 }
230
2310 this.variationCount++;
2320 return this;
233 },
234
235 /**
236 * Generates XML skeleton for a testcase
237 *
238 * @method testStarted
239 * @param {object} data Event data
240 * @chainable
241 */
242
243 testStarted: function (data) {
2440 this.variationCount = -1;
2450 this.xml[0].children[this.testIdx].children.push({
246 name: 'testcase',
247 children: [],
248 attrs: {
249 start: Math.round(new Date().getTime() / 1000),
250 name: data.name,
251 end: null,
252 result: null
253 }
254 });
255
2560 return this;
257 },
258
259 /**
260 * Finishes XML skeleton for a testcase
261 *
262 * @method testFinished
263 * @param {object} data Event data
264 * @chainable
265 */
266
267 testFinished: function (data) {
2680 var timestamp = Math.round(new Date().getTime() / 1000);
2690 this.xml[0].children[this.testIdx].children[this.testCount].attrs.end = timestamp;
2700 this.xml[0].children[this.testIdx].children[this.testCount].attrs.result = data.status ? 'Passed' : 'Failed';
271
2720 if (this.variationCount > -1) {
2730 this.xml[0].children[this.testIdx].children[this.testCount].children[this.variationCount].attrs.end = timestamp;
274 }
275
2760 this.testCount++;
2770 this.variationCount = -1;
2780 this.xml[0].children[this.testIdx].attrs.totalTests = this.testCount;
2790 return this;
280 },
281
282 /**
283 * Finishes XML and writes file to the file system
284 *
285 * @method runnerFinished
286 * @param {object} data Event data
287 * @chainable
288 */
289
290 runnerFinished: function (data) {
2910 this.data.elapsedTime = data.elapsedTime;
2920 this.data.status = data.status;
2930 this.data.assertions = data.assertions;
2940 this.data.assertionsFailed = data.assertionsFailed;
2950 this.data.assertionsPassed = data.assertionsPassed;
296
2970 var contents = jsonxml(this.xml, {escape: true, removeIllegalNameCharacters: true, prettyPrint: true, xmlHeader: 'version="1.0" encoding="UTF-8"'});
298
2990 if (path.extname(this.dest) !== '.xml') {
3000 this.dest = this.dest + '/dalek.xml';
301 }
302
3030 this.events.emit('report:written', {type: 'junit', dest: this.dest});
3040 this._recursiveMakeDirSync(path.dirname(this.dest.replace(path.basename(this.dest, ''))));
3050 fs.writeFileSync(this.dest, contents, 'utf8');
306 },
307
308 /**
309 * Helper method to generate deeper nested directory structures
310 *
311 * @method _recursiveMakeDirSync
312 * @param {string} path Path to create
313 */
314
315 _recursiveMakeDirSync: function (path) {
3160 var pathSep = require('path').sep;
3170 var dirs = path.split(pathSep);
3180 var root = '';
319
3200 while (dirs.length > 0) {
3210 var dir = dirs.shift();
3220 if (dir === '') {
3230 root = pathSep;
324 }
3250 if (!fs.existsSync(root + dir)) {
3260 fs.mkdirSync(root + dir);
327 }
3280 root += dir + pathSep;
329 }
330 }
331};
332