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