|
1 /* -*- js-indent-level: 4; tab-width: 4; indent-tabs-mode: nil -*- */ |
|
2 /* vim:set ts=4 sw=4 sts=4 et: */ |
|
3 /** |
|
4 * SimpleTest, a partial Test.Simple/Test.More API compatible test library. |
|
5 * |
|
6 * Why? |
|
7 * |
|
8 * Test.Simple doesn't work on IE < 6. |
|
9 * TODO: |
|
10 * * Support the Test.Simple API used by MochiKit, to be able to test MochiKit |
|
11 * itself against IE 5.5 |
|
12 * |
|
13 * NOTE: Pay attention to cross-browser compatibility in this file. For |
|
14 * instance, do not use const or JS > 1.5 features which are not yet |
|
15 * implemented everywhere. |
|
16 * |
|
17 **/ |
|
18 |
|
19 var SimpleTest = { }; |
|
20 var parentRunner = null; |
|
21 |
|
22 // In normal test runs, the window that has a TestRunner in its parent is |
|
23 // the primary window. In single test runs, if there is no parent and there |
|
24 // is no opener then it is the primary window. |
|
25 var isSingleTestRun = (parent == window && !opener) |
|
26 var isPrimaryTestWindow = !!parent.TestRunner || isSingleTestRun; |
|
27 |
|
28 // Finds the TestRunner for this test run and the SpecialPowers object (in |
|
29 // case it is not defined) from a parent/opener window. |
|
30 // |
|
31 // Finding the SpecialPowers object is needed when we have ChromePowers in |
|
32 // harness.xul and we need SpecialPowers in the iframe, and also for tests |
|
33 // like test_focus.xul where we open a window which opens another window which |
|
34 // includes SimpleTest.js. |
|
35 (function() { |
|
36 function ancestor(w) { |
|
37 return w.parent != w ? w.parent : w.opener; |
|
38 } |
|
39 |
|
40 var w = ancestor(window); |
|
41 while (w && (!parentRunner || !window.SpecialPowers)) { |
|
42 if (!parentRunner) { |
|
43 parentRunner = w.TestRunner; |
|
44 if (!parentRunner && w.wrappedJSObject) { |
|
45 parentRunner = w.wrappedJSObject.TestRunner; |
|
46 } |
|
47 } |
|
48 if (!window.SpecialPowers) { |
|
49 window.SpecialPowers = w.SpecialPowers; |
|
50 } |
|
51 w = ancestor(w); |
|
52 } |
|
53 })(); |
|
54 |
|
55 /* Helper functions pulled out of various MochiKit modules */ |
|
56 if (typeof(repr) == 'undefined') { |
|
57 this.repr = function(o) { |
|
58 if (typeof(o) == "undefined") { |
|
59 return "undefined"; |
|
60 } else if (o === null) { |
|
61 return "null"; |
|
62 } |
|
63 try { |
|
64 if (typeof(o.__repr__) == 'function') { |
|
65 return o.__repr__(); |
|
66 } else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) { |
|
67 return o.repr(); |
|
68 } |
|
69 } catch (e) { |
|
70 } |
|
71 try { |
|
72 if (typeof(o.NAME) == 'string' && ( |
|
73 o.toString == Function.prototype.toString || |
|
74 o.toString == Object.prototype.toString |
|
75 )) { |
|
76 return o.NAME; |
|
77 } |
|
78 } catch (e) { |
|
79 } |
|
80 try { |
|
81 var ostring = (o + ""); |
|
82 } catch (e) { |
|
83 return "[" + typeof(o) + "]"; |
|
84 } |
|
85 if (typeof(o) == "function") { |
|
86 o = ostring.replace(/^\s+/, ""); |
|
87 var idx = o.indexOf("{"); |
|
88 if (idx != -1) { |
|
89 o = o.substr(0, idx) + "{...}"; |
|
90 } |
|
91 } |
|
92 return ostring; |
|
93 }; |
|
94 } |
|
95 |
|
96 /* This returns a function that applies the previously given parameters. |
|
97 * This is used by SimpleTest.showReport |
|
98 */ |
|
99 if (typeof(partial) == 'undefined') { |
|
100 this.partial = function(func) { |
|
101 var args = []; |
|
102 for (var i = 1; i < arguments.length; i++) { |
|
103 args.push(arguments[i]); |
|
104 } |
|
105 return function() { |
|
106 if (arguments.length > 0) { |
|
107 for (var i = 1; i < arguments.length; i++) { |
|
108 args.push(arguments[i]); |
|
109 } |
|
110 } |
|
111 func(args); |
|
112 }; |
|
113 }; |
|
114 } |
|
115 |
|
116 if (typeof(getElement) == 'undefined') { |
|
117 this.getElement = function(id) { |
|
118 return ((typeof(id) == "string") ? |
|
119 document.getElementById(id) : id); |
|
120 }; |
|
121 this.$ = this.getElement; |
|
122 } |
|
123 |
|
124 SimpleTest._newCallStack = function(path) { |
|
125 var rval = function () { |
|
126 var callStack = arguments.callee.callStack; |
|
127 for (var i = 0; i < callStack.length; i++) { |
|
128 if (callStack[i].apply(this, arguments) === false) { |
|
129 break; |
|
130 } |
|
131 } |
|
132 try { |
|
133 this[path] = null; |
|
134 } catch (e) { |
|
135 // pass |
|
136 } |
|
137 }; |
|
138 rval.callStack = []; |
|
139 return rval; |
|
140 }; |
|
141 |
|
142 if (typeof(addLoadEvent) == 'undefined') { |
|
143 this.addLoadEvent = function(func) { |
|
144 var existing = window["onload"]; |
|
145 var regfunc = existing; |
|
146 if (!(typeof(existing) == 'function' |
|
147 && typeof(existing.callStack) == "object" |
|
148 && existing.callStack !== null)) { |
|
149 regfunc = SimpleTest._newCallStack("onload"); |
|
150 if (typeof(existing) == 'function') { |
|
151 regfunc.callStack.push(existing); |
|
152 } |
|
153 window["onload"] = regfunc; |
|
154 } |
|
155 regfunc.callStack.push(func); |
|
156 }; |
|
157 } |
|
158 |
|
159 function createEl(type, attrs, html) { |
|
160 //use createElementNS so the xul/xhtml tests have no issues |
|
161 var el; |
|
162 if (!document.body) { |
|
163 el = document.createElementNS("http://www.w3.org/1999/xhtml", type); |
|
164 } |
|
165 else { |
|
166 el = document.createElement(type); |
|
167 } |
|
168 if (attrs !== null && attrs !== undefined) { |
|
169 for (var k in attrs) { |
|
170 el.setAttribute(k, attrs[k]); |
|
171 } |
|
172 } |
|
173 if (html !== null && html !== undefined) { |
|
174 el.appendChild(document.createTextNode(html)); |
|
175 } |
|
176 return el; |
|
177 } |
|
178 |
|
179 /* lots of tests use this as a helper to get css properties */ |
|
180 if (typeof(computedStyle) == 'undefined') { |
|
181 this.computedStyle = function(elem, cssProperty) { |
|
182 elem = getElement(elem); |
|
183 if (elem.currentStyle) { |
|
184 return elem.currentStyle[cssProperty]; |
|
185 } |
|
186 if (typeof(document.defaultView) == 'undefined' || document === null) { |
|
187 return undefined; |
|
188 } |
|
189 var style = document.defaultView.getComputedStyle(elem, null); |
|
190 if (typeof(style) == 'undefined' || style === null) { |
|
191 return undefined; |
|
192 } |
|
193 |
|
194 var selectorCase = cssProperty.replace(/([A-Z])/g, '-$1' |
|
195 ).toLowerCase(); |
|
196 |
|
197 return style.getPropertyValue(selectorCase); |
|
198 }; |
|
199 } |
|
200 |
|
201 /** |
|
202 * Check for OOP test plugin |
|
203 **/ |
|
204 SimpleTest.testPluginIsOOP = function () { |
|
205 var testPluginIsOOP = false; |
|
206 if (navigator.platform.indexOf("Mac") == 0) { |
|
207 if (SpecialPowers.XPCOMABI.match(/x86-/)) { |
|
208 try { |
|
209 testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.i386.test.plugin"); |
|
210 } catch (e) { |
|
211 testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.i386"); |
|
212 } |
|
213 } |
|
214 else if (SpecialPowers.XPCOMABI.match(/x86_64-/)) { |
|
215 try { |
|
216 testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.x86_64.test.plugin"); |
|
217 } catch (e) { |
|
218 testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.x86_64"); |
|
219 } |
|
220 } |
|
221 } |
|
222 else { |
|
223 testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled"); |
|
224 } |
|
225 |
|
226 return testPluginIsOOP; |
|
227 }; |
|
228 |
|
229 SimpleTest._tests = []; |
|
230 SimpleTest._stopOnLoad = true; |
|
231 SimpleTest._cleanupFunctions = []; |
|
232 |
|
233 /** |
|
234 * Something like assert. |
|
235 **/ |
|
236 SimpleTest.ok = function (condition, name, diag) { |
|
237 var test = {'result': !!condition, 'name': name, 'diag': diag}; |
|
238 SimpleTest._logResult(test, "TEST-PASS", "TEST-UNEXPECTED-FAIL"); |
|
239 SimpleTest._tests.push(test); |
|
240 }; |
|
241 |
|
242 /** |
|
243 * Roughly equivalent to ok(a==b, name) |
|
244 **/ |
|
245 SimpleTest.is = function (a, b, name) { |
|
246 var pass = (a == b); |
|
247 var diag = pass ? "" : "got " + repr(a) + ", expected " + repr(b) |
|
248 SimpleTest.ok(pass, name, diag); |
|
249 }; |
|
250 |
|
251 SimpleTest.isfuzzy = function (a, b, epsilon, name) { |
|
252 var pass = (a > b - epsilon) && (a < b + epsilon); |
|
253 var diag = pass ? "" : "got " + repr(a) + ", expected " + repr(b) + " epsilon: +/- " + repr(epsilon) |
|
254 SimpleTest.ok(pass, name, diag); |
|
255 }; |
|
256 |
|
257 SimpleTest.isnot = function (a, b, name) { |
|
258 var pass = (a != b); |
|
259 var diag = pass ? "" : "didn't expect " + repr(a) + ", but got it"; |
|
260 SimpleTest.ok(pass, name, diag); |
|
261 }; |
|
262 |
|
263 /** |
|
264 * Roughly equivalent to ok(a===b, name) |
|
265 **/ |
|
266 SimpleTest.ise = function (a, b, name) { |
|
267 var pass = (a === b); |
|
268 var diag = pass ? "" : "got " + repr(a) + ", strictly expected " + repr(b) |
|
269 SimpleTest.ok(pass, name, diag); |
|
270 }; |
|
271 |
|
272 /** |
|
273 * Check that the function call throws an exception. |
|
274 */ |
|
275 SimpleTest.doesThrow = function(fn, name) { |
|
276 var gotException = false; |
|
277 try { |
|
278 fn(); |
|
279 } catch (ex) { gotException = true; } |
|
280 ok(gotException, name); |
|
281 }; |
|
282 |
|
283 // --------------- Test.Builder/Test.More todo() ----------------- |
|
284 |
|
285 SimpleTest.todo = function(condition, name, diag) { |
|
286 var test = {'result': !!condition, 'name': name, 'diag': diag, todo: true}; |
|
287 SimpleTest._logResult(test, "TEST-UNEXPECTED-PASS", "TEST-KNOWN-FAIL"); |
|
288 SimpleTest._tests.push(test); |
|
289 }; |
|
290 |
|
291 /* |
|
292 * Returns the absolute URL to a test data file from where tests |
|
293 * are served. i.e. the file doesn't necessarely exists where tests |
|
294 * are executed. |
|
295 * (For b2g and android, mochitest are executed on the device, while |
|
296 * all mochitest html (and others) files are served from the test runner |
|
297 * slave) |
|
298 */ |
|
299 SimpleTest.getTestFileURL = function(path) { |
|
300 var lastSlashIdx = path.lastIndexOf("/") + 1; |
|
301 var filename = path.substr(lastSlashIdx); |
|
302 var location = window.location; |
|
303 // Remove mochitest html file name from the path |
|
304 var remotePath = location.pathname.replace(/\/[^\/]+?$/,""); |
|
305 var url = location.origin + |
|
306 remotePath + "/" + path; |
|
307 return url; |
|
308 }; |
|
309 |
|
310 SimpleTest._getCurrentTestURL = function() { |
|
311 return parentRunner && parentRunner.currentTestURL || |
|
312 typeof gTestPath == "string" && gTestPath || |
|
313 "unknown test url"; |
|
314 }; |
|
315 |
|
316 SimpleTest._forceLogMessageOutput = parentRunner && !parentRunner.quiet; |
|
317 |
|
318 /** |
|
319 * Force all test messages to be displayed. Only applies for the current test. |
|
320 */ |
|
321 SimpleTest.requestCompleteLog = function() { |
|
322 if (SimpleTest._forceLogMessageOutput) |
|
323 return; |
|
324 |
|
325 SimpleTest._forceLogMessageOutput = true; |
|
326 SimpleTest.registerCleanupFunction(function() { |
|
327 SimpleTest._forceLogMessageOutput = false; |
|
328 }); |
|
329 }; |
|
330 |
|
331 /** |
|
332 * A circular buffer, managed by _logResult. We explicitly manage the |
|
333 * circularness of the buffer, rather than resorting to .shift()/.push() |
|
334 * because explicit management is much faster. |
|
335 */ |
|
336 SimpleTest._bufferedMessages = []; |
|
337 SimpleTest._logResult = (function () { |
|
338 var bufferingThreshold = 100; |
|
339 var outputIndex = 0; |
|
340 |
|
341 function logResult(test, passString, failString) { |
|
342 var url = SimpleTest._getCurrentTestURL(); |
|
343 var resultString = test.result ? passString : failString; |
|
344 var diagnostic = test.name + (test.diag ? " - " + test.diag : ""); |
|
345 var msg = [resultString, url, diagnostic].join(" | "); |
|
346 var isError = !test.result == !test.todo; |
|
347 |
|
348 // Due to JavaScript's name lookup rules, it is important that |
|
349 // the second parameter here be named identically to the isError |
|
350 // variable declared above. |
|
351 function dumpMessage(msg, isError) { |
|
352 if (parentRunner) { |
|
353 if (isError) { |
|
354 parentRunner.addFailedTest(url); |
|
355 parentRunner.error(msg); |
|
356 } else { |
|
357 parentRunner.log(msg); |
|
358 } |
|
359 } else if (typeof dump === "function") { |
|
360 dump(msg + "\n"); |
|
361 } else { |
|
362 // Non-Mozilla browser? Just do nothing. |
|
363 } |
|
364 } |
|
365 |
|
366 // Detect when SimpleTest.reset() has been called, so we can |
|
367 // reset outputIndex. We store outputIndex as local state to |
|
368 // avoid adding even more state to SimpleTest. |
|
369 if (SimpleTest._bufferedMessages.length == 0) { |
|
370 outputIndex = 0; |
|
371 } |
|
372 |
|
373 // We want to eliminate mundane TEST-PASS/TEST-KNOWN-FAIL |
|
374 // output, since some tests produce tens of thousands of of such |
|
375 // messages. These messages can consume a lot of memory to |
|
376 // generate and take a significant amount of time to output. |
|
377 // However, the reality is that TEST-PASS messages can also be |
|
378 // used as a form of logging via constructs like: |
|
379 // |
|
380 // SimpleTest.ok(true, "some informative message"); |
|
381 // |
|
382 // And eliding the logging can be very confusing when trying to |
|
383 // debug test failures. |
|
384 // |
|
385 // Hence the compromise adopted here: We buffer messages up to |
|
386 // some limit and dump the buffer when a test failure happens. |
|
387 // This behavior ought to provide enough context for developers |
|
388 // looking to understand where in the test things failed. |
|
389 if (isError) { |
|
390 // Display this message and all the messages we have buffered. |
|
391 if (SimpleTest._bufferedMessages.length > 0) { |
|
392 dumpMessage("TEST-INFO | dumping last " + SimpleTest._bufferedMessages.length + " message(s)"); |
|
393 dumpMessage("TEST-INFO | if you need more context, please use SimpleTest.requestCompleteLog() in your test"); |
|
394 |
|
395 function dumpBufferedMessage(m) { |
|
396 dumpMessage(m, false); |
|
397 } |
|
398 // The latest message is just before outputIndex. |
|
399 // The earliest message is located at outputIndex. |
|
400 var earliest = SimpleTest._bufferedMessages.slice(outputIndex); |
|
401 var latest = SimpleTest._bufferedMessages.slice(0, outputIndex); |
|
402 earliest.map(dumpBufferedMessage); |
|
403 latest.map(dumpBufferedMessage); |
|
404 |
|
405 SimpleTest._bufferedMessages = []; |
|
406 } |
|
407 |
|
408 dumpMessage(msg); |
|
409 return; |
|
410 } |
|
411 |
|
412 var runningSingleTest = ((parentRunner && |
|
413 parentRunner._urls.length == 1) || |
|
414 isSingleTestRun); |
|
415 var shouldLogImmediately = (runningSingleTest || |
|
416 SimpleTest._forceLogMessageOutput); |
|
417 |
|
418 if (!shouldLogImmediately) { |
|
419 // Buffer the message for possible later output. |
|
420 if (SimpleTest._bufferedMessages.length >= bufferingThreshold) { |
|
421 if (outputIndex >= bufferingThreshold) { |
|
422 outputIndex = 0; |
|
423 } |
|
424 SimpleTest._bufferedMessages[outputIndex] = msg; |
|
425 outputIndex++; |
|
426 } else { |
|
427 SimpleTest._bufferedMessages.push(msg); |
|
428 } |
|
429 return; |
|
430 } |
|
431 |
|
432 dumpMessage(msg); |
|
433 } |
|
434 |
|
435 return logResult; |
|
436 })(); |
|
437 |
|
438 SimpleTest.info = function(name, message) { |
|
439 SimpleTest._logResult({result:true, name:name, diag:message}, "TEST-INFO"); |
|
440 }; |
|
441 |
|
442 /** |
|
443 * Copies of is and isnot with the call to ok replaced by a call to todo. |
|
444 **/ |
|
445 |
|
446 SimpleTest.todo_is = function (a, b, name) { |
|
447 var pass = (a == b); |
|
448 var diag = pass ? repr(a) + " should equal " + repr(b) |
|
449 : "got " + repr(a) + ", expected " + repr(b); |
|
450 SimpleTest.todo(pass, name, diag); |
|
451 }; |
|
452 |
|
453 SimpleTest.todo_isnot = function (a, b, name) { |
|
454 var pass = (a != b); |
|
455 var diag = pass ? repr(a) + " should not equal " + repr(b) |
|
456 : "didn't expect " + repr(a) + ", but got it"; |
|
457 SimpleTest.todo(pass, name, diag); |
|
458 }; |
|
459 |
|
460 |
|
461 /** |
|
462 * Makes a test report, returns it as a DIV element. |
|
463 **/ |
|
464 SimpleTest.report = function () { |
|
465 var passed = 0; |
|
466 var failed = 0; |
|
467 var todo = 0; |
|
468 |
|
469 var tallyAndCreateDiv = function (test) { |
|
470 var cls, msg, div; |
|
471 var diag = test.diag ? " - " + test.diag : ""; |
|
472 if (test.todo && !test.result) { |
|
473 todo++; |
|
474 cls = "test_todo"; |
|
475 msg = "todo | " + test.name + diag; |
|
476 } else if (test.result && !test.todo) { |
|
477 passed++; |
|
478 cls = "test_ok"; |
|
479 msg = "passed | " + test.name + diag; |
|
480 } else { |
|
481 failed++; |
|
482 cls = "test_not_ok"; |
|
483 msg = "failed | " + test.name + diag; |
|
484 } |
|
485 div = createEl('div', {'class': cls}, msg); |
|
486 return div; |
|
487 }; |
|
488 var results = []; |
|
489 for (var d=0; d<SimpleTest._tests.length; d++) { |
|
490 results.push(tallyAndCreateDiv(SimpleTest._tests[d])); |
|
491 } |
|
492 |
|
493 var summary_class = failed != 0 ? 'some_fail' : |
|
494 passed == 0 ? 'todo_only' : 'all_pass'; |
|
495 |
|
496 var div1 = createEl('div', {'class': 'tests_report'}); |
|
497 var div2 = createEl('div', {'class': 'tests_summary ' + summary_class}); |
|
498 var div3 = createEl('div', {'class': 'tests_passed'}, 'Passed: ' + passed); |
|
499 var div4 = createEl('div', {'class': 'tests_failed'}, 'Failed: ' + failed); |
|
500 var div5 = createEl('div', {'class': 'tests_todo'}, 'Todo: ' + todo); |
|
501 div2.appendChild(div3); |
|
502 div2.appendChild(div4); |
|
503 div2.appendChild(div5); |
|
504 div1.appendChild(div2); |
|
505 for (var t=0; t<results.length; t++) { |
|
506 //iterate in order |
|
507 div1.appendChild(results[t]); |
|
508 } |
|
509 return div1; |
|
510 }; |
|
511 |
|
512 /** |
|
513 * Toggle element visibility |
|
514 **/ |
|
515 SimpleTest.toggle = function(el) { |
|
516 if (computedStyle(el, 'display') == 'block') { |
|
517 el.style.display = 'none'; |
|
518 } else { |
|
519 el.style.display = 'block'; |
|
520 } |
|
521 }; |
|
522 |
|
523 /** |
|
524 * Toggle visibility for divs with a specific class. |
|
525 **/ |
|
526 SimpleTest.toggleByClass = function (cls, evt) { |
|
527 var children = document.getElementsByTagName('div'); |
|
528 var elements = []; |
|
529 for (var i=0; i<children.length; i++) { |
|
530 var child = children[i]; |
|
531 var clsName = child.className; |
|
532 if (!clsName) { |
|
533 continue; |
|
534 } |
|
535 var classNames = clsName.split(' '); |
|
536 for (var j = 0; j < classNames.length; j++) { |
|
537 if (classNames[j] == cls) { |
|
538 elements.push(child); |
|
539 break; |
|
540 } |
|
541 } |
|
542 } |
|
543 for (var t=0; t<elements.length; t++) { |
|
544 //TODO: again, for-in loop over elems seems to break this |
|
545 SimpleTest.toggle(elements[t]); |
|
546 } |
|
547 if (evt) |
|
548 evt.preventDefault(); |
|
549 }; |
|
550 |
|
551 /** |
|
552 * Shows the report in the browser |
|
553 **/ |
|
554 SimpleTest.showReport = function() { |
|
555 var togglePassed = createEl('a', {'href': '#'}, "Toggle passed checks"); |
|
556 var toggleFailed = createEl('a', {'href': '#'}, "Toggle failed checks"); |
|
557 var toggleTodo = createEl('a',{'href': '#'}, "Toggle todo checks"); |
|
558 togglePassed.onclick = partial(SimpleTest.toggleByClass, 'test_ok'); |
|
559 toggleFailed.onclick = partial(SimpleTest.toggleByClass, 'test_not_ok'); |
|
560 toggleTodo.onclick = partial(SimpleTest.toggleByClass, 'test_todo'); |
|
561 var body = document.body; // Handles HTML documents |
|
562 if (!body) { |
|
563 // Do the XML thing. |
|
564 body = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", |
|
565 "body")[0]; |
|
566 } |
|
567 var firstChild = body.childNodes[0]; |
|
568 var addNode; |
|
569 if (firstChild) { |
|
570 addNode = function (el) { |
|
571 body.insertBefore(el, firstChild); |
|
572 }; |
|
573 } else { |
|
574 addNode = function (el) { |
|
575 body.appendChild(el) |
|
576 }; |
|
577 } |
|
578 addNode(togglePassed); |
|
579 addNode(createEl('span', null, " ")); |
|
580 addNode(toggleFailed); |
|
581 addNode(createEl('span', null, " ")); |
|
582 addNode(toggleTodo); |
|
583 addNode(SimpleTest.report()); |
|
584 // Add a separator from the test content. |
|
585 addNode(createEl('hr')); |
|
586 }; |
|
587 |
|
588 /** |
|
589 * Tells SimpleTest to don't finish the test when the document is loaded, |
|
590 * useful for asynchronous tests. |
|
591 * |
|
592 * When SimpleTest.waitForExplicitFinish is called, |
|
593 * explicit SimpleTest.finish() is required. |
|
594 **/ |
|
595 SimpleTest.waitForExplicitFinish = function () { |
|
596 SimpleTest._stopOnLoad = false; |
|
597 }; |
|
598 |
|
599 /** |
|
600 * Multiply the timeout the parent runner uses for this test by the |
|
601 * given factor. |
|
602 * |
|
603 * For example, in a test that may take a long time to complete, using |
|
604 * "SimpleTest.requestLongerTimeout(5)" will give it 5 times as long to |
|
605 * finish. |
|
606 */ |
|
607 SimpleTest.requestLongerTimeout = function (factor) { |
|
608 if (parentRunner) { |
|
609 parentRunner.requestLongerTimeout(factor); |
|
610 } |
|
611 } |
|
612 |
|
613 /** |
|
614 * Note that the given range of assertions is to be expected. When |
|
615 * this function is not called, 0 assertions are expected. When only |
|
616 * one argument is given, that number of assertions are expected. |
|
617 * |
|
618 * A test where we expect to have assertions (which should largely be a |
|
619 * transitional mechanism to get assertion counts down from our current |
|
620 * situation) can call the SimpleTest.expectAssertions() function, with |
|
621 * either one or two arguments: one argument gives an exact number |
|
622 * expected, and two arguments give a range. For example, a test might do |
|
623 * one of the following: |
|
624 * |
|
625 * // Currently triggers two assertions (bug NNNNNN). |
|
626 * SimpleTest.expectAssertions(2); |
|
627 * |
|
628 * // Currently triggers one assertion on Mac (bug NNNNNN). |
|
629 * if (navigator.platform.indexOf("Mac") == 0) { |
|
630 * SimpleTest.expectAssertions(1); |
|
631 * } |
|
632 * |
|
633 * // Currently triggers two assertions on all platforms (bug NNNNNN), |
|
634 * // but intermittently triggers two additional assertions (bug NNNNNN) |
|
635 * // on Windows. |
|
636 * if (navigator.platform.indexOf("Win") == 0) { |
|
637 * SimpleTest.expectAssertions(2, 4); |
|
638 * } else { |
|
639 * SimpleTest.expectAssertions(2); |
|
640 * } |
|
641 * |
|
642 * // Intermittently triggers up to three assertions (bug NNNNNN). |
|
643 * SimpleTest.expectAssertions(0, 3); |
|
644 */ |
|
645 SimpleTest.expectAssertions = function(min, max) { |
|
646 if (parentRunner) { |
|
647 parentRunner.expectAssertions(min, max); |
|
648 } |
|
649 } |
|
650 |
|
651 SimpleTest.waitForFocus_started = false; |
|
652 SimpleTest.waitForFocus_loaded = false; |
|
653 SimpleTest.waitForFocus_focused = false; |
|
654 SimpleTest._pendingWaitForFocusCount = 0; |
|
655 |
|
656 /** |
|
657 * If the page is not yet loaded, waits for the load event. In addition, if |
|
658 * the page is not yet focused, focuses and waits for the window to be |
|
659 * focused. Calls the callback when completed. If the current page is |
|
660 * 'about:blank', then the page is assumed to not yet be loaded. Pass true for |
|
661 * expectBlankPage to not make this assumption if you expect a blank page to |
|
662 * be present. |
|
663 * |
|
664 * targetWindow should be specified if it is different than 'window'. The actual |
|
665 * focused window may be a descendant of targetWindow. |
|
666 * |
|
667 * @param callback |
|
668 * function called when load and focus are complete |
|
669 * @param targetWindow |
|
670 * optional window to be loaded and focused, defaults to 'window' |
|
671 * @param expectBlankPage |
|
672 * true if targetWindow.location is 'about:blank'. Defaults to false |
|
673 */ |
|
674 SimpleTest.waitForFocus = function (callback, targetWindow, expectBlankPage) { |
|
675 SimpleTest._pendingWaitForFocusCount++; |
|
676 if (!targetWindow) |
|
677 targetWindow = window; |
|
678 |
|
679 SimpleTest.waitForFocus_started = false; |
|
680 expectBlankPage = !!expectBlankPage; |
|
681 |
|
682 var childTargetWindow = {}; |
|
683 SpecialPowers.getFocusedElementForWindow(targetWindow, true, childTargetWindow); |
|
684 childTargetWindow = childTargetWindow.value; |
|
685 |
|
686 function info(msg) { |
|
687 SimpleTest.info(msg); |
|
688 } |
|
689 function getHref(aWindow) { |
|
690 return SpecialPowers.getPrivilegedProps(aWindow, 'location.href'); |
|
691 } |
|
692 |
|
693 function maybeRunTests() { |
|
694 if (SimpleTest.waitForFocus_loaded && |
|
695 SimpleTest.waitForFocus_focused && |
|
696 !SimpleTest.waitForFocus_started) { |
|
697 SimpleTest._pendingWaitForFocusCount--; |
|
698 SimpleTest.waitForFocus_started = true; |
|
699 setTimeout(callback, 0, targetWindow); |
|
700 } |
|
701 } |
|
702 |
|
703 function waitForEvent(event) { |
|
704 try { |
|
705 // Check to make sure that this isn't a load event for a blank or |
|
706 // non-blank page that wasn't desired. |
|
707 if (event.type == "load" && (expectBlankPage != (event.target.location == "about:blank"))) |
|
708 return; |
|
709 |
|
710 SimpleTest["waitForFocus_" + event.type + "ed"] = true; |
|
711 var win = (event.type == "load") ? targetWindow : childTargetWindow; |
|
712 win.removeEventListener(event.type, waitForEvent, true); |
|
713 maybeRunTests(); |
|
714 } catch (e) { |
|
715 SimpleTest.ok(false, "Exception caught in waitForEvent: " + e.message + |
|
716 ", at: " + e.fileName + " (" + e.lineNumber + ")"); |
|
717 } |
|
718 } |
|
719 |
|
720 // If the current document is about:blank and we are not expecting a blank |
|
721 // page (or vice versa), and the document has not yet loaded, wait for the |
|
722 // page to load. A common situation is to wait for a newly opened window |
|
723 // to load its content, and we want to skip over any intermediate blank |
|
724 // pages that load. This issue is described in bug 554873. |
|
725 SimpleTest.waitForFocus_loaded = |
|
726 expectBlankPage ? |
|
727 getHref(targetWindow) == "about:blank" : |
|
728 getHref(targetWindow) != "about:blank" && targetWindow.document.readyState == "complete"; |
|
729 if (!SimpleTest.waitForFocus_loaded) { |
|
730 info("must wait for load"); |
|
731 targetWindow.addEventListener("load", waitForEvent, true); |
|
732 } |
|
733 |
|
734 // Check if the desired window is already focused. |
|
735 var focusedChildWindow = { }; |
|
736 if (SpecialPowers.activeWindow()) { |
|
737 SpecialPowers.getFocusedElementForWindow(SpecialPowers.activeWindow(), true, focusedChildWindow); |
|
738 focusedChildWindow = focusedChildWindow.value; |
|
739 } |
|
740 |
|
741 // If this is a child frame, ensure that the frame is focused. |
|
742 SimpleTest.waitForFocus_focused = (focusedChildWindow == childTargetWindow); |
|
743 if (SimpleTest.waitForFocus_focused) { |
|
744 // If the frame is already focused and loaded, call the callback directly. |
|
745 maybeRunTests(); |
|
746 } |
|
747 else { |
|
748 info("must wait for focus"); |
|
749 childTargetWindow.addEventListener("focus", waitForEvent, true); |
|
750 SpecialPowers.focus(childTargetWindow); |
|
751 } |
|
752 }; |
|
753 |
|
754 SimpleTest.waitForClipboard_polls = 0; |
|
755 |
|
756 /* |
|
757 * Polls the clipboard waiting for the expected value. A known value different than |
|
758 * the expected value is put on the clipboard first (and also polled for) so we |
|
759 * can be sure the value we get isn't just the expected value because it was already |
|
760 * on the clipboard. This only uses the global clipboard and only for text/unicode |
|
761 * values. |
|
762 * |
|
763 * @param aExpectedStringOrValidatorFn |
|
764 * The string value that is expected to be on the clipboard or a |
|
765 * validator function getting cripboard data and returning a bool. |
|
766 * @param aSetupFn |
|
767 * A function responsible for setting the clipboard to the expected value, |
|
768 * called after the known value setting succeeds. |
|
769 * @param aSuccessFn |
|
770 * A function called when the expected value is found on the clipboard. |
|
771 * @param aFailureFn |
|
772 * A function called if the expected value isn't found on the clipboard |
|
773 * within 5s. It can also be called if the known value can't be found. |
|
774 * @param aFlavor [optional] The flavor to look for. Defaults to "text/unicode". |
|
775 */ |
|
776 SimpleTest.__waitForClipboardMonotonicCounter = 0; |
|
777 SimpleTest.__defineGetter__("_waitForClipboardMonotonicCounter", function () { |
|
778 return SimpleTest.__waitForClipboardMonotonicCounter++; |
|
779 }); |
|
780 SimpleTest.waitForClipboard = function(aExpectedStringOrValidatorFn, aSetupFn, |
|
781 aSuccessFn, aFailureFn, aFlavor) { |
|
782 var requestedFlavor = aFlavor || "text/unicode"; |
|
783 |
|
784 // Build a default validator function for common string input. |
|
785 var inputValidatorFn = typeof(aExpectedStringOrValidatorFn) == "string" |
|
786 ? function(aData) { return aData == aExpectedStringOrValidatorFn; } |
|
787 : aExpectedStringOrValidatorFn; |
|
788 |
|
789 // reset for the next use |
|
790 function reset() { |
|
791 SimpleTest.waitForClipboard_polls = 0; |
|
792 } |
|
793 |
|
794 function wait(validatorFn, successFn, failureFn, flavor) { |
|
795 if (++SimpleTest.waitForClipboard_polls > 50) { |
|
796 // Log the failure. |
|
797 SimpleTest.ok(false, "Timed out while polling clipboard for pasted data."); |
|
798 reset(); |
|
799 failureFn(); |
|
800 return; |
|
801 } |
|
802 |
|
803 var data = SpecialPowers.getClipboardData(flavor); |
|
804 |
|
805 if (validatorFn(data)) { |
|
806 // Don't show the success message when waiting for preExpectedVal |
|
807 if (preExpectedVal) |
|
808 preExpectedVal = null; |
|
809 else |
|
810 SimpleTest.ok(true, "Clipboard has the correct value"); |
|
811 reset(); |
|
812 successFn(); |
|
813 } else { |
|
814 setTimeout(function() { return wait(validatorFn, successFn, failureFn, flavor); }, 100); |
|
815 } |
|
816 } |
|
817 |
|
818 // First we wait for a known value different from the expected one. |
|
819 var preExpectedVal = SimpleTest._waitForClipboardMonotonicCounter + |
|
820 "-waitForClipboard-known-value"; |
|
821 SpecialPowers.clipboardCopyString(preExpectedVal); |
|
822 wait(function(aData) { return aData == preExpectedVal; }, |
|
823 function() { |
|
824 // Call the original setup fn |
|
825 aSetupFn(); |
|
826 wait(inputValidatorFn, aSuccessFn, aFailureFn, requestedFlavor); |
|
827 }, aFailureFn, "text/unicode"); |
|
828 } |
|
829 |
|
830 /** |
|
831 * Executes a function shortly after the call, but lets the caller continue |
|
832 * working (or finish). |
|
833 */ |
|
834 SimpleTest.executeSoon = function(aFunc) { |
|
835 if ("SpecialPowers" in window) { |
|
836 return SpecialPowers.executeSoon(aFunc, window); |
|
837 } |
|
838 setTimeout(aFunc, 0); |
|
839 return null; // Avoid warning. |
|
840 }; |
|
841 |
|
842 SimpleTest.registerCleanupFunction = function(aFunc) { |
|
843 SimpleTest._cleanupFunctions.push(aFunc); |
|
844 }; |
|
845 |
|
846 /** |
|
847 * Finishes the tests. This is automatically called, except when |
|
848 * SimpleTest.waitForExplicitFinish() has been invoked. |
|
849 **/ |
|
850 SimpleTest.finish = function() { |
|
851 var Task = SpecialPowers.Cu.import("resource://gre/modules/Task.jsm").Task; |
|
852 |
|
853 if (SimpleTest._alreadyFinished) { |
|
854 SimpleTest.ok(false, "[SimpleTest.finish()] this test already called finish!"); |
|
855 } |
|
856 |
|
857 SimpleTest._alreadyFinished = true; |
|
858 |
|
859 Task.spawn(function*() { |
|
860 // Execute all of our cleanup functions. |
|
861 var func; |
|
862 while ((func = SimpleTest._cleanupFunctions.pop())) { |
|
863 try { |
|
864 yield func(); |
|
865 } |
|
866 catch (ex) { |
|
867 SimpleTest.ok(false, "Cleanup function threw exception: " + ex); |
|
868 } |
|
869 } |
|
870 |
|
871 if (SpecialPowers.DOMWindowUtils.isTestControllingRefreshes) { |
|
872 SimpleTest.ok(false, "test left refresh driver under test control"); |
|
873 SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); |
|
874 } |
|
875 if (SimpleTest._expectingUncaughtException) { |
|
876 SimpleTest.ok(false, "expectUncaughtException was called but no uncaught exception was detected!"); |
|
877 } |
|
878 if (SimpleTest._pendingWaitForFocusCount != 0) { |
|
879 SimpleTest.is(SimpleTest._pendingWaitForFocusCount, 0, |
|
880 "[SimpleTest.finish()] waitForFocus() was called a " |
|
881 + "different number of times from the number of " |
|
882 + "callbacks run. Maybe the test terminated " |
|
883 + "prematurely -- be sure to use " |
|
884 + "SimpleTest.waitForExplicitFinish()."); |
|
885 } |
|
886 if (SimpleTest._tests.length == 0) { |
|
887 SimpleTest.ok(false, "[SimpleTest.finish()] No checks actually run. " |
|
888 + "(You need to call ok(), is(), or similar " |
|
889 + "functions at least once. Make sure you use " |
|
890 + "SimpleTest.waitForExplicitFinish() if you need " |
|
891 + "it.)"); |
|
892 } |
|
893 |
|
894 if (parentRunner) { |
|
895 /* We're running in an iframe, and the parent has a TestRunner */ |
|
896 parentRunner.testFinished(SimpleTest._tests); |
|
897 } else { |
|
898 SpecialPowers.flushAllAppsLaunchable(); |
|
899 SpecialPowers.flushPermissions(function () { |
|
900 SpecialPowers.flushPrefEnv(function() { |
|
901 SimpleTest.showReport(); |
|
902 }); |
|
903 }); |
|
904 } |
|
905 }); |
|
906 }; |
|
907 |
|
908 /** |
|
909 * Monitor console output from now until endMonitorConsole is called. |
|
910 * |
|
911 * Expect to receive all console messages described by the elements of |
|
912 * |msgs|, an array, in the order listed in |msgs|; each element is an |
|
913 * object which may have any number of the following properties: |
|
914 * message, errorMessage, sourceName, sourceLine, category: |
|
915 * string or regexp |
|
916 * lineNumber, columnNumber: number |
|
917 * isScriptError, isWarning, isException, isStrict: boolean |
|
918 * Strings, numbers, and booleans must compare equal to the named |
|
919 * property of the Nth console message. Regexps must match. Any |
|
920 * fields present in the message but not in the pattern object are ignored. |
|
921 * |
|
922 * In addition to the above properties, elements in |msgs| may have a |forbid| |
|
923 * boolean property. When |forbid| is true, a failure is logged each time a |
|
924 * matching message is received. |
|
925 * |
|
926 * If |forbidUnexpectedMsgs| is true, then the messages received in the console |
|
927 * must exactly match the non-forbidden messages in |msgs|; for each received |
|
928 * message not described by the next element in |msgs|, a failure is logged. If |
|
929 * false, then other non-forbidden messages are ignored, but all expected |
|
930 * messages must still be received. |
|
931 * |
|
932 * After endMonitorConsole is called, |continuation| will be called |
|
933 * asynchronously. (Normally, you will want to pass |SimpleTest.finish| here.) |
|
934 * |
|
935 * It is incorrect to use this function in a test which has not called |
|
936 * SimpleTest.waitForExplicitFinish. |
|
937 */ |
|
938 SimpleTest.monitorConsole = function (continuation, msgs, forbidUnexpectedMsgs) { |
|
939 if (SimpleTest._stopOnLoad) { |
|
940 ok(false, "Console monitoring requires use of waitForExplicitFinish."); |
|
941 } |
|
942 |
|
943 function msgMatches(msg, pat) { |
|
944 for (k in pat) { |
|
945 if (!(k in msg)) { |
|
946 return false; |
|
947 } |
|
948 if (pat[k] instanceof RegExp && typeof(msg[k]) === 'string') { |
|
949 if (!pat[k].test(msg[k])) { |
|
950 return false; |
|
951 } |
|
952 } else if (msg[k] !== pat[k]) { |
|
953 return false; |
|
954 } |
|
955 } |
|
956 return true; |
|
957 } |
|
958 |
|
959 var forbiddenMsgs = []; |
|
960 var i = 0; |
|
961 while (i < msgs.length) { |
|
962 var pat = msgs[i]; |
|
963 if ("forbid" in pat) { |
|
964 var forbid = pat.forbid; |
|
965 delete pat.forbid; |
|
966 if (forbid) { |
|
967 forbiddenMsgs.push(pat); |
|
968 msgs.splice(i, 1); |
|
969 continue; |
|
970 } |
|
971 } |
|
972 i++; |
|
973 } |
|
974 |
|
975 var counter = 0; |
|
976 function listener(msg) { |
|
977 if (msg.message === "SENTINEL" && !msg.isScriptError) { |
|
978 is(counter, msgs.length, "monitorConsole | number of messages"); |
|
979 SimpleTest.executeSoon(continuation); |
|
980 return; |
|
981 } |
|
982 for (var pat of forbiddenMsgs) { |
|
983 if (msgMatches(msg, pat)) { |
|
984 ok(false, "monitorConsole | observed forbidden message " + |
|
985 JSON.stringify(msg)); |
|
986 return; |
|
987 } |
|
988 } |
|
989 if (counter >= msgs.length) { |
|
990 var str = "monitorConsole | extra message | " + JSON.stringify(msg); |
|
991 if (forbidUnexpectedMsgs) { |
|
992 ok(false, str); |
|
993 } else { |
|
994 info(str); |
|
995 } |
|
996 return; |
|
997 } |
|
998 var matches = msgMatches(msg, msgs[counter]); |
|
999 if (forbidUnexpectedMsgs) { |
|
1000 ok(matches, "monitorConsole | [" + counter + "] must match " + |
|
1001 JSON.stringify(msg)); |
|
1002 } else { |
|
1003 info("monitorConsole | [" + counter + "] " + |
|
1004 (matches ? "matched " : "did not match ") + JSON.stringify(msg)); |
|
1005 } |
|
1006 if (matches) |
|
1007 counter++; |
|
1008 } |
|
1009 SpecialPowers.registerConsoleListener(listener); |
|
1010 }; |
|
1011 |
|
1012 /** |
|
1013 * Stop monitoring console output. |
|
1014 */ |
|
1015 SimpleTest.endMonitorConsole = function () { |
|
1016 SpecialPowers.postConsoleSentinel(); |
|
1017 }; |
|
1018 |
|
1019 /** |
|
1020 * Run |testfn| synchronously, and monitor its console output. |
|
1021 * |
|
1022 * |msgs| is handled as described above for monitorConsole. |
|
1023 * |
|
1024 * After |testfn| returns, console monitoring will stop, and |
|
1025 * |continuation| will be called asynchronously. |
|
1026 */ |
|
1027 SimpleTest.expectConsoleMessages = function (testfn, msgs, continuation) { |
|
1028 SimpleTest.monitorConsole(continuation, msgs); |
|
1029 testfn(); |
|
1030 SimpleTest.executeSoon(SimpleTest.endMonitorConsole); |
|
1031 }; |
|
1032 |
|
1033 /** |
|
1034 * Wrapper around |expectConsoleMessages| for the case where the test has |
|
1035 * only one |testfn| to run. |
|
1036 */ |
|
1037 SimpleTest.runTestExpectingConsoleMessages = function(testfn, msgs) { |
|
1038 SimpleTest.waitForExplicitFinish(); |
|
1039 SimpleTest.expectConsoleMessages(testfn, msgs, SimpleTest.finish); |
|
1040 }; |
|
1041 |
|
1042 /** |
|
1043 * Indicates to the test framework that the current test expects one or |
|
1044 * more crashes (from plugins or IPC documents), and that the minidumps from |
|
1045 * those crashes should be removed. |
|
1046 */ |
|
1047 SimpleTest.expectChildProcessCrash = function () { |
|
1048 if (parentRunner) { |
|
1049 parentRunner.expectChildProcessCrash(); |
|
1050 } |
|
1051 }; |
|
1052 |
|
1053 /** |
|
1054 * Indicates to the test framework that the next uncaught exception during |
|
1055 * the test is expected, and should not cause a test failure. |
|
1056 */ |
|
1057 SimpleTest.expectUncaughtException = function (aExpecting) { |
|
1058 SimpleTest._expectingUncaughtException = aExpecting === void 0 || !!aExpecting; |
|
1059 }; |
|
1060 |
|
1061 /** |
|
1062 * Returns whether the test has indicated that it expects an uncaught exception |
|
1063 * to occur. |
|
1064 */ |
|
1065 SimpleTest.isExpectingUncaughtException = function () { |
|
1066 return SimpleTest._expectingUncaughtException; |
|
1067 }; |
|
1068 |
|
1069 /** |
|
1070 * Indicates to the test framework that all of the uncaught exceptions |
|
1071 * during the test are known problems that should be fixed in the future, |
|
1072 * but which should not cause the test to fail currently. |
|
1073 */ |
|
1074 SimpleTest.ignoreAllUncaughtExceptions = function (aIgnoring) { |
|
1075 SimpleTest._ignoringAllUncaughtExceptions = aIgnoring === void 0 || !!aIgnoring; |
|
1076 }; |
|
1077 |
|
1078 /** |
|
1079 * Returns whether the test has indicated that all uncaught exceptions should be |
|
1080 * ignored. |
|
1081 */ |
|
1082 SimpleTest.isIgnoringAllUncaughtExceptions = function () { |
|
1083 return SimpleTest._ignoringAllUncaughtExceptions; |
|
1084 }; |
|
1085 |
|
1086 /** |
|
1087 * Resets any state this SimpleTest object has. This is important for |
|
1088 * browser chrome mochitests, which reuse the same SimpleTest object |
|
1089 * across a run. |
|
1090 */ |
|
1091 SimpleTest.reset = function () { |
|
1092 SimpleTest._ignoringAllUncaughtExceptions = false; |
|
1093 SimpleTest._expectingUncaughtException = false; |
|
1094 SimpleTest._bufferedMessages = []; |
|
1095 }; |
|
1096 |
|
1097 if (isPrimaryTestWindow) { |
|
1098 addLoadEvent(function() { |
|
1099 if (SimpleTest._stopOnLoad) { |
|
1100 SimpleTest.finish(); |
|
1101 } |
|
1102 }); |
|
1103 } |
|
1104 |
|
1105 // --------------- Test.Builder/Test.More isDeeply() ----------------- |
|
1106 |
|
1107 |
|
1108 SimpleTest.DNE = {dne: 'Does not exist'}; |
|
1109 SimpleTest.LF = "\r\n"; |
|
1110 SimpleTest._isRef = function (object) { |
|
1111 var type = typeof(object); |
|
1112 return type == 'object' || type == 'function'; |
|
1113 }; |
|
1114 |
|
1115 |
|
1116 SimpleTest._deepCheck = function (e1, e2, stack, seen) { |
|
1117 var ok = false; |
|
1118 // Either they're both references or both not. |
|
1119 var sameRef = !(!SimpleTest._isRef(e1) ^ !SimpleTest._isRef(e2)); |
|
1120 if (e1 == null && e2 == null) { |
|
1121 ok = true; |
|
1122 } else if (e1 != null ^ e2 != null) { |
|
1123 ok = false; |
|
1124 } else if (e1 == SimpleTest.DNE ^ e2 == SimpleTest.DNE) { |
|
1125 ok = false; |
|
1126 } else if (sameRef && e1 == e2) { |
|
1127 // Handles primitives and any variables that reference the same |
|
1128 // object, including functions. |
|
1129 ok = true; |
|
1130 } else if (SimpleTest.isa(e1, 'Array') && SimpleTest.isa(e2, 'Array')) { |
|
1131 ok = SimpleTest._eqArray(e1, e2, stack, seen); |
|
1132 } else if (typeof e1 == "object" && typeof e2 == "object") { |
|
1133 ok = SimpleTest._eqAssoc(e1, e2, stack, seen); |
|
1134 } else if (typeof e1 == "number" && typeof e2 == "number" |
|
1135 && isNaN(e1) && isNaN(e2)) { |
|
1136 ok = true; |
|
1137 } else { |
|
1138 // If we get here, they're not the same (function references must |
|
1139 // always simply reference the same function). |
|
1140 stack.push({ vals: [e1, e2] }); |
|
1141 ok = false; |
|
1142 } |
|
1143 return ok; |
|
1144 }; |
|
1145 |
|
1146 SimpleTest._eqArray = function (a1, a2, stack, seen) { |
|
1147 // Return if they're the same object. |
|
1148 if (a1 == a2) return true; |
|
1149 |
|
1150 // JavaScript objects have no unique identifiers, so we have to store |
|
1151 // references to them all in an array, and then compare the references |
|
1152 // directly. It's slow, but probably won't be much of an issue in |
|
1153 // practice. Start by making a local copy of the array to as to avoid |
|
1154 // confusing a reference seen more than once (such as [a, a]) for a |
|
1155 // circular reference. |
|
1156 for (var j = 0; j < seen.length; j++) { |
|
1157 if (seen[j][0] == a1) { |
|
1158 return seen[j][1] == a2; |
|
1159 } |
|
1160 } |
|
1161 |
|
1162 // If we get here, we haven't seen a1 before, so store it with reference |
|
1163 // to a2. |
|
1164 seen.push([ a1, a2 ]); |
|
1165 |
|
1166 var ok = true; |
|
1167 // Only examines enumerable attributes. Only works for numeric arrays! |
|
1168 // Associative arrays return 0. So call _eqAssoc() for them, instead. |
|
1169 var max = a1.length > a2.length ? a1.length : a2.length; |
|
1170 if (max == 0) return SimpleTest._eqAssoc(a1, a2, stack, seen); |
|
1171 for (var i = 0; i < max; i++) { |
|
1172 var e1 = i > a1.length - 1 ? SimpleTest.DNE : a1[i]; |
|
1173 var e2 = i > a2.length - 1 ? SimpleTest.DNE : a2[i]; |
|
1174 stack.push({ type: 'Array', idx: i, vals: [e1, e2] }); |
|
1175 ok = SimpleTest._deepCheck(e1, e2, stack, seen); |
|
1176 if (ok) { |
|
1177 stack.pop(); |
|
1178 } else { |
|
1179 break; |
|
1180 } |
|
1181 } |
|
1182 return ok; |
|
1183 }; |
|
1184 |
|
1185 SimpleTest._eqAssoc = function (o1, o2, stack, seen) { |
|
1186 // Return if they're the same object. |
|
1187 if (o1 == o2) return true; |
|
1188 |
|
1189 // JavaScript objects have no unique identifiers, so we have to store |
|
1190 // references to them all in an array, and then compare the references |
|
1191 // directly. It's slow, but probably won't be much of an issue in |
|
1192 // practice. Start by making a local copy of the array to as to avoid |
|
1193 // confusing a reference seen more than once (such as [a, a]) for a |
|
1194 // circular reference. |
|
1195 seen = seen.slice(0); |
|
1196 for (var j = 0; j < seen.length; j++) { |
|
1197 if (seen[j][0] == o1) { |
|
1198 return seen[j][1] == o2; |
|
1199 } |
|
1200 } |
|
1201 |
|
1202 // If we get here, we haven't seen o1 before, so store it with reference |
|
1203 // to o2. |
|
1204 seen.push([ o1, o2 ]); |
|
1205 |
|
1206 // They should be of the same class. |
|
1207 |
|
1208 var ok = true; |
|
1209 // Only examines enumerable attributes. |
|
1210 var o1Size = 0; for (var i in o1) o1Size++; |
|
1211 var o2Size = 0; for (var i in o2) o2Size++; |
|
1212 var bigger = o1Size > o2Size ? o1 : o2; |
|
1213 for (var i in bigger) { |
|
1214 var e1 = o1[i] == undefined ? SimpleTest.DNE : o1[i]; |
|
1215 var e2 = o2[i] == undefined ? SimpleTest.DNE : o2[i]; |
|
1216 stack.push({ type: 'Object', idx: i, vals: [e1, e2] }); |
|
1217 ok = SimpleTest._deepCheck(e1, e2, stack, seen) |
|
1218 if (ok) { |
|
1219 stack.pop(); |
|
1220 } else { |
|
1221 break; |
|
1222 } |
|
1223 } |
|
1224 return ok; |
|
1225 }; |
|
1226 |
|
1227 SimpleTest._formatStack = function (stack) { |
|
1228 var variable = '$Foo'; |
|
1229 for (var i = 0; i < stack.length; i++) { |
|
1230 var entry = stack[i]; |
|
1231 var type = entry['type']; |
|
1232 var idx = entry['idx']; |
|
1233 if (idx != null) { |
|
1234 if (/^\d+$/.test(idx)) { |
|
1235 // Numeric array index. |
|
1236 variable += '[' + idx + ']'; |
|
1237 } else { |
|
1238 // Associative array index. |
|
1239 idx = idx.replace("'", "\\'"); |
|
1240 variable += "['" + idx + "']"; |
|
1241 } |
|
1242 } |
|
1243 } |
|
1244 |
|
1245 var vals = stack[stack.length-1]['vals'].slice(0, 2); |
|
1246 var vars = [ |
|
1247 variable.replace('$Foo', 'got'), |
|
1248 variable.replace('$Foo', 'expected') |
|
1249 ]; |
|
1250 |
|
1251 var out = "Structures begin differing at:" + SimpleTest.LF; |
|
1252 for (var i = 0; i < vals.length; i++) { |
|
1253 var val = vals[i]; |
|
1254 if (val == null) { |
|
1255 val = 'undefined'; |
|
1256 } else { |
|
1257 val == SimpleTest.DNE ? "Does not exist" : "'" + val + "'"; |
|
1258 } |
|
1259 } |
|
1260 |
|
1261 out += vars[0] + ' = ' + vals[0] + SimpleTest.LF; |
|
1262 out += vars[1] + ' = ' + vals[1] + SimpleTest.LF; |
|
1263 |
|
1264 return ' ' + out; |
|
1265 }; |
|
1266 |
|
1267 |
|
1268 SimpleTest.isDeeply = function (it, as, name) { |
|
1269 var ok; |
|
1270 // ^ is the XOR operator. |
|
1271 if (SimpleTest._isRef(it) ^ SimpleTest._isRef(as)) { |
|
1272 // One's a reference, one isn't. |
|
1273 ok = false; |
|
1274 } else if (!SimpleTest._isRef(it) && !SimpleTest._isRef(as)) { |
|
1275 // Neither is an object. |
|
1276 ok = SimpleTest.is(it, as, name); |
|
1277 } else { |
|
1278 // We have two objects. Do a deep comparison. |
|
1279 var stack = [], seen = []; |
|
1280 if ( SimpleTest._deepCheck(it, as, stack, seen)) { |
|
1281 ok = SimpleTest.ok(true, name); |
|
1282 } else { |
|
1283 ok = SimpleTest.ok(false, name, SimpleTest._formatStack(stack)); |
|
1284 } |
|
1285 } |
|
1286 return ok; |
|
1287 }; |
|
1288 |
|
1289 SimpleTest.typeOf = function (object) { |
|
1290 var c = Object.prototype.toString.apply(object); |
|
1291 var name = c.substring(8, c.length - 1); |
|
1292 if (name != 'Object') return name; |
|
1293 // It may be a non-core class. Try to extract the class name from |
|
1294 // the constructor function. This may not work in all implementations. |
|
1295 if (/function ([^(\s]+)/.test(Function.toString.call(object.constructor))) { |
|
1296 return RegExp.$1; |
|
1297 } |
|
1298 // No idea. :-( |
|
1299 return name; |
|
1300 }; |
|
1301 |
|
1302 SimpleTest.isa = function (object, clas) { |
|
1303 return SimpleTest.typeOf(object) == clas; |
|
1304 }; |
|
1305 |
|
1306 // Global symbols: |
|
1307 var ok = SimpleTest.ok; |
|
1308 var is = SimpleTest.is; |
|
1309 var isfuzzy = SimpleTest.isfuzzy; |
|
1310 var isnot = SimpleTest.isnot; |
|
1311 var ise = SimpleTest.ise; |
|
1312 var todo = SimpleTest.todo; |
|
1313 var todo_is = SimpleTest.todo_is; |
|
1314 var todo_isnot = SimpleTest.todo_isnot; |
|
1315 var isDeeply = SimpleTest.isDeeply; |
|
1316 var info = SimpleTest.info; |
|
1317 |
|
1318 var gOldOnError = window.onerror; |
|
1319 window.onerror = function simpletestOnerror(errorMsg, url, lineNumber) { |
|
1320 // Log the message. |
|
1321 // XXX Chrome mochitests sometimes trigger this window.onerror handler, |
|
1322 // but there are a number of uncaught JS exceptions from those tests. |
|
1323 // For now, for tests that self identify as having unintentional uncaught |
|
1324 // exceptions, just dump it so that the error is visible but doesn't cause |
|
1325 // a test failure. See bug 652494. |
|
1326 var isExpected = !!SimpleTest._expectingUncaughtException; |
|
1327 var message = (isExpected ? "expected " : "") + "uncaught exception"; |
|
1328 var error = errorMsg + " at " + url + ":" + lineNumber; |
|
1329 if (!SimpleTest._ignoringAllUncaughtExceptions) { |
|
1330 SimpleTest.ok(isExpected, message, error); |
|
1331 SimpleTest._expectingUncaughtException = false; |
|
1332 } else { |
|
1333 SimpleTest.todo(false, message + ": " + error); |
|
1334 } |
|
1335 // There is no Components.stack.caller to log. (See bug 511888.) |
|
1336 |
|
1337 // Call previous handler. |
|
1338 if (gOldOnError) { |
|
1339 try { |
|
1340 // Ignore return value: always run default handler. |
|
1341 gOldOnError(errorMsg, url, lineNumber); |
|
1342 } catch (e) { |
|
1343 // Log the error. |
|
1344 SimpleTest.info("Exception thrown by gOldOnError(): " + e); |
|
1345 // Log its stack. |
|
1346 if (e.stack) { |
|
1347 SimpleTest.info("JavaScript error stack:\n" + e.stack); |
|
1348 } |
|
1349 } |
|
1350 } |
|
1351 |
|
1352 if (!SimpleTest._stopOnLoad && !isExpected) { |
|
1353 // Need to finish() manually here, yet let the test actually end first. |
|
1354 SimpleTest.executeSoon(SimpleTest.finish); |
|
1355 } |
|
1356 }; |