testing/mochitest/tests/SimpleTest/SimpleTest.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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 };

mercurial