1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/testing/mochitest/tests/SimpleTest/SimpleTest.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1356 @@ 1.4 +/* -*- js-indent-level: 4; tab-width: 4; indent-tabs-mode: nil -*- */ 1.5 +/* vim:set ts=4 sw=4 sts=4 et: */ 1.6 +/** 1.7 + * SimpleTest, a partial Test.Simple/Test.More API compatible test library. 1.8 + * 1.9 + * Why? 1.10 + * 1.11 + * Test.Simple doesn't work on IE < 6. 1.12 + * TODO: 1.13 + * * Support the Test.Simple API used by MochiKit, to be able to test MochiKit 1.14 + * itself against IE 5.5 1.15 + * 1.16 + * NOTE: Pay attention to cross-browser compatibility in this file. For 1.17 + * instance, do not use const or JS > 1.5 features which are not yet 1.18 + * implemented everywhere. 1.19 + * 1.20 +**/ 1.21 + 1.22 +var SimpleTest = { }; 1.23 +var parentRunner = null; 1.24 + 1.25 +// In normal test runs, the window that has a TestRunner in its parent is 1.26 +// the primary window. In single test runs, if there is no parent and there 1.27 +// is no opener then it is the primary window. 1.28 +var isSingleTestRun = (parent == window && !opener) 1.29 +var isPrimaryTestWindow = !!parent.TestRunner || isSingleTestRun; 1.30 + 1.31 +// Finds the TestRunner for this test run and the SpecialPowers object (in 1.32 +// case it is not defined) from a parent/opener window. 1.33 +// 1.34 +// Finding the SpecialPowers object is needed when we have ChromePowers in 1.35 +// harness.xul and we need SpecialPowers in the iframe, and also for tests 1.36 +// like test_focus.xul where we open a window which opens another window which 1.37 +// includes SimpleTest.js. 1.38 +(function() { 1.39 + function ancestor(w) { 1.40 + return w.parent != w ? w.parent : w.opener; 1.41 + } 1.42 + 1.43 + var w = ancestor(window); 1.44 + while (w && (!parentRunner || !window.SpecialPowers)) { 1.45 + if (!parentRunner) { 1.46 + parentRunner = w.TestRunner; 1.47 + if (!parentRunner && w.wrappedJSObject) { 1.48 + parentRunner = w.wrappedJSObject.TestRunner; 1.49 + } 1.50 + } 1.51 + if (!window.SpecialPowers) { 1.52 + window.SpecialPowers = w.SpecialPowers; 1.53 + } 1.54 + w = ancestor(w); 1.55 + } 1.56 +})(); 1.57 + 1.58 +/* Helper functions pulled out of various MochiKit modules */ 1.59 +if (typeof(repr) == 'undefined') { 1.60 + this.repr = function(o) { 1.61 + if (typeof(o) == "undefined") { 1.62 + return "undefined"; 1.63 + } else if (o === null) { 1.64 + return "null"; 1.65 + } 1.66 + try { 1.67 + if (typeof(o.__repr__) == 'function') { 1.68 + return o.__repr__(); 1.69 + } else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) { 1.70 + return o.repr(); 1.71 + } 1.72 + } catch (e) { 1.73 + } 1.74 + try { 1.75 + if (typeof(o.NAME) == 'string' && ( 1.76 + o.toString == Function.prototype.toString || 1.77 + o.toString == Object.prototype.toString 1.78 + )) { 1.79 + return o.NAME; 1.80 + } 1.81 + } catch (e) { 1.82 + } 1.83 + try { 1.84 + var ostring = (o + ""); 1.85 + } catch (e) { 1.86 + return "[" + typeof(o) + "]"; 1.87 + } 1.88 + if (typeof(o) == "function") { 1.89 + o = ostring.replace(/^\s+/, ""); 1.90 + var idx = o.indexOf("{"); 1.91 + if (idx != -1) { 1.92 + o = o.substr(0, idx) + "{...}"; 1.93 + } 1.94 + } 1.95 + return ostring; 1.96 + }; 1.97 +} 1.98 + 1.99 +/* This returns a function that applies the previously given parameters. 1.100 + * This is used by SimpleTest.showReport 1.101 + */ 1.102 +if (typeof(partial) == 'undefined') { 1.103 + this.partial = function(func) { 1.104 + var args = []; 1.105 + for (var i = 1; i < arguments.length; i++) { 1.106 + args.push(arguments[i]); 1.107 + } 1.108 + return function() { 1.109 + if (arguments.length > 0) { 1.110 + for (var i = 1; i < arguments.length; i++) { 1.111 + args.push(arguments[i]); 1.112 + } 1.113 + } 1.114 + func(args); 1.115 + }; 1.116 + }; 1.117 +} 1.118 + 1.119 +if (typeof(getElement) == 'undefined') { 1.120 + this.getElement = function(id) { 1.121 + return ((typeof(id) == "string") ? 1.122 + document.getElementById(id) : id); 1.123 + }; 1.124 + this.$ = this.getElement; 1.125 +} 1.126 + 1.127 +SimpleTest._newCallStack = function(path) { 1.128 + var rval = function () { 1.129 + var callStack = arguments.callee.callStack; 1.130 + for (var i = 0; i < callStack.length; i++) { 1.131 + if (callStack[i].apply(this, arguments) === false) { 1.132 + break; 1.133 + } 1.134 + } 1.135 + try { 1.136 + this[path] = null; 1.137 + } catch (e) { 1.138 + // pass 1.139 + } 1.140 + }; 1.141 + rval.callStack = []; 1.142 + return rval; 1.143 +}; 1.144 + 1.145 +if (typeof(addLoadEvent) == 'undefined') { 1.146 + this.addLoadEvent = function(func) { 1.147 + var existing = window["onload"]; 1.148 + var regfunc = existing; 1.149 + if (!(typeof(existing) == 'function' 1.150 + && typeof(existing.callStack) == "object" 1.151 + && existing.callStack !== null)) { 1.152 + regfunc = SimpleTest._newCallStack("onload"); 1.153 + if (typeof(existing) == 'function') { 1.154 + regfunc.callStack.push(existing); 1.155 + } 1.156 + window["onload"] = regfunc; 1.157 + } 1.158 + regfunc.callStack.push(func); 1.159 + }; 1.160 +} 1.161 + 1.162 +function createEl(type, attrs, html) { 1.163 + //use createElementNS so the xul/xhtml tests have no issues 1.164 + var el; 1.165 + if (!document.body) { 1.166 + el = document.createElementNS("http://www.w3.org/1999/xhtml", type); 1.167 + } 1.168 + else { 1.169 + el = document.createElement(type); 1.170 + } 1.171 + if (attrs !== null && attrs !== undefined) { 1.172 + for (var k in attrs) { 1.173 + el.setAttribute(k, attrs[k]); 1.174 + } 1.175 + } 1.176 + if (html !== null && html !== undefined) { 1.177 + el.appendChild(document.createTextNode(html)); 1.178 + } 1.179 + return el; 1.180 +} 1.181 + 1.182 +/* lots of tests use this as a helper to get css properties */ 1.183 +if (typeof(computedStyle) == 'undefined') { 1.184 + this.computedStyle = function(elem, cssProperty) { 1.185 + elem = getElement(elem); 1.186 + if (elem.currentStyle) { 1.187 + return elem.currentStyle[cssProperty]; 1.188 + } 1.189 + if (typeof(document.defaultView) == 'undefined' || document === null) { 1.190 + return undefined; 1.191 + } 1.192 + var style = document.defaultView.getComputedStyle(elem, null); 1.193 + if (typeof(style) == 'undefined' || style === null) { 1.194 + return undefined; 1.195 + } 1.196 + 1.197 + var selectorCase = cssProperty.replace(/([A-Z])/g, '-$1' 1.198 + ).toLowerCase(); 1.199 + 1.200 + return style.getPropertyValue(selectorCase); 1.201 + }; 1.202 +} 1.203 + 1.204 +/** 1.205 + * Check for OOP test plugin 1.206 +**/ 1.207 +SimpleTest.testPluginIsOOP = function () { 1.208 + var testPluginIsOOP = false; 1.209 + if (navigator.platform.indexOf("Mac") == 0) { 1.210 + if (SpecialPowers.XPCOMABI.match(/x86-/)) { 1.211 + try { 1.212 + testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.i386.test.plugin"); 1.213 + } catch (e) { 1.214 + testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.i386"); 1.215 + } 1.216 + } 1.217 + else if (SpecialPowers.XPCOMABI.match(/x86_64-/)) { 1.218 + try { 1.219 + testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.x86_64.test.plugin"); 1.220 + } catch (e) { 1.221 + testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.x86_64"); 1.222 + } 1.223 + } 1.224 + } 1.225 + else { 1.226 + testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled"); 1.227 + } 1.228 + 1.229 + return testPluginIsOOP; 1.230 +}; 1.231 + 1.232 +SimpleTest._tests = []; 1.233 +SimpleTest._stopOnLoad = true; 1.234 +SimpleTest._cleanupFunctions = []; 1.235 + 1.236 +/** 1.237 + * Something like assert. 1.238 +**/ 1.239 +SimpleTest.ok = function (condition, name, diag) { 1.240 + var test = {'result': !!condition, 'name': name, 'diag': diag}; 1.241 + SimpleTest._logResult(test, "TEST-PASS", "TEST-UNEXPECTED-FAIL"); 1.242 + SimpleTest._tests.push(test); 1.243 +}; 1.244 + 1.245 +/** 1.246 + * Roughly equivalent to ok(a==b, name) 1.247 +**/ 1.248 +SimpleTest.is = function (a, b, name) { 1.249 + var pass = (a == b); 1.250 + var diag = pass ? "" : "got " + repr(a) + ", expected " + repr(b) 1.251 + SimpleTest.ok(pass, name, diag); 1.252 +}; 1.253 + 1.254 +SimpleTest.isfuzzy = function (a, b, epsilon, name) { 1.255 + var pass = (a > b - epsilon) && (a < b + epsilon); 1.256 + var diag = pass ? "" : "got " + repr(a) + ", expected " + repr(b) + " epsilon: +/- " + repr(epsilon) 1.257 + SimpleTest.ok(pass, name, diag); 1.258 +}; 1.259 + 1.260 +SimpleTest.isnot = function (a, b, name) { 1.261 + var pass = (a != b); 1.262 + var diag = pass ? "" : "didn't expect " + repr(a) + ", but got it"; 1.263 + SimpleTest.ok(pass, name, diag); 1.264 +}; 1.265 + 1.266 +/** 1.267 + * Roughly equivalent to ok(a===b, name) 1.268 +**/ 1.269 +SimpleTest.ise = function (a, b, name) { 1.270 + var pass = (a === b); 1.271 + var diag = pass ? "" : "got " + repr(a) + ", strictly expected " + repr(b) 1.272 + SimpleTest.ok(pass, name, diag); 1.273 +}; 1.274 + 1.275 +/** 1.276 + * Check that the function call throws an exception. 1.277 + */ 1.278 +SimpleTest.doesThrow = function(fn, name) { 1.279 + var gotException = false; 1.280 + try { 1.281 + fn(); 1.282 + } catch (ex) { gotException = true; } 1.283 + ok(gotException, name); 1.284 +}; 1.285 + 1.286 +// --------------- Test.Builder/Test.More todo() ----------------- 1.287 + 1.288 +SimpleTest.todo = function(condition, name, diag) { 1.289 + var test = {'result': !!condition, 'name': name, 'diag': diag, todo: true}; 1.290 + SimpleTest._logResult(test, "TEST-UNEXPECTED-PASS", "TEST-KNOWN-FAIL"); 1.291 + SimpleTest._tests.push(test); 1.292 +}; 1.293 + 1.294 +/* 1.295 + * Returns the absolute URL to a test data file from where tests 1.296 + * are served. i.e. the file doesn't necessarely exists where tests 1.297 + * are executed. 1.298 + * (For b2g and android, mochitest are executed on the device, while 1.299 + * all mochitest html (and others) files are served from the test runner 1.300 + * slave) 1.301 + */ 1.302 +SimpleTest.getTestFileURL = function(path) { 1.303 + var lastSlashIdx = path.lastIndexOf("/") + 1; 1.304 + var filename = path.substr(lastSlashIdx); 1.305 + var location = window.location; 1.306 + // Remove mochitest html file name from the path 1.307 + var remotePath = location.pathname.replace(/\/[^\/]+?$/,""); 1.308 + var url = location.origin + 1.309 + remotePath + "/" + path; 1.310 + return url; 1.311 +}; 1.312 + 1.313 +SimpleTest._getCurrentTestURL = function() { 1.314 + return parentRunner && parentRunner.currentTestURL || 1.315 + typeof gTestPath == "string" && gTestPath || 1.316 + "unknown test url"; 1.317 +}; 1.318 + 1.319 +SimpleTest._forceLogMessageOutput = parentRunner && !parentRunner.quiet; 1.320 + 1.321 +/** 1.322 + * Force all test messages to be displayed. Only applies for the current test. 1.323 + */ 1.324 +SimpleTest.requestCompleteLog = function() { 1.325 + if (SimpleTest._forceLogMessageOutput) 1.326 + return; 1.327 + 1.328 + SimpleTest._forceLogMessageOutput = true; 1.329 + SimpleTest.registerCleanupFunction(function() { 1.330 + SimpleTest._forceLogMessageOutput = false; 1.331 + }); 1.332 +}; 1.333 + 1.334 +/** 1.335 + * A circular buffer, managed by _logResult. We explicitly manage the 1.336 + * circularness of the buffer, rather than resorting to .shift()/.push() 1.337 + * because explicit management is much faster. 1.338 + */ 1.339 +SimpleTest._bufferedMessages = []; 1.340 +SimpleTest._logResult = (function () { 1.341 + var bufferingThreshold = 100; 1.342 + var outputIndex = 0; 1.343 + 1.344 + function logResult(test, passString, failString) { 1.345 + var url = SimpleTest._getCurrentTestURL(); 1.346 + var resultString = test.result ? passString : failString; 1.347 + var diagnostic = test.name + (test.diag ? " - " + test.diag : ""); 1.348 + var msg = [resultString, url, diagnostic].join(" | "); 1.349 + var isError = !test.result == !test.todo; 1.350 + 1.351 + // Due to JavaScript's name lookup rules, it is important that 1.352 + // the second parameter here be named identically to the isError 1.353 + // variable declared above. 1.354 + function dumpMessage(msg, isError) { 1.355 + if (parentRunner) { 1.356 + if (isError) { 1.357 + parentRunner.addFailedTest(url); 1.358 + parentRunner.error(msg); 1.359 + } else { 1.360 + parentRunner.log(msg); 1.361 + } 1.362 + } else if (typeof dump === "function") { 1.363 + dump(msg + "\n"); 1.364 + } else { 1.365 + // Non-Mozilla browser? Just do nothing. 1.366 + } 1.367 + } 1.368 + 1.369 + // Detect when SimpleTest.reset() has been called, so we can 1.370 + // reset outputIndex. We store outputIndex as local state to 1.371 + // avoid adding even more state to SimpleTest. 1.372 + if (SimpleTest._bufferedMessages.length == 0) { 1.373 + outputIndex = 0; 1.374 + } 1.375 + 1.376 + // We want to eliminate mundane TEST-PASS/TEST-KNOWN-FAIL 1.377 + // output, since some tests produce tens of thousands of of such 1.378 + // messages. These messages can consume a lot of memory to 1.379 + // generate and take a significant amount of time to output. 1.380 + // However, the reality is that TEST-PASS messages can also be 1.381 + // used as a form of logging via constructs like: 1.382 + // 1.383 + // SimpleTest.ok(true, "some informative message"); 1.384 + // 1.385 + // And eliding the logging can be very confusing when trying to 1.386 + // debug test failures. 1.387 + // 1.388 + // Hence the compromise adopted here: We buffer messages up to 1.389 + // some limit and dump the buffer when a test failure happens. 1.390 + // This behavior ought to provide enough context for developers 1.391 + // looking to understand where in the test things failed. 1.392 + if (isError) { 1.393 + // Display this message and all the messages we have buffered. 1.394 + if (SimpleTest._bufferedMessages.length > 0) { 1.395 + dumpMessage("TEST-INFO | dumping last " + SimpleTest._bufferedMessages.length + " message(s)"); 1.396 + dumpMessage("TEST-INFO | if you need more context, please use SimpleTest.requestCompleteLog() in your test"); 1.397 + 1.398 + function dumpBufferedMessage(m) { 1.399 + dumpMessage(m, false); 1.400 + } 1.401 + // The latest message is just before outputIndex. 1.402 + // The earliest message is located at outputIndex. 1.403 + var earliest = SimpleTest._bufferedMessages.slice(outputIndex); 1.404 + var latest = SimpleTest._bufferedMessages.slice(0, outputIndex); 1.405 + earliest.map(dumpBufferedMessage); 1.406 + latest.map(dumpBufferedMessage); 1.407 + 1.408 + SimpleTest._bufferedMessages = []; 1.409 + } 1.410 + 1.411 + dumpMessage(msg); 1.412 + return; 1.413 + } 1.414 + 1.415 + var runningSingleTest = ((parentRunner && 1.416 + parentRunner._urls.length == 1) || 1.417 + isSingleTestRun); 1.418 + var shouldLogImmediately = (runningSingleTest || 1.419 + SimpleTest._forceLogMessageOutput); 1.420 + 1.421 + if (!shouldLogImmediately) { 1.422 + // Buffer the message for possible later output. 1.423 + if (SimpleTest._bufferedMessages.length >= bufferingThreshold) { 1.424 + if (outputIndex >= bufferingThreshold) { 1.425 + outputIndex = 0; 1.426 + } 1.427 + SimpleTest._bufferedMessages[outputIndex] = msg; 1.428 + outputIndex++; 1.429 + } else { 1.430 + SimpleTest._bufferedMessages.push(msg); 1.431 + } 1.432 + return; 1.433 + } 1.434 + 1.435 + dumpMessage(msg); 1.436 + } 1.437 + 1.438 + return logResult; 1.439 +})(); 1.440 + 1.441 +SimpleTest.info = function(name, message) { 1.442 + SimpleTest._logResult({result:true, name:name, diag:message}, "TEST-INFO"); 1.443 +}; 1.444 + 1.445 +/** 1.446 + * Copies of is and isnot with the call to ok replaced by a call to todo. 1.447 +**/ 1.448 + 1.449 +SimpleTest.todo_is = function (a, b, name) { 1.450 + var pass = (a == b); 1.451 + var diag = pass ? repr(a) + " should equal " + repr(b) 1.452 + : "got " + repr(a) + ", expected " + repr(b); 1.453 + SimpleTest.todo(pass, name, diag); 1.454 +}; 1.455 + 1.456 +SimpleTest.todo_isnot = function (a, b, name) { 1.457 + var pass = (a != b); 1.458 + var diag = pass ? repr(a) + " should not equal " + repr(b) 1.459 + : "didn't expect " + repr(a) + ", but got it"; 1.460 + SimpleTest.todo(pass, name, diag); 1.461 +}; 1.462 + 1.463 + 1.464 +/** 1.465 + * Makes a test report, returns it as a DIV element. 1.466 +**/ 1.467 +SimpleTest.report = function () { 1.468 + var passed = 0; 1.469 + var failed = 0; 1.470 + var todo = 0; 1.471 + 1.472 + var tallyAndCreateDiv = function (test) { 1.473 + var cls, msg, div; 1.474 + var diag = test.diag ? " - " + test.diag : ""; 1.475 + if (test.todo && !test.result) { 1.476 + todo++; 1.477 + cls = "test_todo"; 1.478 + msg = "todo | " + test.name + diag; 1.479 + } else if (test.result && !test.todo) { 1.480 + passed++; 1.481 + cls = "test_ok"; 1.482 + msg = "passed | " + test.name + diag; 1.483 + } else { 1.484 + failed++; 1.485 + cls = "test_not_ok"; 1.486 + msg = "failed | " + test.name + diag; 1.487 + } 1.488 + div = createEl('div', {'class': cls}, msg); 1.489 + return div; 1.490 + }; 1.491 + var results = []; 1.492 + for (var d=0; d<SimpleTest._tests.length; d++) { 1.493 + results.push(tallyAndCreateDiv(SimpleTest._tests[d])); 1.494 + } 1.495 + 1.496 + var summary_class = failed != 0 ? 'some_fail' : 1.497 + passed == 0 ? 'todo_only' : 'all_pass'; 1.498 + 1.499 + var div1 = createEl('div', {'class': 'tests_report'}); 1.500 + var div2 = createEl('div', {'class': 'tests_summary ' + summary_class}); 1.501 + var div3 = createEl('div', {'class': 'tests_passed'}, 'Passed: ' + passed); 1.502 + var div4 = createEl('div', {'class': 'tests_failed'}, 'Failed: ' + failed); 1.503 + var div5 = createEl('div', {'class': 'tests_todo'}, 'Todo: ' + todo); 1.504 + div2.appendChild(div3); 1.505 + div2.appendChild(div4); 1.506 + div2.appendChild(div5); 1.507 + div1.appendChild(div2); 1.508 + for (var t=0; t<results.length; t++) { 1.509 + //iterate in order 1.510 + div1.appendChild(results[t]); 1.511 + } 1.512 + return div1; 1.513 +}; 1.514 + 1.515 +/** 1.516 + * Toggle element visibility 1.517 +**/ 1.518 +SimpleTest.toggle = function(el) { 1.519 + if (computedStyle(el, 'display') == 'block') { 1.520 + el.style.display = 'none'; 1.521 + } else { 1.522 + el.style.display = 'block'; 1.523 + } 1.524 +}; 1.525 + 1.526 +/** 1.527 + * Toggle visibility for divs with a specific class. 1.528 +**/ 1.529 +SimpleTest.toggleByClass = function (cls, evt) { 1.530 + var children = document.getElementsByTagName('div'); 1.531 + var elements = []; 1.532 + for (var i=0; i<children.length; i++) { 1.533 + var child = children[i]; 1.534 + var clsName = child.className; 1.535 + if (!clsName) { 1.536 + continue; 1.537 + } 1.538 + var classNames = clsName.split(' '); 1.539 + for (var j = 0; j < classNames.length; j++) { 1.540 + if (classNames[j] == cls) { 1.541 + elements.push(child); 1.542 + break; 1.543 + } 1.544 + } 1.545 + } 1.546 + for (var t=0; t<elements.length; t++) { 1.547 + //TODO: again, for-in loop over elems seems to break this 1.548 + SimpleTest.toggle(elements[t]); 1.549 + } 1.550 + if (evt) 1.551 + evt.preventDefault(); 1.552 +}; 1.553 + 1.554 +/** 1.555 + * Shows the report in the browser 1.556 +**/ 1.557 +SimpleTest.showReport = function() { 1.558 + var togglePassed = createEl('a', {'href': '#'}, "Toggle passed checks"); 1.559 + var toggleFailed = createEl('a', {'href': '#'}, "Toggle failed checks"); 1.560 + var toggleTodo = createEl('a',{'href': '#'}, "Toggle todo checks"); 1.561 + togglePassed.onclick = partial(SimpleTest.toggleByClass, 'test_ok'); 1.562 + toggleFailed.onclick = partial(SimpleTest.toggleByClass, 'test_not_ok'); 1.563 + toggleTodo.onclick = partial(SimpleTest.toggleByClass, 'test_todo'); 1.564 + var body = document.body; // Handles HTML documents 1.565 + if (!body) { 1.566 + // Do the XML thing. 1.567 + body = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", 1.568 + "body")[0]; 1.569 + } 1.570 + var firstChild = body.childNodes[0]; 1.571 + var addNode; 1.572 + if (firstChild) { 1.573 + addNode = function (el) { 1.574 + body.insertBefore(el, firstChild); 1.575 + }; 1.576 + } else { 1.577 + addNode = function (el) { 1.578 + body.appendChild(el) 1.579 + }; 1.580 + } 1.581 + addNode(togglePassed); 1.582 + addNode(createEl('span', null, " ")); 1.583 + addNode(toggleFailed); 1.584 + addNode(createEl('span', null, " ")); 1.585 + addNode(toggleTodo); 1.586 + addNode(SimpleTest.report()); 1.587 + // Add a separator from the test content. 1.588 + addNode(createEl('hr')); 1.589 +}; 1.590 + 1.591 +/** 1.592 + * Tells SimpleTest to don't finish the test when the document is loaded, 1.593 + * useful for asynchronous tests. 1.594 + * 1.595 + * When SimpleTest.waitForExplicitFinish is called, 1.596 + * explicit SimpleTest.finish() is required. 1.597 +**/ 1.598 +SimpleTest.waitForExplicitFinish = function () { 1.599 + SimpleTest._stopOnLoad = false; 1.600 +}; 1.601 + 1.602 +/** 1.603 + * Multiply the timeout the parent runner uses for this test by the 1.604 + * given factor. 1.605 + * 1.606 + * For example, in a test that may take a long time to complete, using 1.607 + * "SimpleTest.requestLongerTimeout(5)" will give it 5 times as long to 1.608 + * finish. 1.609 + */ 1.610 +SimpleTest.requestLongerTimeout = function (factor) { 1.611 + if (parentRunner) { 1.612 + parentRunner.requestLongerTimeout(factor); 1.613 + } 1.614 +} 1.615 + 1.616 +/** 1.617 + * Note that the given range of assertions is to be expected. When 1.618 + * this function is not called, 0 assertions are expected. When only 1.619 + * one argument is given, that number of assertions are expected. 1.620 + * 1.621 + * A test where we expect to have assertions (which should largely be a 1.622 + * transitional mechanism to get assertion counts down from our current 1.623 + * situation) can call the SimpleTest.expectAssertions() function, with 1.624 + * either one or two arguments: one argument gives an exact number 1.625 + * expected, and two arguments give a range. For example, a test might do 1.626 + * one of the following: 1.627 + * 1.628 + * // Currently triggers two assertions (bug NNNNNN). 1.629 + * SimpleTest.expectAssertions(2); 1.630 + * 1.631 + * // Currently triggers one assertion on Mac (bug NNNNNN). 1.632 + * if (navigator.platform.indexOf("Mac") == 0) { 1.633 + * SimpleTest.expectAssertions(1); 1.634 + * } 1.635 + * 1.636 + * // Currently triggers two assertions on all platforms (bug NNNNNN), 1.637 + * // but intermittently triggers two additional assertions (bug NNNNNN) 1.638 + * // on Windows. 1.639 + * if (navigator.platform.indexOf("Win") == 0) { 1.640 + * SimpleTest.expectAssertions(2, 4); 1.641 + * } else { 1.642 + * SimpleTest.expectAssertions(2); 1.643 + * } 1.644 + * 1.645 + * // Intermittently triggers up to three assertions (bug NNNNNN). 1.646 + * SimpleTest.expectAssertions(0, 3); 1.647 + */ 1.648 +SimpleTest.expectAssertions = function(min, max) { 1.649 + if (parentRunner) { 1.650 + parentRunner.expectAssertions(min, max); 1.651 + } 1.652 +} 1.653 + 1.654 +SimpleTest.waitForFocus_started = false; 1.655 +SimpleTest.waitForFocus_loaded = false; 1.656 +SimpleTest.waitForFocus_focused = false; 1.657 +SimpleTest._pendingWaitForFocusCount = 0; 1.658 + 1.659 +/** 1.660 + * If the page is not yet loaded, waits for the load event. In addition, if 1.661 + * the page is not yet focused, focuses and waits for the window to be 1.662 + * focused. Calls the callback when completed. If the current page is 1.663 + * 'about:blank', then the page is assumed to not yet be loaded. Pass true for 1.664 + * expectBlankPage to not make this assumption if you expect a blank page to 1.665 + * be present. 1.666 + * 1.667 + * targetWindow should be specified if it is different than 'window'. The actual 1.668 + * focused window may be a descendant of targetWindow. 1.669 + * 1.670 + * @param callback 1.671 + * function called when load and focus are complete 1.672 + * @param targetWindow 1.673 + * optional window to be loaded and focused, defaults to 'window' 1.674 + * @param expectBlankPage 1.675 + * true if targetWindow.location is 'about:blank'. Defaults to false 1.676 + */ 1.677 +SimpleTest.waitForFocus = function (callback, targetWindow, expectBlankPage) { 1.678 + SimpleTest._pendingWaitForFocusCount++; 1.679 + if (!targetWindow) 1.680 + targetWindow = window; 1.681 + 1.682 + SimpleTest.waitForFocus_started = false; 1.683 + expectBlankPage = !!expectBlankPage; 1.684 + 1.685 + var childTargetWindow = {}; 1.686 + SpecialPowers.getFocusedElementForWindow(targetWindow, true, childTargetWindow); 1.687 + childTargetWindow = childTargetWindow.value; 1.688 + 1.689 + function info(msg) { 1.690 + SimpleTest.info(msg); 1.691 + } 1.692 + function getHref(aWindow) { 1.693 + return SpecialPowers.getPrivilegedProps(aWindow, 'location.href'); 1.694 + } 1.695 + 1.696 + function maybeRunTests() { 1.697 + if (SimpleTest.waitForFocus_loaded && 1.698 + SimpleTest.waitForFocus_focused && 1.699 + !SimpleTest.waitForFocus_started) { 1.700 + SimpleTest._pendingWaitForFocusCount--; 1.701 + SimpleTest.waitForFocus_started = true; 1.702 + setTimeout(callback, 0, targetWindow); 1.703 + } 1.704 + } 1.705 + 1.706 + function waitForEvent(event) { 1.707 + try { 1.708 + // Check to make sure that this isn't a load event for a blank or 1.709 + // non-blank page that wasn't desired. 1.710 + if (event.type == "load" && (expectBlankPage != (event.target.location == "about:blank"))) 1.711 + return; 1.712 + 1.713 + SimpleTest["waitForFocus_" + event.type + "ed"] = true; 1.714 + var win = (event.type == "load") ? targetWindow : childTargetWindow; 1.715 + win.removeEventListener(event.type, waitForEvent, true); 1.716 + maybeRunTests(); 1.717 + } catch (e) { 1.718 + SimpleTest.ok(false, "Exception caught in waitForEvent: " + e.message + 1.719 + ", at: " + e.fileName + " (" + e.lineNumber + ")"); 1.720 + } 1.721 + } 1.722 + 1.723 + // If the current document is about:blank and we are not expecting a blank 1.724 + // page (or vice versa), and the document has not yet loaded, wait for the 1.725 + // page to load. A common situation is to wait for a newly opened window 1.726 + // to load its content, and we want to skip over any intermediate blank 1.727 + // pages that load. This issue is described in bug 554873. 1.728 + SimpleTest.waitForFocus_loaded = 1.729 + expectBlankPage ? 1.730 + getHref(targetWindow) == "about:blank" : 1.731 + getHref(targetWindow) != "about:blank" && targetWindow.document.readyState == "complete"; 1.732 + if (!SimpleTest.waitForFocus_loaded) { 1.733 + info("must wait for load"); 1.734 + targetWindow.addEventListener("load", waitForEvent, true); 1.735 + } 1.736 + 1.737 + // Check if the desired window is already focused. 1.738 + var focusedChildWindow = { }; 1.739 + if (SpecialPowers.activeWindow()) { 1.740 + SpecialPowers.getFocusedElementForWindow(SpecialPowers.activeWindow(), true, focusedChildWindow); 1.741 + focusedChildWindow = focusedChildWindow.value; 1.742 + } 1.743 + 1.744 + // If this is a child frame, ensure that the frame is focused. 1.745 + SimpleTest.waitForFocus_focused = (focusedChildWindow == childTargetWindow); 1.746 + if (SimpleTest.waitForFocus_focused) { 1.747 + // If the frame is already focused and loaded, call the callback directly. 1.748 + maybeRunTests(); 1.749 + } 1.750 + else { 1.751 + info("must wait for focus"); 1.752 + childTargetWindow.addEventListener("focus", waitForEvent, true); 1.753 + SpecialPowers.focus(childTargetWindow); 1.754 + } 1.755 +}; 1.756 + 1.757 +SimpleTest.waitForClipboard_polls = 0; 1.758 + 1.759 +/* 1.760 + * Polls the clipboard waiting for the expected value. A known value different than 1.761 + * the expected value is put on the clipboard first (and also polled for) so we 1.762 + * can be sure the value we get isn't just the expected value because it was already 1.763 + * on the clipboard. This only uses the global clipboard and only for text/unicode 1.764 + * values. 1.765 + * 1.766 + * @param aExpectedStringOrValidatorFn 1.767 + * The string value that is expected to be on the clipboard or a 1.768 + * validator function getting cripboard data and returning a bool. 1.769 + * @param aSetupFn 1.770 + * A function responsible for setting the clipboard to the expected value, 1.771 + * called after the known value setting succeeds. 1.772 + * @param aSuccessFn 1.773 + * A function called when the expected value is found on the clipboard. 1.774 + * @param aFailureFn 1.775 + * A function called if the expected value isn't found on the clipboard 1.776 + * within 5s. It can also be called if the known value can't be found. 1.777 + * @param aFlavor [optional] The flavor to look for. Defaults to "text/unicode". 1.778 + */ 1.779 +SimpleTest.__waitForClipboardMonotonicCounter = 0; 1.780 +SimpleTest.__defineGetter__("_waitForClipboardMonotonicCounter", function () { 1.781 + return SimpleTest.__waitForClipboardMonotonicCounter++; 1.782 +}); 1.783 +SimpleTest.waitForClipboard = function(aExpectedStringOrValidatorFn, aSetupFn, 1.784 + aSuccessFn, aFailureFn, aFlavor) { 1.785 + var requestedFlavor = aFlavor || "text/unicode"; 1.786 + 1.787 + // Build a default validator function for common string input. 1.788 + var inputValidatorFn = typeof(aExpectedStringOrValidatorFn) == "string" 1.789 + ? function(aData) { return aData == aExpectedStringOrValidatorFn; } 1.790 + : aExpectedStringOrValidatorFn; 1.791 + 1.792 + // reset for the next use 1.793 + function reset() { 1.794 + SimpleTest.waitForClipboard_polls = 0; 1.795 + } 1.796 + 1.797 + function wait(validatorFn, successFn, failureFn, flavor) { 1.798 + if (++SimpleTest.waitForClipboard_polls > 50) { 1.799 + // Log the failure. 1.800 + SimpleTest.ok(false, "Timed out while polling clipboard for pasted data."); 1.801 + reset(); 1.802 + failureFn(); 1.803 + return; 1.804 + } 1.805 + 1.806 + var data = SpecialPowers.getClipboardData(flavor); 1.807 + 1.808 + if (validatorFn(data)) { 1.809 + // Don't show the success message when waiting for preExpectedVal 1.810 + if (preExpectedVal) 1.811 + preExpectedVal = null; 1.812 + else 1.813 + SimpleTest.ok(true, "Clipboard has the correct value"); 1.814 + reset(); 1.815 + successFn(); 1.816 + } else { 1.817 + setTimeout(function() { return wait(validatorFn, successFn, failureFn, flavor); }, 100); 1.818 + } 1.819 + } 1.820 + 1.821 + // First we wait for a known value different from the expected one. 1.822 + var preExpectedVal = SimpleTest._waitForClipboardMonotonicCounter + 1.823 + "-waitForClipboard-known-value"; 1.824 + SpecialPowers.clipboardCopyString(preExpectedVal); 1.825 + wait(function(aData) { return aData == preExpectedVal; }, 1.826 + function() { 1.827 + // Call the original setup fn 1.828 + aSetupFn(); 1.829 + wait(inputValidatorFn, aSuccessFn, aFailureFn, requestedFlavor); 1.830 + }, aFailureFn, "text/unicode"); 1.831 +} 1.832 + 1.833 +/** 1.834 + * Executes a function shortly after the call, but lets the caller continue 1.835 + * working (or finish). 1.836 + */ 1.837 +SimpleTest.executeSoon = function(aFunc) { 1.838 + if ("SpecialPowers" in window) { 1.839 + return SpecialPowers.executeSoon(aFunc, window); 1.840 + } 1.841 + setTimeout(aFunc, 0); 1.842 + return null; // Avoid warning. 1.843 +}; 1.844 + 1.845 +SimpleTest.registerCleanupFunction = function(aFunc) { 1.846 + SimpleTest._cleanupFunctions.push(aFunc); 1.847 +}; 1.848 + 1.849 +/** 1.850 + * Finishes the tests. This is automatically called, except when 1.851 + * SimpleTest.waitForExplicitFinish() has been invoked. 1.852 +**/ 1.853 +SimpleTest.finish = function() { 1.854 + var Task = SpecialPowers.Cu.import("resource://gre/modules/Task.jsm").Task; 1.855 + 1.856 + if (SimpleTest._alreadyFinished) { 1.857 + SimpleTest.ok(false, "[SimpleTest.finish()] this test already called finish!"); 1.858 + } 1.859 + 1.860 + SimpleTest._alreadyFinished = true; 1.861 + 1.862 + Task.spawn(function*() { 1.863 + // Execute all of our cleanup functions. 1.864 + var func; 1.865 + while ((func = SimpleTest._cleanupFunctions.pop())) { 1.866 + try { 1.867 + yield func(); 1.868 + } 1.869 + catch (ex) { 1.870 + SimpleTest.ok(false, "Cleanup function threw exception: " + ex); 1.871 + } 1.872 + } 1.873 + 1.874 + if (SpecialPowers.DOMWindowUtils.isTestControllingRefreshes) { 1.875 + SimpleTest.ok(false, "test left refresh driver under test control"); 1.876 + SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); 1.877 + } 1.878 + if (SimpleTest._expectingUncaughtException) { 1.879 + SimpleTest.ok(false, "expectUncaughtException was called but no uncaught exception was detected!"); 1.880 + } 1.881 + if (SimpleTest._pendingWaitForFocusCount != 0) { 1.882 + SimpleTest.is(SimpleTest._pendingWaitForFocusCount, 0, 1.883 + "[SimpleTest.finish()] waitForFocus() was called a " 1.884 + + "different number of times from the number of " 1.885 + + "callbacks run. Maybe the test terminated " 1.886 + + "prematurely -- be sure to use " 1.887 + + "SimpleTest.waitForExplicitFinish()."); 1.888 + } 1.889 + if (SimpleTest._tests.length == 0) { 1.890 + SimpleTest.ok(false, "[SimpleTest.finish()] No checks actually run. " 1.891 + + "(You need to call ok(), is(), or similar " 1.892 + + "functions at least once. Make sure you use " 1.893 + + "SimpleTest.waitForExplicitFinish() if you need " 1.894 + + "it.)"); 1.895 + } 1.896 + 1.897 + if (parentRunner) { 1.898 + /* We're running in an iframe, and the parent has a TestRunner */ 1.899 + parentRunner.testFinished(SimpleTest._tests); 1.900 + } else { 1.901 + SpecialPowers.flushAllAppsLaunchable(); 1.902 + SpecialPowers.flushPermissions(function () { 1.903 + SpecialPowers.flushPrefEnv(function() { 1.904 + SimpleTest.showReport(); 1.905 + }); 1.906 + }); 1.907 + } 1.908 + }); 1.909 +}; 1.910 + 1.911 +/** 1.912 + * Monitor console output from now until endMonitorConsole is called. 1.913 + * 1.914 + * Expect to receive all console messages described by the elements of 1.915 + * |msgs|, an array, in the order listed in |msgs|; each element is an 1.916 + * object which may have any number of the following properties: 1.917 + * message, errorMessage, sourceName, sourceLine, category: 1.918 + * string or regexp 1.919 + * lineNumber, columnNumber: number 1.920 + * isScriptError, isWarning, isException, isStrict: boolean 1.921 + * Strings, numbers, and booleans must compare equal to the named 1.922 + * property of the Nth console message. Regexps must match. Any 1.923 + * fields present in the message but not in the pattern object are ignored. 1.924 + * 1.925 + * In addition to the above properties, elements in |msgs| may have a |forbid| 1.926 + * boolean property. When |forbid| is true, a failure is logged each time a 1.927 + * matching message is received. 1.928 + * 1.929 + * If |forbidUnexpectedMsgs| is true, then the messages received in the console 1.930 + * must exactly match the non-forbidden messages in |msgs|; for each received 1.931 + * message not described by the next element in |msgs|, a failure is logged. If 1.932 + * false, then other non-forbidden messages are ignored, but all expected 1.933 + * messages must still be received. 1.934 + * 1.935 + * After endMonitorConsole is called, |continuation| will be called 1.936 + * asynchronously. (Normally, you will want to pass |SimpleTest.finish| here.) 1.937 + * 1.938 + * It is incorrect to use this function in a test which has not called 1.939 + * SimpleTest.waitForExplicitFinish. 1.940 + */ 1.941 +SimpleTest.monitorConsole = function (continuation, msgs, forbidUnexpectedMsgs) { 1.942 + if (SimpleTest._stopOnLoad) { 1.943 + ok(false, "Console monitoring requires use of waitForExplicitFinish."); 1.944 + } 1.945 + 1.946 + function msgMatches(msg, pat) { 1.947 + for (k in pat) { 1.948 + if (!(k in msg)) { 1.949 + return false; 1.950 + } 1.951 + if (pat[k] instanceof RegExp && typeof(msg[k]) === 'string') { 1.952 + if (!pat[k].test(msg[k])) { 1.953 + return false; 1.954 + } 1.955 + } else if (msg[k] !== pat[k]) { 1.956 + return false; 1.957 + } 1.958 + } 1.959 + return true; 1.960 + } 1.961 + 1.962 + var forbiddenMsgs = []; 1.963 + var i = 0; 1.964 + while (i < msgs.length) { 1.965 + var pat = msgs[i]; 1.966 + if ("forbid" in pat) { 1.967 + var forbid = pat.forbid; 1.968 + delete pat.forbid; 1.969 + if (forbid) { 1.970 + forbiddenMsgs.push(pat); 1.971 + msgs.splice(i, 1); 1.972 + continue; 1.973 + } 1.974 + } 1.975 + i++; 1.976 + } 1.977 + 1.978 + var counter = 0; 1.979 + function listener(msg) { 1.980 + if (msg.message === "SENTINEL" && !msg.isScriptError) { 1.981 + is(counter, msgs.length, "monitorConsole | number of messages"); 1.982 + SimpleTest.executeSoon(continuation); 1.983 + return; 1.984 + } 1.985 + for (var pat of forbiddenMsgs) { 1.986 + if (msgMatches(msg, pat)) { 1.987 + ok(false, "monitorConsole | observed forbidden message " + 1.988 + JSON.stringify(msg)); 1.989 + return; 1.990 + } 1.991 + } 1.992 + if (counter >= msgs.length) { 1.993 + var str = "monitorConsole | extra message | " + JSON.stringify(msg); 1.994 + if (forbidUnexpectedMsgs) { 1.995 + ok(false, str); 1.996 + } else { 1.997 + info(str); 1.998 + } 1.999 + return; 1.1000 + } 1.1001 + var matches = msgMatches(msg, msgs[counter]); 1.1002 + if (forbidUnexpectedMsgs) { 1.1003 + ok(matches, "monitorConsole | [" + counter + "] must match " + 1.1004 + JSON.stringify(msg)); 1.1005 + } else { 1.1006 + info("monitorConsole | [" + counter + "] " + 1.1007 + (matches ? "matched " : "did not match ") + JSON.stringify(msg)); 1.1008 + } 1.1009 + if (matches) 1.1010 + counter++; 1.1011 + } 1.1012 + SpecialPowers.registerConsoleListener(listener); 1.1013 +}; 1.1014 + 1.1015 +/** 1.1016 + * Stop monitoring console output. 1.1017 + */ 1.1018 +SimpleTest.endMonitorConsole = function () { 1.1019 + SpecialPowers.postConsoleSentinel(); 1.1020 +}; 1.1021 + 1.1022 +/** 1.1023 + * Run |testfn| synchronously, and monitor its console output. 1.1024 + * 1.1025 + * |msgs| is handled as described above for monitorConsole. 1.1026 + * 1.1027 + * After |testfn| returns, console monitoring will stop, and 1.1028 + * |continuation| will be called asynchronously. 1.1029 + */ 1.1030 +SimpleTest.expectConsoleMessages = function (testfn, msgs, continuation) { 1.1031 + SimpleTest.monitorConsole(continuation, msgs); 1.1032 + testfn(); 1.1033 + SimpleTest.executeSoon(SimpleTest.endMonitorConsole); 1.1034 +}; 1.1035 + 1.1036 +/** 1.1037 + * Wrapper around |expectConsoleMessages| for the case where the test has 1.1038 + * only one |testfn| to run. 1.1039 + */ 1.1040 +SimpleTest.runTestExpectingConsoleMessages = function(testfn, msgs) { 1.1041 + SimpleTest.waitForExplicitFinish(); 1.1042 + SimpleTest.expectConsoleMessages(testfn, msgs, SimpleTest.finish); 1.1043 +}; 1.1044 + 1.1045 +/** 1.1046 + * Indicates to the test framework that the current test expects one or 1.1047 + * more crashes (from plugins or IPC documents), and that the minidumps from 1.1048 + * those crashes should be removed. 1.1049 + */ 1.1050 +SimpleTest.expectChildProcessCrash = function () { 1.1051 + if (parentRunner) { 1.1052 + parentRunner.expectChildProcessCrash(); 1.1053 + } 1.1054 +}; 1.1055 + 1.1056 +/** 1.1057 + * Indicates to the test framework that the next uncaught exception during 1.1058 + * the test is expected, and should not cause a test failure. 1.1059 + */ 1.1060 +SimpleTest.expectUncaughtException = function (aExpecting) { 1.1061 + SimpleTest._expectingUncaughtException = aExpecting === void 0 || !!aExpecting; 1.1062 +}; 1.1063 + 1.1064 +/** 1.1065 + * Returns whether the test has indicated that it expects an uncaught exception 1.1066 + * to occur. 1.1067 + */ 1.1068 +SimpleTest.isExpectingUncaughtException = function () { 1.1069 + return SimpleTest._expectingUncaughtException; 1.1070 +}; 1.1071 + 1.1072 +/** 1.1073 + * Indicates to the test framework that all of the uncaught exceptions 1.1074 + * during the test are known problems that should be fixed in the future, 1.1075 + * but which should not cause the test to fail currently. 1.1076 + */ 1.1077 +SimpleTest.ignoreAllUncaughtExceptions = function (aIgnoring) { 1.1078 + SimpleTest._ignoringAllUncaughtExceptions = aIgnoring === void 0 || !!aIgnoring; 1.1079 +}; 1.1080 + 1.1081 +/** 1.1082 + * Returns whether the test has indicated that all uncaught exceptions should be 1.1083 + * ignored. 1.1084 + */ 1.1085 +SimpleTest.isIgnoringAllUncaughtExceptions = function () { 1.1086 + return SimpleTest._ignoringAllUncaughtExceptions; 1.1087 +}; 1.1088 + 1.1089 +/** 1.1090 + * Resets any state this SimpleTest object has. This is important for 1.1091 + * browser chrome mochitests, which reuse the same SimpleTest object 1.1092 + * across a run. 1.1093 + */ 1.1094 +SimpleTest.reset = function () { 1.1095 + SimpleTest._ignoringAllUncaughtExceptions = false; 1.1096 + SimpleTest._expectingUncaughtException = false; 1.1097 + SimpleTest._bufferedMessages = []; 1.1098 +}; 1.1099 + 1.1100 +if (isPrimaryTestWindow) { 1.1101 + addLoadEvent(function() { 1.1102 + if (SimpleTest._stopOnLoad) { 1.1103 + SimpleTest.finish(); 1.1104 + } 1.1105 + }); 1.1106 +} 1.1107 + 1.1108 +// --------------- Test.Builder/Test.More isDeeply() ----------------- 1.1109 + 1.1110 + 1.1111 +SimpleTest.DNE = {dne: 'Does not exist'}; 1.1112 +SimpleTest.LF = "\r\n"; 1.1113 +SimpleTest._isRef = function (object) { 1.1114 + var type = typeof(object); 1.1115 + return type == 'object' || type == 'function'; 1.1116 +}; 1.1117 + 1.1118 + 1.1119 +SimpleTest._deepCheck = function (e1, e2, stack, seen) { 1.1120 + var ok = false; 1.1121 + // Either they're both references or both not. 1.1122 + var sameRef = !(!SimpleTest._isRef(e1) ^ !SimpleTest._isRef(e2)); 1.1123 + if (e1 == null && e2 == null) { 1.1124 + ok = true; 1.1125 + } else if (e1 != null ^ e2 != null) { 1.1126 + ok = false; 1.1127 + } else if (e1 == SimpleTest.DNE ^ e2 == SimpleTest.DNE) { 1.1128 + ok = false; 1.1129 + } else if (sameRef && e1 == e2) { 1.1130 + // Handles primitives and any variables that reference the same 1.1131 + // object, including functions. 1.1132 + ok = true; 1.1133 + } else if (SimpleTest.isa(e1, 'Array') && SimpleTest.isa(e2, 'Array')) { 1.1134 + ok = SimpleTest._eqArray(e1, e2, stack, seen); 1.1135 + } else if (typeof e1 == "object" && typeof e2 == "object") { 1.1136 + ok = SimpleTest._eqAssoc(e1, e2, stack, seen); 1.1137 + } else if (typeof e1 == "number" && typeof e2 == "number" 1.1138 + && isNaN(e1) && isNaN(e2)) { 1.1139 + ok = true; 1.1140 + } else { 1.1141 + // If we get here, they're not the same (function references must 1.1142 + // always simply reference the same function). 1.1143 + stack.push({ vals: [e1, e2] }); 1.1144 + ok = false; 1.1145 + } 1.1146 + return ok; 1.1147 +}; 1.1148 + 1.1149 +SimpleTest._eqArray = function (a1, a2, stack, seen) { 1.1150 + // Return if they're the same object. 1.1151 + if (a1 == a2) return true; 1.1152 + 1.1153 + // JavaScript objects have no unique identifiers, so we have to store 1.1154 + // references to them all in an array, and then compare the references 1.1155 + // directly. It's slow, but probably won't be much of an issue in 1.1156 + // practice. Start by making a local copy of the array to as to avoid 1.1157 + // confusing a reference seen more than once (such as [a, a]) for a 1.1158 + // circular reference. 1.1159 + for (var j = 0; j < seen.length; j++) { 1.1160 + if (seen[j][0] == a1) { 1.1161 + return seen[j][1] == a2; 1.1162 + } 1.1163 + } 1.1164 + 1.1165 + // If we get here, we haven't seen a1 before, so store it with reference 1.1166 + // to a2. 1.1167 + seen.push([ a1, a2 ]); 1.1168 + 1.1169 + var ok = true; 1.1170 + // Only examines enumerable attributes. Only works for numeric arrays! 1.1171 + // Associative arrays return 0. So call _eqAssoc() for them, instead. 1.1172 + var max = a1.length > a2.length ? a1.length : a2.length; 1.1173 + if (max == 0) return SimpleTest._eqAssoc(a1, a2, stack, seen); 1.1174 + for (var i = 0; i < max; i++) { 1.1175 + var e1 = i > a1.length - 1 ? SimpleTest.DNE : a1[i]; 1.1176 + var e2 = i > a2.length - 1 ? SimpleTest.DNE : a2[i]; 1.1177 + stack.push({ type: 'Array', idx: i, vals: [e1, e2] }); 1.1178 + ok = SimpleTest._deepCheck(e1, e2, stack, seen); 1.1179 + if (ok) { 1.1180 + stack.pop(); 1.1181 + } else { 1.1182 + break; 1.1183 + } 1.1184 + } 1.1185 + return ok; 1.1186 +}; 1.1187 + 1.1188 +SimpleTest._eqAssoc = function (o1, o2, stack, seen) { 1.1189 + // Return if they're the same object. 1.1190 + if (o1 == o2) return true; 1.1191 + 1.1192 + // JavaScript objects have no unique identifiers, so we have to store 1.1193 + // references to them all in an array, and then compare the references 1.1194 + // directly. It's slow, but probably won't be much of an issue in 1.1195 + // practice. Start by making a local copy of the array to as to avoid 1.1196 + // confusing a reference seen more than once (such as [a, a]) for a 1.1197 + // circular reference. 1.1198 + seen = seen.slice(0); 1.1199 + for (var j = 0; j < seen.length; j++) { 1.1200 + if (seen[j][0] == o1) { 1.1201 + return seen[j][1] == o2; 1.1202 + } 1.1203 + } 1.1204 + 1.1205 + // If we get here, we haven't seen o1 before, so store it with reference 1.1206 + // to o2. 1.1207 + seen.push([ o1, o2 ]); 1.1208 + 1.1209 + // They should be of the same class. 1.1210 + 1.1211 + var ok = true; 1.1212 + // Only examines enumerable attributes. 1.1213 + var o1Size = 0; for (var i in o1) o1Size++; 1.1214 + var o2Size = 0; for (var i in o2) o2Size++; 1.1215 + var bigger = o1Size > o2Size ? o1 : o2; 1.1216 + for (var i in bigger) { 1.1217 + var e1 = o1[i] == undefined ? SimpleTest.DNE : o1[i]; 1.1218 + var e2 = o2[i] == undefined ? SimpleTest.DNE : o2[i]; 1.1219 + stack.push({ type: 'Object', idx: i, vals: [e1, e2] }); 1.1220 + ok = SimpleTest._deepCheck(e1, e2, stack, seen) 1.1221 + if (ok) { 1.1222 + stack.pop(); 1.1223 + } else { 1.1224 + break; 1.1225 + } 1.1226 + } 1.1227 + return ok; 1.1228 +}; 1.1229 + 1.1230 +SimpleTest._formatStack = function (stack) { 1.1231 + var variable = '$Foo'; 1.1232 + for (var i = 0; i < stack.length; i++) { 1.1233 + var entry = stack[i]; 1.1234 + var type = entry['type']; 1.1235 + var idx = entry['idx']; 1.1236 + if (idx != null) { 1.1237 + if (/^\d+$/.test(idx)) { 1.1238 + // Numeric array index. 1.1239 + variable += '[' + idx + ']'; 1.1240 + } else { 1.1241 + // Associative array index. 1.1242 + idx = idx.replace("'", "\\'"); 1.1243 + variable += "['" + idx + "']"; 1.1244 + } 1.1245 + } 1.1246 + } 1.1247 + 1.1248 + var vals = stack[stack.length-1]['vals'].slice(0, 2); 1.1249 + var vars = [ 1.1250 + variable.replace('$Foo', 'got'), 1.1251 + variable.replace('$Foo', 'expected') 1.1252 + ]; 1.1253 + 1.1254 + var out = "Structures begin differing at:" + SimpleTest.LF; 1.1255 + for (var i = 0; i < vals.length; i++) { 1.1256 + var val = vals[i]; 1.1257 + if (val == null) { 1.1258 + val = 'undefined'; 1.1259 + } else { 1.1260 + val == SimpleTest.DNE ? "Does not exist" : "'" + val + "'"; 1.1261 + } 1.1262 + } 1.1263 + 1.1264 + out += vars[0] + ' = ' + vals[0] + SimpleTest.LF; 1.1265 + out += vars[1] + ' = ' + vals[1] + SimpleTest.LF; 1.1266 + 1.1267 + return ' ' + out; 1.1268 +}; 1.1269 + 1.1270 + 1.1271 +SimpleTest.isDeeply = function (it, as, name) { 1.1272 + var ok; 1.1273 + // ^ is the XOR operator. 1.1274 + if (SimpleTest._isRef(it) ^ SimpleTest._isRef(as)) { 1.1275 + // One's a reference, one isn't. 1.1276 + ok = false; 1.1277 + } else if (!SimpleTest._isRef(it) && !SimpleTest._isRef(as)) { 1.1278 + // Neither is an object. 1.1279 + ok = SimpleTest.is(it, as, name); 1.1280 + } else { 1.1281 + // We have two objects. Do a deep comparison. 1.1282 + var stack = [], seen = []; 1.1283 + if ( SimpleTest._deepCheck(it, as, stack, seen)) { 1.1284 + ok = SimpleTest.ok(true, name); 1.1285 + } else { 1.1286 + ok = SimpleTest.ok(false, name, SimpleTest._formatStack(stack)); 1.1287 + } 1.1288 + } 1.1289 + return ok; 1.1290 +}; 1.1291 + 1.1292 +SimpleTest.typeOf = function (object) { 1.1293 + var c = Object.prototype.toString.apply(object); 1.1294 + var name = c.substring(8, c.length - 1); 1.1295 + if (name != 'Object') return name; 1.1296 + // It may be a non-core class. Try to extract the class name from 1.1297 + // the constructor function. This may not work in all implementations. 1.1298 + if (/function ([^(\s]+)/.test(Function.toString.call(object.constructor))) { 1.1299 + return RegExp.$1; 1.1300 + } 1.1301 + // No idea. :-( 1.1302 + return name; 1.1303 +}; 1.1304 + 1.1305 +SimpleTest.isa = function (object, clas) { 1.1306 + return SimpleTest.typeOf(object) == clas; 1.1307 +}; 1.1308 + 1.1309 +// Global symbols: 1.1310 +var ok = SimpleTest.ok; 1.1311 +var is = SimpleTest.is; 1.1312 +var isfuzzy = SimpleTest.isfuzzy; 1.1313 +var isnot = SimpleTest.isnot; 1.1314 +var ise = SimpleTest.ise; 1.1315 +var todo = SimpleTest.todo; 1.1316 +var todo_is = SimpleTest.todo_is; 1.1317 +var todo_isnot = SimpleTest.todo_isnot; 1.1318 +var isDeeply = SimpleTest.isDeeply; 1.1319 +var info = SimpleTest.info; 1.1320 + 1.1321 +var gOldOnError = window.onerror; 1.1322 +window.onerror = function simpletestOnerror(errorMsg, url, lineNumber) { 1.1323 + // Log the message. 1.1324 + // XXX Chrome mochitests sometimes trigger this window.onerror handler, 1.1325 + // but there are a number of uncaught JS exceptions from those tests. 1.1326 + // For now, for tests that self identify as having unintentional uncaught 1.1327 + // exceptions, just dump it so that the error is visible but doesn't cause 1.1328 + // a test failure. See bug 652494. 1.1329 + var isExpected = !!SimpleTest._expectingUncaughtException; 1.1330 + var message = (isExpected ? "expected " : "") + "uncaught exception"; 1.1331 + var error = errorMsg + " at " + url + ":" + lineNumber; 1.1332 + if (!SimpleTest._ignoringAllUncaughtExceptions) { 1.1333 + SimpleTest.ok(isExpected, message, error); 1.1334 + SimpleTest._expectingUncaughtException = false; 1.1335 + } else { 1.1336 + SimpleTest.todo(false, message + ": " + error); 1.1337 + } 1.1338 + // There is no Components.stack.caller to log. (See bug 511888.) 1.1339 + 1.1340 + // Call previous handler. 1.1341 + if (gOldOnError) { 1.1342 + try { 1.1343 + // Ignore return value: always run default handler. 1.1344 + gOldOnError(errorMsg, url, lineNumber); 1.1345 + } catch (e) { 1.1346 + // Log the error. 1.1347 + SimpleTest.info("Exception thrown by gOldOnError(): " + e); 1.1348 + // Log its stack. 1.1349 + if (e.stack) { 1.1350 + SimpleTest.info("JavaScript error stack:\n" + e.stack); 1.1351 + } 1.1352 + } 1.1353 + } 1.1354 + 1.1355 + if (!SimpleTest._stopOnLoad && !isExpected) { 1.1356 + // Need to finish() manually here, yet let the test actually end first. 1.1357 + SimpleTest.executeSoon(SimpleTest.finish); 1.1358 + } 1.1359 +};