1 | /***************************************************************************** |
---|
2 | * |
---|
3 | * Copyright (c) 2003-2004 EcmaUnit Contributors. All rights reserved. |
---|
4 | * |
---|
5 | * This software is distributed under the terms of the EcmaUnit |
---|
6 | * License. See LICENSE.txt for license text. For a list of EcmaUnit |
---|
7 | * Contributors see CREDITS.txt. |
---|
8 | * |
---|
9 | *****************************************************************************/ |
---|
10 | |
---|
11 | // $Id: ecmaunit.js 23620 2006-02-23 16:16:28Z guido $ |
---|
12 | |
---|
13 | /* |
---|
14 | Object-oriented prototype-based unit test suite |
---|
15 | */ |
---|
16 | |
---|
17 | function TestCase() { |
---|
18 | /* a single test case */ |
---|
19 | this.name = 'TestCase'; |
---|
20 | }; |
---|
21 | |
---|
22 | TestCase.prototype.initialize = function(reporter) { |
---|
23 | // this array's contents will be displayed when done (if it |
---|
24 | // contains anything) |
---|
25 | this._exceptions = new Array(); |
---|
26 | this._reporter = reporter; |
---|
27 | }; |
---|
28 | |
---|
29 | TestCase.prototype.setUp = function() { |
---|
30 | /* this will be called on before each test method that is ran */ |
---|
31 | }; |
---|
32 | |
---|
33 | TestCase.prototype.tearDown = function() { |
---|
34 | /* this will be called after each test method that has been ran */ |
---|
35 | }; |
---|
36 | |
---|
37 | TestCase.prototype.assertEquals = function(var1, var2, message) { |
---|
38 | /* assert whether 2 vars have the same value */ |
---|
39 | if (!message) { |
---|
40 | message = ''; |
---|
41 | } else { |
---|
42 | message = "'" + message + "' "; |
---|
43 | } |
---|
44 | if (var1 != var2 && |
---|
45 | (!(var1 instanceof Array && var2 instanceof Array) || |
---|
46 | !this._arrayDeepCompare(var1, var2))) { |
---|
47 | this._throwException('Assertion ' + message + 'failed: ' + |
---|
48 | var1 + ' != ' + var2); |
---|
49 | }; |
---|
50 | }; |
---|
51 | |
---|
52 | TestCase.prototype.assertNotEquals = function(var1, var2, message) { |
---|
53 | /* assert whether 2 vars have different values */ |
---|
54 | if (!message) { |
---|
55 | message = ''; |
---|
56 | } else { |
---|
57 | message = "'" + message + "' "; |
---|
58 | } |
---|
59 | if (var1 && var1.toSource && var2 && var2.toSource) { |
---|
60 | if (var1.toSource() == var2.toSource()) { |
---|
61 | this._throwException('Assertion ' + message + 'failed: ' + |
---|
62 | var1 + ' == ' + var2); |
---|
63 | }; |
---|
64 | } else { |
---|
65 | if (var1 == var2) { |
---|
66 | this._throwException('Assertion ' + message + 'failed: ' + |
---|
67 | var1 + ' == ' + var2); |
---|
68 | }; |
---|
69 | }; |
---|
70 | }; |
---|
71 | |
---|
72 | TestCase.prototype.debug = function(msg) { |
---|
73 | this._reporter.debug(msg); |
---|
74 | } |
---|
75 | TestCase.prototype.assert = function(statement, message) { |
---|
76 | /* assert whether a variable resolves to true */ |
---|
77 | if (!statement) { |
---|
78 | if (!message) message = (statement && statement.toString) ? |
---|
79 | statement.toString() : statement; |
---|
80 | this._throwException('Assertion \'' + message + '\' failed'); |
---|
81 | }; |
---|
82 | }; |
---|
83 | |
---|
84 | TestCase.prototype.assertTrue = TestCase.prototype.assert; |
---|
85 | |
---|
86 | TestCase.prototype.assertFalse = function(statement, message) { |
---|
87 | /* assert whether a variable resolves to false */ |
---|
88 | if (statement) { |
---|
89 | if (!message) message = statement.toString ? |
---|
90 | statement.toString() : statement; |
---|
91 | this._throwException('AssertFalse \'' + message + '\' failed'); |
---|
92 | }; |
---|
93 | }; |
---|
94 | |
---|
95 | TestCase.prototype.assertThrows = function(func, exception, context) { |
---|
96 | /* assert whether a certain exception is raised */ |
---|
97 | if (!context) { |
---|
98 | context = null; |
---|
99 | }; |
---|
100 | var exception_thrown = false; |
---|
101 | // remove the first three args, they're the function's normal args |
---|
102 | var args = []; |
---|
103 | for (var i=3; i < arguments.length; i++) { |
---|
104 | args.push(arguments[i]); |
---|
105 | }; |
---|
106 | try { |
---|
107 | func.apply(context, args); |
---|
108 | } catch(e) { |
---|
109 | // allow catching undefined exceptions too |
---|
110 | if (exception === undefined) { |
---|
111 | } else if (exception) { |
---|
112 | var isinstance = false; |
---|
113 | try { |
---|
114 | if (e instanceof exception) { |
---|
115 | isinstance = true; |
---|
116 | }; |
---|
117 | } catch(f) { |
---|
118 | }; |
---|
119 | if (!isinstance) { |
---|
120 | if (exception.toSource && e.toSource) { |
---|
121 | exception = exception.toSource(); |
---|
122 | e = e.toSource(); |
---|
123 | }; |
---|
124 | if (exception.toString && e.toString) { |
---|
125 | exception = exception.toString(); |
---|
126 | e = e.toString(); |
---|
127 | }; |
---|
128 | if (e != exception) { |
---|
129 | this._throwException('Function threw the wrong ' + |
---|
130 | 'exception ' + e.toString() + |
---|
131 | ', while expecting ' + exception.toString()); |
---|
132 | }; |
---|
133 | }; |
---|
134 | }; |
---|
135 | exception_thrown = true; |
---|
136 | }; |
---|
137 | if (!exception_thrown) { |
---|
138 | if (exception) { |
---|
139 | this._throwException("function didn\'t raise exception \'" + |
---|
140 | exception.toString() + "'"); |
---|
141 | } else { |
---|
142 | this._throwException('function didn\'t raise exception'); |
---|
143 | }; |
---|
144 | }; |
---|
145 | }; |
---|
146 | |
---|
147 | TestCase.prototype.runTests = function() { |
---|
148 | /* find all methods of which the name starts with 'test' |
---|
149 | and call them */ |
---|
150 | var ret = this._runHelper(); |
---|
151 | this._reporter.summarize(ret[0], ret[1], this._exceptions); |
---|
152 | }; |
---|
153 | |
---|
154 | TestCase.prototype._runHelper = function() { |
---|
155 | /* this actually runs the tests |
---|
156 | return value is an array [total tests ran, total time spent (ms)] |
---|
157 | */ |
---|
158 | var now = new Date(); |
---|
159 | var starttime = now.getTime(); |
---|
160 | var numtests = 0; |
---|
161 | for (var attr in this) { |
---|
162 | if (attr.substr(0, 4) == 'test') { |
---|
163 | this.setUp(); |
---|
164 | try { |
---|
165 | this[attr](); |
---|
166 | this._reporter.reportSuccess(this.name, attr); |
---|
167 | } catch(e) { |
---|
168 | var raw = e; |
---|
169 | if (e && e.name && e.message) { // Microsoft |
---|
170 | e = e.name + ': ' + e.message; |
---|
171 | } |
---|
172 | this._reporter.reportError(this.name, attr, e, raw); |
---|
173 | this._exceptions.push(new Array(this.name, attr, e, raw)); |
---|
174 | }; |
---|
175 | this.tearDown(); |
---|
176 | numtests++; |
---|
177 | }; |
---|
178 | }; |
---|
179 | var now = new Date(); |
---|
180 | var totaltime = now.getTime() - starttime; |
---|
181 | return new Array(numtests, totaltime); |
---|
182 | }; |
---|
183 | |
---|
184 | TestCase.prototype._throwException = function(message) { |
---|
185 | var lineno = this._getLineNo(); |
---|
186 | if (lineno) { |
---|
187 | message = 'line ' + lineno + ' - ' + message; |
---|
188 | }; |
---|
189 | throw(message); |
---|
190 | }; |
---|
191 | |
---|
192 | TestCase.prototype._getLineNo = function() { |
---|
193 | /* tries to get the line no in Moz */ |
---|
194 | var stack = undefined; |
---|
195 | try {notdefined()} catch(e) {stack = e.stack}; |
---|
196 | if (stack) { |
---|
197 | stack = stack.toString().split('\n'); |
---|
198 | for (var i=0; i < stack.length; i++) { |
---|
199 | var line = stack[i].split('@')[1]; |
---|
200 | if (line.indexOf('ecmaunit') == -1) { |
---|
201 | // return the first line after we get out of ecmaunit |
---|
202 | var chunks = line.split(':'); |
---|
203 | var lineno = chunks[chunks.length - 1]; |
---|
204 | if (lineno != '0') { |
---|
205 | return lineno; |
---|
206 | }; |
---|
207 | }; |
---|
208 | }; |
---|
209 | } else { |
---|
210 | return false; |
---|
211 | }; |
---|
212 | }; |
---|
213 | |
---|
214 | TestCase.prototype._arrayDeepCompare = function(a1, a2) { |
---|
215 | if (!(a1 instanceof Array && a2 instanceof Array)) { |
---|
216 | return false; |
---|
217 | }; |
---|
218 | if (a1.length != a2.length) { |
---|
219 | return false; |
---|
220 | }; |
---|
221 | for (var i=0; i < a1.length; i++) { |
---|
222 | if (a1[i] instanceof Array) { |
---|
223 | if (!this._arrayDeepCompare(a1[i], a2[i])) { |
---|
224 | return false; |
---|
225 | }; |
---|
226 | } else if (a1[i] != a2[i]) { |
---|
227 | return false; |
---|
228 | }; |
---|
229 | }; |
---|
230 | return true; |
---|
231 | }; |
---|
232 | |
---|
233 | function TestSuite(reporter) { |
---|
234 | /* run a suite of tests */ |
---|
235 | if (reporter) { |
---|
236 | this._reporter = reporter; |
---|
237 | this._tests = new Array(); |
---|
238 | this._exceptions = new Array(); |
---|
239 | }; |
---|
240 | }; |
---|
241 | |
---|
242 | TestSuite.prototype.registerTest = function(test) { |
---|
243 | /* register a test */ |
---|
244 | if (!test) { |
---|
245 | throw('TestSuite.registerTest() requires a testcase as argument'); |
---|
246 | }; |
---|
247 | this._tests.push(test); |
---|
248 | }; |
---|
249 | |
---|
250 | TestSuite.prototype.runSuite = function() { |
---|
251 | /* run the suite */ |
---|
252 | var now = new Date(); |
---|
253 | var starttime = now.getTime(); |
---|
254 | var testsran = 0; |
---|
255 | for (var i=0; i < this._tests.length; i++) { |
---|
256 | var test = new this._tests[i](); |
---|
257 | test.initialize(this._reporter); |
---|
258 | testsran += test._runHelper()[0]; |
---|
259 | // the TestCase class handles output of dots and Fs, but we |
---|
260 | // should take care of the exceptions |
---|
261 | if (test._exceptions.length) { |
---|
262 | for (var j=0; j < test._exceptions.length; j++) { |
---|
263 | // attr, exc in the org array, so here it becomes |
---|
264 | // name, attr, exc |
---|
265 | var excinfo = test._exceptions[j]; |
---|
266 | this._exceptions.push(excinfo); |
---|
267 | }; |
---|
268 | }; |
---|
269 | }; |
---|
270 | var now = new Date(); |
---|
271 | var totaltime = now.getTime() - starttime; |
---|
272 | this._reporter.summarize(testsran, totaltime, this._exceptions); |
---|
273 | }; |
---|
274 | |
---|
275 | function StdoutReporter(verbose) { |
---|
276 | if (verbose) { |
---|
277 | this.verbose = verbose; |
---|
278 | }; |
---|
279 | }; |
---|
280 | |
---|
281 | StdoutReporter.prototype.debug = function(text) { |
---|
282 | print(text+"\n"); |
---|
283 | } |
---|
284 | |
---|
285 | StdoutReporter.prototype.reportSuccess = function(testcase, attr) { |
---|
286 | /* report a test success */ |
---|
287 | if (this.verbose) { |
---|
288 | print(testcase + '.' + attr + '(): OK'); |
---|
289 | } else { |
---|
290 | print('.'); |
---|
291 | }; |
---|
292 | }; |
---|
293 | |
---|
294 | StdoutReporter.prototype.reportError = function(testcase, attr, |
---|
295 | exception, raw) { |
---|
296 | /* report a test failure */ |
---|
297 | if (this.verbose) { |
---|
298 | print(testcase + '.' + attr + '(): FAILED!'); |
---|
299 | } else { |
---|
300 | print('F'); |
---|
301 | }; |
---|
302 | }; |
---|
303 | |
---|
304 | StdoutReporter.prototype.summarize = function(numtests, time, exceptions) { |
---|
305 | print('\n' + numtests + ' tests ran in ' + time / 1000.0 + |
---|
306 | ' seconds\n'); |
---|
307 | if (exceptions.length) { |
---|
308 | for (var i=0; i < exceptions.length; i++) { |
---|
309 | var testcase = exceptions[i][0]; |
---|
310 | var attr = exceptions[i][1]; |
---|
311 | var exception = exceptions[i][2]; |
---|
312 | var raw = exceptions[i][3]; |
---|
313 | print(testcase + '.' + attr + ', exception: ' + exception); |
---|
314 | if (this.verbose) { |
---|
315 | this._printStackTrace(raw); |
---|
316 | }; |
---|
317 | }; |
---|
318 | print('NOT OK!'); |
---|
319 | } else { |
---|
320 | print('OK!'); |
---|
321 | }; |
---|
322 | }; |
---|
323 | |
---|
324 | StdoutReporter.prototype._printStackTrace = function(exc) { |
---|
325 | if (!exc.stack) { |
---|
326 | print('no stacktrace available'); |
---|
327 | return; |
---|
328 | }; |
---|
329 | var lines = exc.stack.toString().split('\n'); |
---|
330 | var toprint = []; |
---|
331 | for (var i=0; i < lines.length; i++) { |
---|
332 | var line = lines[i]; |
---|
333 | if (line.indexOf('ecmaunit.js') > -1) { |
---|
334 | // remove useless bit of traceback |
---|
335 | break; |
---|
336 | }; |
---|
337 | if (line.charAt(0) == '(') { |
---|
338 | line = 'function' + line; |
---|
339 | }; |
---|
340 | var chunks = line.split('@'); |
---|
341 | toprint.push(chunks); |
---|
342 | }; |
---|
343 | toprint.reverse(); |
---|
344 | for (var i=0; i < toprint.length; i++) { |
---|
345 | print(' ' + toprint[i][1]); |
---|
346 | print(' ' + toprint[i][0]); |
---|
347 | }; |
---|
348 | print(); |
---|
349 | }; |
---|
350 | |
---|
351 | function HTMLReporter(outputelement, verbose) { |
---|
352 | if (outputelement) { |
---|
353 | this.outputelement = outputelement; |
---|
354 | this.document = outputelement.ownerDocument; |
---|
355 | this.verbose = verbose; |
---|
356 | }; |
---|
357 | }; |
---|
358 | |
---|
359 | HTMLReporter.prototype.debug = function(text) { |
---|
360 | var msg = this.document.createTextNode(text); |
---|
361 | var div = this.document.createElement('div'); |
---|
362 | div.appendChild(msg); |
---|
363 | this.outputelement.appendChild(div); |
---|
364 | }; |
---|
365 | |
---|
366 | HTMLReporter.prototype.reportSuccess = function(testcase, attr) { |
---|
367 | /* report a test success */ |
---|
368 | // a single dot looks rather small |
---|
369 | var dot = this.document.createTextNode('+'); |
---|
370 | this.outputelement.appendChild(dot); |
---|
371 | }; |
---|
372 | |
---|
373 | HTMLReporter.prototype.reportError = function(testcase, attr, exception, raw) { |
---|
374 | /* report a test failure */ |
---|
375 | var f = this.document.createTextNode('F'); |
---|
376 | this.outputelement.appendChild(f); |
---|
377 | }; |
---|
378 | |
---|
379 | HTMLReporter.prototype.summarize = function(numtests, time, exceptions) { |
---|
380 | /* write the result output to the html node */ |
---|
381 | var p = this.document.createElement('p'); |
---|
382 | var text = this.document.createTextNode(numtests + ' tests ran in ' + |
---|
383 | time / 1000.0 + ' seconds'); |
---|
384 | p.appendChild(text); |
---|
385 | this.outputelement.appendChild(p); |
---|
386 | if (exceptions.length) { |
---|
387 | for (var i=0; i < exceptions.length; i++) { |
---|
388 | var testcase = exceptions[i][0]; |
---|
389 | var attr = exceptions[i][1]; |
---|
390 | var exception = exceptions[i][2].toString(); |
---|
391 | var raw = exceptions[i][3]; |
---|
392 | var div = this.document.createElement('div'); |
---|
393 | var lines = exception.toString().split('\n'); |
---|
394 | var text = this.document.createTextNode( |
---|
395 | testcase + '.' + attr + ', exception '); |
---|
396 | div.appendChild(text); |
---|
397 | // add some formatting for Opera: this browser displays nice |
---|
398 | // tracebacks... |
---|
399 | for (var j=0; j < lines.length; j++) { |
---|
400 | var text = lines[j]; |
---|
401 | if (j > 0) { |
---|
402 | text = '\xa0\xa0\xa0\xa0' + text; |
---|
403 | }; |
---|
404 | div.appendChild(this.document.createTextNode(text)); |
---|
405 | div.appendChild(this.document.createElement('br')); |
---|
406 | }; |
---|
407 | div.style.color = 'red'; |
---|
408 | this.outputelement.appendChild(div); |
---|
409 | if (this.verbose) { |
---|
410 | // display stack trace on Moz |
---|
411 | this._displayStackTrace(raw); |
---|
412 | }; |
---|
413 | }; |
---|
414 | var div = this.document.createElement('div'); |
---|
415 | var text = this.document.createTextNode('NOT OK!'); |
---|
416 | div.appendChild(text); |
---|
417 | div.style.backgroundColor = 'red'; |
---|
418 | div.style.color = 'black'; |
---|
419 | div.style.fontWeight = 'bold'; |
---|
420 | div.style.textAlign = 'center'; |
---|
421 | div.style.marginTop = '1em'; |
---|
422 | this.outputelement.appendChild(div); |
---|
423 | } else { |
---|
424 | var div = this.document.createElement('div'); |
---|
425 | var text = this.document.createTextNode('OK!'); |
---|
426 | div.appendChild(text); |
---|
427 | div.style.backgroundColor = 'lightgreen'; |
---|
428 | div.style.color = 'black'; |
---|
429 | div.style.fontWeight = 'bold'; |
---|
430 | div.style.textAlign = 'center'; |
---|
431 | div.style.marginTop = '1em'; |
---|
432 | this.outputelement.appendChild(div); |
---|
433 | }; |
---|
434 | }; |
---|
435 | |
---|
436 | HTMLReporter.prototype._displayStackTrace = function(exc) { |
---|
437 | /* |
---|
438 | if (arguments.caller) { |
---|
439 | // IE |
---|
440 | var caller = arguments; |
---|
441 | toprint = []; |
---|
442 | while (caller) { |
---|
443 | var callee = caller.callee.toString(); |
---|
444 | callee = callee.replace('\n', '').replace(/\s+/g, ' '); |
---|
445 | var funcsig = /(.*?)\s*\{/.exec(callee)[1]; |
---|
446 | var args = caller.callee.arguments; |
---|
447 | var displayargs = []; |
---|
448 | for (var i=0; i < args.length; i++) { |
---|
449 | displayargs.push(args[i].toString()); |
---|
450 | }; |
---|
451 | toprint.push((funcsig + ' - (' + displayargs + ')')); |
---|
452 | caller = caller.caller; |
---|
453 | }; |
---|
454 | toprint.reverse(); |
---|
455 | var pre = this.document.createElement('pre'); |
---|
456 | for (var i=0; i < toprint.length; i++) { |
---|
457 | pre.appendChild(document.createTextNode(toprint[i])); |
---|
458 | pre.appendChild(document.createElement('br')); |
---|
459 | }; |
---|
460 | this.outputelement.appendChild(pre); |
---|
461 | }; |
---|
462 | */ |
---|
463 | if (exc.stack) { |
---|
464 | // Moz (sometimes) |
---|
465 | var lines = exc.stack.toString().split('\n'); |
---|
466 | var toprint = []; // need to reverse this before outputting |
---|
467 | for (var i=0; i < lines.length; i++) { |
---|
468 | var line = lines[i]; |
---|
469 | if (line.indexOf('ecmaunit.js') > -1) { |
---|
470 | // remove useless bit of traceback |
---|
471 | break; |
---|
472 | }; |
---|
473 | if (line[0] == '(') { |
---|
474 | line = 'function' + line; |
---|
475 | }; |
---|
476 | line = line.split('@'); |
---|
477 | toprint.push(line); |
---|
478 | }; |
---|
479 | toprint.reverse(); |
---|
480 | var pre = this.document.createElement('pre'); |
---|
481 | for (var i=0; i < toprint.length; i++) { |
---|
482 | pre.appendChild( |
---|
483 | this.document.createTextNode( |
---|
484 | ' ' + toprint[i][1] + '\n ' + toprint[i][0] + '\n' |
---|
485 | ) |
---|
486 | ); |
---|
487 | }; |
---|
488 | pre.appendChild(document.createTextNode('\n')); |
---|
489 | this.outputelement.appendChild(pre); |
---|
490 | }; |
---|
491 | }; |
---|