UNPKG

11.6 kBJavaScriptView Raw
1/* global __phantom_writeFile */
2(function(global) {
3 var UNDEFINED,
4 exportObject;
5
6 if (typeof module !== "undefined" && module.exports) {
7 exportObject = exports;
8 } else {
9 exportObject = global.jasmineReporters = global.jasmineReporters || {};
10 }
11
12 function elapsed(start, end) { return (end - start)/1000; }
13 function isFailed(obj) { return obj.status === "failed"; }
14 function isSkipped(obj) { return obj.status === "pending"; }
15 function isDisabled(obj) { return obj.status === "disabled"; }
16 function pad(n) { return n < 10 ? "0"+n : n; }
17 function extend(dupe, obj) { // performs a shallow copy of all props of `obj` onto `dupe`
18 for (var prop in obj) {
19 if (obj.hasOwnProperty(prop)) {
20 dupe[prop] = obj[prop];
21 }
22 }
23 return dupe;
24 }
25 function escapeInvalidXmlChars(str) {
26 return str.replace(/&/g, "&amp;")
27 .replace(/</g, "&lt;")
28 .replace(/>/g, "&gt;")
29 .replace(/"/g, "&quot;")
30 .replace(/'/g, "&apos;");
31 }
32 function dateString(date) {
33 var year = date.getFullYear();
34 var month = date.getMonth()+1; // 0-based
35 var day = date.getDate();
36 return year + "-" + pad(month) + "-" + pad(day);
37 }
38 function timeString(date) {
39 var hours = date.getHours();
40 var minutes = date.getMinutes();
41 var seconds = date.getSeconds();
42 return pad(hours) + ":" + pad(minutes) + ":" + pad(seconds);
43 }
44 function getQualifiedFilename(path, filename, separator) {
45 if (path && path.substr(-1) !== separator && filename.substr(0) !== separator) {
46 path += separator;
47 }
48 return path + filename;
49 }
50 function log(str) {
51 var con = global.console || console;
52 if (con && con.log) {
53 con.log(str);
54 }
55 }
56
57
58 /**
59 * Generates NUnit XML for the given spec run.
60 * Allows the test results to be used in java based CI
61 * systems like Jenkins.
62 *
63 * Originally from https://github.com/gmusick/jasmine-reporters. Adapted
64 * to support file output via PhantomJS/Rhino/Node.js like JUnitXmlReporter.
65 * Also fixed a couple minor bugs (ie month being reported incorrectly) and
66 * added a few options to control how / where the file is generated.
67 *
68 * Usage:
69 *
70 * jasmine.getEnv().addReporter(new jasmineReporters.NUnitXmlReporter(options);
71 *
72 * @param {object} [options]
73 * @param {string} [options.savePath] directory to save the files (default: '')
74 * @param {string} [options.filename] name of xml output file (default: 'nunitresults.xml')
75 * @param {string} [options.reportName] name for parent test-results node (default: 'Jasmine Results')
76 */
77 exportObject.NUnitXmlReporter = function(options) {
78 var self = this;
79 self.started = false;
80 self.finished = false;
81 // sanitize arguments
82 options = options || {};
83 self.savePath = options.savePath || "";
84 self.filename = options.filename || "nunitresults.xml";
85 self.reportName = options.reportName || "Jasmine Results";
86
87 var suites = [],
88 currentSuite = null,
89 totalSpecsExecuted = 0,
90 totalSpecsSkipped = 0,
91 totalSpecsDisabled = 0,
92 totalSpecsFailed = 0,
93 totalSpecsDefined,
94 // when use use fit, jasmine never calls suiteStarted / suiteDone, so make a fake one to use
95 fakeFocusedSuite = {
96 id: "focused",
97 description: "focused specs",
98 fullName: "focused specs"
99 };
100
101 var __suites = {}, __specs = {};
102 function getSuite(suite) {
103 __suites[suite.id] = extend(__suites[suite.id] || {}, suite);
104 return __suites[suite.id];
105 }
106 function getSpec(spec, suite) {
107 __specs[spec.id] = extend(__specs[spec.id] || {}, spec);
108 var ret = __specs[spec.id];
109 if (suite && !ret._suite) {
110 ret._suite = suite;
111 suite._specs.push(ret);
112 }
113 return ret;
114 }
115
116 self.jasmineStarted = function(summary) {
117 totalSpecsDefined = summary && summary.totalSpecsDefined || NaN;
118 exportObject.startTime = new Date();
119 self.started = true;
120 };
121 self.suiteStarted = function(suite) {
122 suite = getSuite(suite);
123 suite._startTime = new Date();
124 suite._specs = [];
125 suite._suites = [];
126 suite._failures = 0;
127 suite._nestedFailures = 0;
128 suite._skipped = 0;
129 suite._disabled = 0;
130 suite._parent = currentSuite;
131 if (!currentSuite) {
132 suites.push(suite);
133 } else {
134 currentSuite._suites.push(suite);
135 }
136 currentSuite = suite;
137 };
138 self.specStarted = function(spec) {
139 if (!currentSuite) {
140 // focused spec (fit) -- suiteStarted was never called
141 self.suiteStarted(fakeFocusedSuite);
142 }
143 spec = getSpec(spec, currentSuite);
144 spec._startTime = new Date();
145 };
146 self.specDone = function(spec) {
147 spec = getSpec(spec, currentSuite);
148 spec._endTime = new Date();
149 if (isSkipped(spec)) {
150 spec._suite._skipped++;
151 totalSpecsSkipped++;
152 }
153 if (isDisabled(spec)) {
154 spec._suite._disabled++;
155 totalSpecsDisabled++;
156 }
157 if (isFailed(spec)) {
158 spec._suite._failures++;
159 // NUnit wants to know nested failures, so add for parents too
160 for (var parent=spec._suite._parent; parent; parent=parent._parent) {
161 parent._nestedFailures++;
162 }
163 totalSpecsFailed++;
164 }
165 totalSpecsExecuted++;
166 };
167 self.suiteDone = function(suite) {
168 suite = getSuite(suite);
169 if (suite._parent === UNDEFINED) {
170 // disabled suite (xdescribe) -- suiteStarted was never called
171 self.suiteStarted(suite);
172 suite._disabled = true;
173 }
174 suite._endTime = new Date();
175 currentSuite = suite._parent;
176 };
177 self.jasmineDone = function() {
178 if (currentSuite) {
179 // focused spec (fit) -- suiteDone was never called
180 self.suiteDone(fakeFocusedSuite);
181 }
182 self.writeFile(resultsAsXml());
183 //log("Specs skipped but not reported (entire suite skipped or targeted to specific specs)", totalSpecsDefined - totalSpecsExecuted + totalSpecsDisabled);
184
185 self.finished = true;
186 // this is so phantomjs-testrunner.js can tell if we're done executing
187 exportObject.endTime = new Date();
188 };
189
190 self.writeFile = function(text) {
191 var errors = [];
192 var path = self.savePath;
193 var filename = self.filename;
194
195 function phantomWrite(path, filename, text) {
196 // turn filename into a qualified path
197 filename = getQualifiedFilename(path, filename, window.fs_path_separator);
198 // write via a method injected by phantomjs-testrunner.js
199 __phantom_writeFile(filename, text);
200 }
201
202 function nodeWrite(path, filename, text) {
203 var fs = require("fs");
204 var nodejs_path = require("path");
205 require("mkdirp").sync(path); // make sure the path exists
206 var filepath = nodejs_path.join(path, filename);
207 var xmlfile = fs.openSync(filepath, "w");
208 fs.writeSync(xmlfile, text, 0);
209 fs.closeSync(xmlfile);
210 return;
211 }
212 // Attempt writing with each possible environment.
213 // Track errors in case no write succeeds
214 try {
215 phantomWrite(path, filename, text);
216 return;
217 } catch (e) { errors.push(" PhantomJs attempt: " + e.message); }
218 try {
219 nodeWrite(path, filename, text);
220 return;
221 } catch (f) { errors.push(" NodeJS attempt: " + f.message); }
222
223 // If made it here, no write succeeded. Let user know.
224 log("Warning: writing nunit report failed for '" + path + "', '" +
225 filename + "'. Reasons:\n" +
226 errors.join("\n")
227 );
228 };
229
230 /******** Helper functions with closure access for simplicity ********/
231 function resultsAsXml() {
232 var date = new Date(),
233 totalSpecs = totalSpecsDefined || totalSpecsExecuted,
234 disabledSpecs = totalSpecs - totalSpecsExecuted + totalSpecsDisabled,
235 skippedSpecs = totalSpecsSkipped + disabledSpecs;
236
237 var xml = '<?xml version="1.0" encoding="utf-8" ?>';
238 xml += '\n<test-results name="' + escapeInvalidXmlChars(self.reportName) + '"';
239 xml += ' total="' + totalSpecs + '"';
240 xml += ' failures="' + totalSpecsFailed + '"';
241 xml += ' not-run="' + skippedSpecs + '"';
242 xml += ' date="' + dateString(date) + '"';
243 xml += ' time="' + timeString(date) + '"';
244 xml += ">";
245
246 for (var i=0; i<suites.length; i++) {
247 xml += suiteAsXml(suites[i], " ");
248 }
249 xml += "\n</test-results>";
250 return xml;
251 }
252 };
253
254 function suiteAsXml(suite, indent) {
255 indent = indent || "";
256 var i, xml = "\n" + indent + "<test-suite";
257 xml += ' name="' + escapeInvalidXmlChars(suite.description) + '"';
258 xml += ' executed="' + !suite._disabled + '"';
259 xml += ' success="' + !(suite._failures || suite._nestedFailures) + '"';
260 xml += ' time="' + elapsed(suite._startTime, suite._endTime) + '"';
261 xml += ">";
262 xml += "\n" + indent + " <results>";
263
264 for (i=0; i<suite._suites.length; i++) {
265 xml += suiteAsXml(suite._suites[i], indent+" ");
266 }
267 for (i=0; i<suite._specs.length; i++) {
268 xml += specAsXml(suite._specs[i], indent+" ");
269 }
270 xml += "\n" + indent + " </results>";
271 xml += "\n" + indent + "</test-suite>";
272 return xml;
273 }
274 function specAsXml(spec, indent) {
275 indent = indent || "";
276 var xml = "\n" + indent + "<test-case";
277 xml += ' name="' + escapeInvalidXmlChars(spec.description) + '"';
278 xml += ' executed="' + !(isSkipped(spec) || isDisabled(spec)) + '"';
279 xml += ' success="' + !isFailed(spec) + '"';
280 xml += ' time="' + elapsed(spec._startTime, spec._endTime) + '"';
281 xml += ">";
282 for (var i = 0, failure; i < spec.failedExpectations.length; i++) {
283 failure = spec.failedExpectations[i];
284 xml += "\n" + indent + " <failure>";
285 xml += "\n" + indent + " <message><![CDATA[" + failure.message + "]]></message>";
286 xml += "\n" + indent + " <stack-trace><![CDATA[" + failure.stack + "]]></stack-trace>";
287 xml += "\n" + indent + " </failure>";
288 }
289 xml += "\n" + indent + "</test-case>";
290 return xml;
291 }
292})(this);