1/*global self*/ 2/*jshint latedef: nofunc*/ 3 4/* Documentation: https://web-platform-tests.org/writing-tests/testharness-api.html 5 * (../docs/_writing-tests/testharness-api.md) */ 6 7(function (global_scope) 8{ 9 // default timeout is 10 seconds, test can override if needed 10 var settings = { 11 output:true, 12 harness_timeout:{ 13 "normal":10000, 14 "long":60000 15 }, 16 test_timeout:null, 17 message_events: ["start", "test_state", "result", "completion"], 18 debug: false, 19 }; 20 21 var xhtml_ns = "http://www.w3.org/1999/xhtml"; 22 23 /* 24 * TestEnvironment is an abstraction for the environment in which the test 25 * harness is used. Each implementation of a test environment has to provide 26 * the following interface: 27 * 28 * interface TestEnvironment { 29 * // Invoked after the global 'tests' object has been created and it's 30 * // safe to call add_*_callback() to register event handlers. 31 * void on_tests_ready(); 32 * 33 * // Invoked after setup() has been called to notify the test environment 34 * // of changes to the test harness properties. 35 * void on_new_harness_properties(object properties); 36 * 37 * // Should return a new unique default test name. 38 * DOMString next_default_test_name(); 39 * 40 * // Should return the test harness timeout duration in milliseconds. 41 * float test_timeout(); 42 * }; 43 */ 44 45 /* 46 * A test environment with a DOM. The global object is 'window'. By default 47 * test results are displayed in a table. Any parent windows receive 48 * callbacks or messages via postMessage() when test events occur. See 49 * apisample11.html and apisample12.html. 50 */ 51 function WindowTestEnvironment() { 52 this.name_counter = 0; 53 this.window_cache = null; 54 this.output_handler = null; 55 this.all_loaded = false; 56 var this_obj = this; 57 this.message_events = []; 58 this.dispatched_messages = []; 59 60 this.message_functions = { 61 start: [add_start_callback, remove_start_callback, 62 function (properties) { 63 this_obj._dispatch("start_callback", [properties], 64 {type: "start", properties: properties}); 65 }], 66 67 test_state: [add_test_state_callback, remove_test_state_callback, 68 function(test) { 69 this_obj._dispatch("test_state_callback", [test], 70 {type: "test_state", 71 test: test.structured_clone()}); 72 }], 73 result: [add_result_callback, remove_result_callback, 74 function (test) { 75 this_obj.output_handler.show_status(); 76 this_obj._dispatch("result_callback", [test], 77 {type: "result", 78 test: test.structured_clone()}); 79 }], 80 completion: [add_completion_callback, remove_completion_callback, 81 function (tests, harness_status, asserts) { 82 var cloned_tests = map(tests, function(test) { 83 return test.structured_clone(); 84 }); 85 this_obj._dispatch("completion_callback", [tests, harness_status], 86 {type: "complete", 87 tests: cloned_tests, 88 status: harness_status.structured_clone(), 89 asserts: asserts.map(assert => assert.structured_clone())}); 90 }] 91 } 92 93 on_event(window, 'load', function() { 94 this_obj.all_loaded = true; 95 }); 96 97 on_event(window, 'message', function(event) { 98 if (event.data && event.data.type === "getmessages" && event.source) { 99 // A window can post "getmessages" to receive a duplicate of every 100 // message posted by this environment so far. This allows subscribers 101 // from fetch_tests_from_window to 'catch up' to the current state of 102 // this environment. 103 for (var i = 0; i < this_obj.dispatched_messages.length; ++i) 104 { 105 event.source.postMessage(this_obj.dispatched_messages[i], "*"); 106 } 107 } 108 }); 109 } 110 111 WindowTestEnvironment.prototype._dispatch = function(selector, callback_args, message_arg) { 112 this.dispatched_messages.push(message_arg); 113 this._forEach_windows( 114 function(w, same_origin) { 115 if (same_origin) { 116 try { 117 var has_selector = selector in w; 118 } catch(e) { 119 // If document.domain was set at some point same_origin can be 120 // wrong and the above will fail. 121 has_selector = false; 122 } 123 if (has_selector) { 124 try { 125 w[selector].apply(undefined, callback_args); 126 } catch (e) {} 127 } 128 } 129 if (w !== self) { 130 w.postMessage(message_arg, "*"); 131 } 132 }); 133 }; 134 135 WindowTestEnvironment.prototype._forEach_windows = function(callback) { 136 // Iterate over the windows [self ... top, opener]. The callback is passed 137 // two objects, the first one is the window object itself, the second one 138 // is a boolean indicating whether or not it's on the same origin as the 139 // current window. 140 var cache = this.window_cache; 141 if (!cache) { 142 cache = [[self, true]]; 143 var w = self; 144 var i = 0; 145 var so; 146 while (w != w.parent) { 147 w = w.parent; 148 so = is_same_origin(w); 149 cache.push([w, so]); 150 i++; 151 } 152 w = window.opener; 153 if (w) { 154 cache.push([w, is_same_origin(w)]); 155 } 156 this.window_cache = cache; 157 } 158 159 forEach(cache, 160 function(a) { 161 callback.apply(null, a); 162 }); 163 }; 164 165 WindowTestEnvironment.prototype.on_tests_ready = function() { 166 var output = new Output(); 167 this.output_handler = output; 168 169 var this_obj = this; 170 171 add_start_callback(function (properties) { 172 this_obj.output_handler.init(properties); 173 }); 174 175 add_test_state_callback(function(test) { 176 this_obj.output_handler.show_status(); 177 }); 178 179 add_result_callback(function (test) { 180 this_obj.output_handler.show_status(); 181 }); 182 183 add_completion_callback(function (tests, harness_status, asserts_run) { 184 this_obj.output_handler.show_results(tests, harness_status, asserts_run); 185 }); 186 this.setup_messages(settings.message_events); 187 }; 188 189 WindowTestEnvironment.prototype.setup_messages = function(new_events) { 190 var this_obj = this; 191 forEach(settings.message_events, function(x) { 192 var current_dispatch = this_obj.message_events.indexOf(x) !== -1; 193 var new_dispatch = new_events.indexOf(x) !== -1; 194 if (!current_dispatch && new_dispatch) { 195 this_obj.message_functions[x][0](this_obj.message_functions[x][2]); 196 } else if (current_dispatch && !new_dispatch) { 197 this_obj.message_functions[x][1](this_obj.message_functions[x][2]); 198 } 199 }); 200 this.message_events = new_events; 201 } 202 203 WindowTestEnvironment.prototype.next_default_test_name = function() { 204 var suffix = this.name_counter > 0 ? " " + this.name_counter : ""; 205 this.name_counter++; 206 return get_title() + suffix; 207 }; 208 209 WindowTestEnvironment.prototype.on_new_harness_properties = function(properties) { 210 this.output_handler.setup(properties); 211 if (properties.hasOwnProperty("message_events")) { 212 this.setup_messages(properties.message_events); 213 } 214 }; 215 216 WindowTestEnvironment.prototype.add_on_loaded_callback = function(callback) { 217 on_event(window, 'load', callback); 218 }; 219 220 WindowTestEnvironment.prototype.test_timeout = function() { 221 var metas = document.getElementsByTagName("meta"); 222 for (var i = 0; i < metas.length; i++) { 223 if (metas[i].name == "timeout") { 224 if (metas[i].content == "long") { 225 return settings.harness_timeout.long; 226 } 227 break; 228 } 229 } 230 return settings.harness_timeout.normal; 231 }; 232 233 /* 234 * Base TestEnvironment implementation for a generic web worker. 235 * 236 * Workers accumulate test results. One or more clients can connect and 237 * retrieve results from a worker at any time. 238 * 239 * WorkerTestEnvironment supports communicating with a client via a 240 * MessagePort. The mechanism for determining the appropriate MessagePort 241 * for communicating with a client depends on the type of worker and is 242 * implemented by the various specializations of WorkerTestEnvironment 243 * below. 244 * 245 * A client document using testharness can use fetch_tests_from_worker() to 246 * retrieve results from a worker. See apisample16.html. 247 */ 248 function WorkerTestEnvironment() { 249 this.name_counter = 0; 250 this.all_loaded = true; 251 this.message_list = []; 252 this.message_ports = []; 253 } 254 255 WorkerTestEnvironment.prototype._dispatch = function(message) { 256 this.message_list.push(message); 257 for (var i = 0; i < this.message_ports.length; ++i) 258 { 259 this.message_ports[i].postMessage(message); 260 } 261 }; 262 263 // The only requirement is that port has a postMessage() method. It doesn't 264 // have to be an instance of a MessagePort, and often isn't. 265 WorkerTestEnvironment.prototype._add_message_port = function(port) { 266 this.message_ports.push(port); 267 for (var i = 0; i < this.message_list.length; ++i) 268 { 269 port.postMessage(this.message_list[i]); 270 } 271 }; 272 273 WorkerTestEnvironment.prototype.next_default_test_name = function() { 274 var suffix = this.name_counter > 0 ? " " + this.name_counter : ""; 275 this.name_counter++; 276 return get_title() + suffix; 277 }; 278 279 WorkerTestEnvironment.prototype.on_new_harness_properties = function() {}; 280 281 WorkerTestEnvironment.prototype.on_tests_ready = function() { 282 var this_obj = this; 283 add_start_callback( 284 function(properties) { 285 this_obj._dispatch({ 286 type: "start", 287 properties: properties, 288 }); 289 }); 290 add_test_state_callback( 291 function(test) { 292 this_obj._dispatch({ 293 type: "test_state", 294 test: test.structured_clone() 295 }); 296 }); 297 add_result_callback( 298 function(test) { 299 this_obj._dispatch({ 300 type: "result", 301 test: test.structured_clone() 302 }); 303 }); 304 add_completion_callback( 305 function(tests, harness_status, asserts) { 306 this_obj._dispatch({ 307 type: "complete", 308 tests: map(tests, 309 function(test) { 310 return test.structured_clone(); 311 }), 312 status: harness_status.structured_clone(), 313 asserts: asserts.map(assert => assert.structured_clone()), 314 }); 315 }); 316 }; 317 318 WorkerTestEnvironment.prototype.add_on_loaded_callback = function() {}; 319 320 WorkerTestEnvironment.prototype.test_timeout = function() { 321 // Tests running in a worker don't have a default timeout. I.e. all 322 // worker tests behave as if settings.explicit_timeout is true. 323 return null; 324 }; 325 326 /* 327 * Dedicated web workers. 328 * https://html.spec.whatwg.org/multipage/workers.html#dedicatedworkerglobalscope 329 * 330 * This class is used as the test_environment when testharness is running 331 * inside a dedicated worker. 332 */ 333 function DedicatedWorkerTestEnvironment() { 334 WorkerTestEnvironment.call(this); 335 // self is an instance of DedicatedWorkerGlobalScope which exposes 336 // a postMessage() method for communicating via the message channel 337 // established when the worker is created. 338 this._add_message_port(self); 339 } 340 DedicatedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype); 341 342 DedicatedWorkerTestEnvironment.prototype.on_tests_ready = function() { 343 WorkerTestEnvironment.prototype.on_tests_ready.call(this); 344 // In the absence of an onload notification, we a require dedicated 345 // workers to explicitly signal when the tests are done. 346 tests.wait_for_finish = true; 347 }; 348 349 /* 350 * Shared web workers. 351 * https://html.spec.whatwg.org/multipage/workers.html#sharedworkerglobalscope 352 * 353 * This class is used as the test_environment when testharness is running 354 * inside a shared web worker. 355 */ 356 function SharedWorkerTestEnvironment() { 357 WorkerTestEnvironment.call(this); 358 var this_obj = this; 359 // Shared workers receive message ports via the 'onconnect' event for 360 // each connection. 361 self.addEventListener("connect", 362 function(message_event) { 363 this_obj._add_message_port(message_event.source); 364 }, false); 365 } 366 SharedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype); 367 368 SharedWorkerTestEnvironment.prototype.on_tests_ready = function() { 369 WorkerTestEnvironment.prototype.on_tests_ready.call(this); 370 // In the absence of an onload notification, we a require shared 371 // workers to explicitly signal when the tests are done. 372 tests.wait_for_finish = true; 373 }; 374 375 /* 376 * Service workers. 377 * http://www.w3.org/TR/service-workers/ 378 * 379 * This class is used as the test_environment when testharness is running 380 * inside a service worker. 381 */ 382 function ServiceWorkerTestEnvironment() { 383 WorkerTestEnvironment.call(this); 384 this.all_loaded = false; 385 this.on_loaded_callback = null; 386 var this_obj = this; 387 self.addEventListener("message", 388 function(event) { 389 if (event.data && event.data.type && event.data.type === "connect") { 390 this_obj._add_message_port(event.source); 391 } 392 }, false); 393 394 // The oninstall event is received after the service worker script and 395 // all imported scripts have been fetched and executed. It's the 396 // equivalent of an onload event for a document. All tests should have 397 // been added by the time this event is received, thus it's not 398 // necessary to wait until the onactivate event. However, tests for 399 // installed service workers need another event which is equivalent to 400 // the onload event because oninstall is fired only on installation. The 401 // onmessage event is used for that purpose since tests using 402 // testharness.js should ask the result to its service worker by 403 // PostMessage. If the onmessage event is triggered on the service 404 // worker's context, that means the worker's script has been evaluated. 405 on_event(self, "install", on_all_loaded); 406 on_event(self, "message", on_all_loaded); 407 function on_all_loaded() { 408 if (this_obj.all_loaded) 409 return; 410 this_obj.all_loaded = true; 411 if (this_obj.on_loaded_callback) { 412 this_obj.on_loaded_callback(); 413 } 414 } 415 } 416 417 ServiceWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype); 418 419 ServiceWorkerTestEnvironment.prototype.add_on_loaded_callback = function(callback) { 420 if (this.all_loaded) { 421 callback(); 422 } else { 423 this.on_loaded_callback = callback; 424 } 425 }; 426 427 /* 428 * Shadow realms. 429 * https://github.com/tc39/proposal-shadowrealm 430 * 431 * This class is used as the test_environment when testharness is running 432 * inside a shadow realm. 433 */ 434 function ShadowRealmTestEnvironment() { 435 WorkerTestEnvironment.call(this); 436 this.all_loaded = false; 437 this.on_loaded_callback = null; 438 } 439 440 ShadowRealmTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype); 441 442 /** 443 * Signal to the test environment that the tests are ready and the on-loaded 444 * callback should be run. 445 * 446 * Shadow realms are not *really* a DOM context: they have no `onload` or similar 447 * event for us to use to set up the test environment; so, instead, this method 448 * is manually triggered from the incubating realm 449 * 450 * @param {Function} message_destination - a function that receives JSON-serializable 451 * data to send to the incubating realm, in the same format as used by RemoteContext 452 */ 453 ShadowRealmTestEnvironment.prototype.begin = function(message_destination) { 454 if (this.all_loaded) { 455 throw new Error("Tried to start a shadow realm test environment after it has already started"); 456 } 457 var fakeMessagePort = {}; 458 fakeMessagePort.postMessage = message_destination; 459 this._add_message_port(fakeMessagePort); 460 this.all_loaded = true; 461 if (this.on_loaded_callback) { 462 this.on_loaded_callback(); 463 } 464 }; 465 466 ShadowRealmTestEnvironment.prototype.add_on_loaded_callback = function(callback) { 467 if (this.all_loaded) { 468 callback(); 469 } else { 470 this.on_loaded_callback = callback; 471 } 472 }; 473 474 /* 475 * JavaScript shells. 476 * 477 * This class is used as the test_environment when testharness is running 478 * inside a JavaScript shell. 479 */ 480 function ShellTestEnvironment() { 481 this.name_counter = 0; 482 this.all_loaded = false; 483 this.on_loaded_callback = null; 484 Promise.resolve().then(function() { 485 this.all_loaded = true 486 if (this.on_loaded_callback) { 487 this.on_loaded_callback(); 488 } 489 }.bind(this)); 490 this.message_list = []; 491 this.message_ports = []; 492 } 493 494 ShellTestEnvironment.prototype.next_default_test_name = function() { 495 var suffix = this.name_counter > 0 ? " " + this.name_counter : ""; 496 this.name_counter++; 497 return "Untitled" + suffix; 498 }; 499 500 ShellTestEnvironment.prototype.on_new_harness_properties = function() {}; 501 502 ShellTestEnvironment.prototype.on_tests_ready = function() {}; 503 504 ShellTestEnvironment.prototype.add_on_loaded_callback = function(callback) { 505 if (this.all_loaded) { 506 callback(); 507 } else { 508 this.on_loaded_callback = callback; 509 } 510 }; 511 512 ShellTestEnvironment.prototype.test_timeout = function() { 513 // Tests running in a shell don't have a default timeout, so behave as 514 // if settings.explicit_timeout is true. 515 return null; 516 }; 517 518 function create_test_environment() { 519 if ('document' in global_scope) { 520 return new WindowTestEnvironment(); 521 } 522 if ('DedicatedWorkerGlobalScope' in global_scope && 523 global_scope instanceof DedicatedWorkerGlobalScope) { 524 return new DedicatedWorkerTestEnvironment(); 525 } 526 if ('SharedWorkerGlobalScope' in global_scope && 527 global_scope instanceof SharedWorkerGlobalScope) { 528 return new SharedWorkerTestEnvironment(); 529 } 530 if ('ServiceWorkerGlobalScope' in global_scope && 531 global_scope instanceof ServiceWorkerGlobalScope) { 532 return new ServiceWorkerTestEnvironment(); 533 } 534 if ('WorkerGlobalScope' in global_scope && 535 global_scope instanceof WorkerGlobalScope) { 536 return new DedicatedWorkerTestEnvironment(); 537 } 538 /* Shadow realm global objects are _ordinary_ objects (i.e. their prototype is 539 * Object) so we don't have a nice `instanceof` test to use; instead, we 540 * check if the there is a GLOBAL.isShadowRealm() property 541 * on the global object. that was set by the test harness when it 542 * created the ShadowRealm. 543 */ 544 if (global_scope.GLOBAL && global_scope.GLOBAL.isShadowRealm()) { 545 return new ShadowRealmTestEnvironment(); 546 } 547 548 return new ShellTestEnvironment(); 549 } 550 551 var test_environment = create_test_environment(); 552 553 function is_shared_worker(worker) { 554 return 'SharedWorker' in global_scope && worker instanceof SharedWorker; 555 } 556 557 function is_service_worker(worker) { 558 // The worker object may be from another execution context, 559 // so do not use instanceof here. 560 return 'ServiceWorker' in global_scope && 561 Object.prototype.toString.call(worker) == '[object ServiceWorker]'; 562 } 563 564 var seen_func_name = Object.create(null); 565 566 function get_test_name(func, name) 567 { 568 if (name) { 569 return name; 570 } 571 572 if (func) { 573 var func_code = func.toString(); 574 575 // Try and match with brackets, but fallback to matching without 576 var arrow = func_code.match(/^\(\)\s*=>\s*(?:{(.*)}\s*|(.*))$/); 577 578 // Check for JS line separators 579 if (arrow !== null && !/[\u000A\u000D\u2028\u2029]/.test(func_code)) { 580 var trimmed = (arrow[1] !== undefined ? arrow[1] : arrow[2]).trim(); 581 // drop trailing ; if there's no earlier ones 582 trimmed = trimmed.replace(/^([^;]*)(;\s*)+$/, "$1"); 583 584 if (trimmed) { 585 let name = trimmed; 586 if (seen_func_name[trimmed]) { 587 // This subtest name already exists, so add a suffix. 588 name += " " + seen_func_name[trimmed]; 589 } else { 590 seen_func_name[trimmed] = 0; 591 } 592 seen_func_name[trimmed] += 1; 593 return name; 594 } 595 } 596 } 597 598 return test_environment.next_default_test_name(); 599 } 600 601 /** 602 * @callback TestFunction 603 * @param {Test} test - The test currnetly being run. 604 * @param {Any[]} args - Additional args to pass to function. 605 * 606 */ 607 608 /** 609 * Create a synchronous test 610 * 611 * @param {TestFunction} func - Test function. This is executed 612 * immediately. If it returns without error, the test status is 613 * set to ``PASS``. If it throws an :js:class:`AssertionError`, or 614 * any other exception, the test status is set to ``FAIL`` 615 * (typically from an `assert` function). 616 * @param {String} name - Test name. This must be unique in a 617 * given file and must be invariant between runs. 618 */ 619 function test(func, name, properties) 620 { 621 if (tests.promise_setup_called) { 622 tests.status.status = tests.status.ERROR; 623 tests.status.message = '`test` invoked after `promise_setup`'; 624 tests.complete(); 625 } 626 var test_name = get_test_name(func, name); 627 var test_obj = new Test(test_name, properties); 628 var value = test_obj.step(func, test_obj, test_obj); 629 630 if (value !== undefined) { 631 var msg = 'Test named "' + test_name + 632 '" passed a function to `test` that returned a value.'; 633 634 try { 635 if (value && typeof value.then === 'function') { 636 msg += ' Consider using `promise_test` instead when ' + 637 'using Promises or async/await.'; 638 } 639 } catch (err) {} 640 641 tests.status.status = tests.status.ERROR; 642 tests.status.message = msg; 643 } 644 645 if (test_obj.phase === test_obj.phases.STARTED) { 646 test_obj.done(); 647 } 648 } 649 650 /** 651 * Create an asynchronous test 652 * 653 * @param {TestFunction|string} funcOrName - Initial step function 654 * to call immediately with the test name as an argument (if any), 655 * or name of the test. 656 * @param {String} name - Test name (if a test function was 657 * provided). This must be unique in a given file and must be 658 * invariant between runs. 659 * @returns {Test} An object representing the ongoing test. 660 */ 661 function async_test(func, name, properties) 662 { 663 if (tests.promise_setup_called) { 664 tests.status.status = tests.status.ERROR; 665 tests.status.message = '`async_test` invoked after `promise_setup`'; 666 tests.complete(); 667 } 668 if (typeof func !== "function") { 669 properties = name; 670 name = func; 671 func = null; 672 } 673 var test_name = get_test_name(func, name); 674 var test_obj = new Test(test_name, properties); 675 if (func) { 676 var value = test_obj.step(func, test_obj, test_obj); 677 678 // Test authors sometimes return values to async_test, expecting us 679 // to handle the value somehow. Make doing so a harness error to be 680 // clear this is invalid, and point authors to promise_test if it 681 // may be appropriate. 682 // 683 // Note that we only perform this check on the initial function 684 // passed to async_test, not on any later steps - we haven't seen a 685 // consistent problem with those (and it's harder to check). 686 if (value !== undefined) { 687 var msg = 'Test named "' + test_name + 688 '" passed a function to `async_test` that returned a value.'; 689 690 try { 691 if (value && typeof value.then === 'function') { 692 msg += ' Consider using `promise_test` instead when ' + 693 'using Promises or async/await.'; 694 } 695 } catch (err) {} 696 697 tests.set_status(tests.status.ERROR, msg); 698 tests.complete(); 699 } 700 } 701 return test_obj; 702 } 703 704 /** 705 * Create a promise test. 706 * 707 * Promise tests are tests which are represented by a promise 708 * object. If the promise is fulfilled the test passes, if it's 709 * rejected the test fails, otherwise the test passes. 710 * 711 * @param {TestFunction} func - Test function. This must return a 712 * promise. The test is automatically marked as complete once the 713 * promise settles. 714 * @param {String} name - Test name. This must be unique in a 715 * given file and must be invariant between runs. 716 */ 717 function promise_test(func, name, properties) { 718 if (typeof func !== "function") { 719 properties = name; 720 name = func; 721 func = null; 722 } 723 var test_name = get_test_name(func, name); 724 var test = new Test(test_name, properties); 725 test._is_promise_test = true; 726 727 // If there is no promise tests queue make one. 728 if (!tests.promise_tests) { 729 tests.promise_tests = Promise.resolve(); 730 } 731 tests.promise_tests = tests.promise_tests.then(function() { 732 return new Promise(function(resolve) { 733 var promise = test.step(func, test, test); 734 735 test.step(function() { 736 assert(!!promise, "promise_test", null, 737 "test body must return a 'thenable' object (received ${value})", 738 {value:promise}); 739 assert(typeof promise.then === "function", "promise_test", null, 740 "test body must return a 'thenable' object (received an object with no `then` method)", 741 null); 742 }); 743 744 // Test authors may use the `step` method within a 745 // `promise_test` even though this reflects a mixture of 746 // asynchronous control flow paradigms. The "done" callback 747 // should be registered prior to the resolution of the 748 // user-provided Promise to avoid timeouts in cases where the 749 // Promise does not settle but a `step` function has thrown an 750 // error. 751 add_test_done_callback(test, resolve); 752 753 Promise.resolve(promise) 754 .catch(test.step_func( 755 function(value) { 756 if (value instanceof AssertionError) { 757 throw value; 758 } 759 assert(false, "promise_test", null, 760 "Unhandled rejection with value: ${value}", {value:value}); 761 })) 762 .then(function() { 763 test.done(); 764 }); 765 }); 766 }); 767 } 768 769 /** 770 * Make a copy of a Promise in the current realm. 771 * 772 * @param {Promise} promise the given promise that may be from a different 773 * realm 774 * @returns {Promise} 775 * 776 * An arbitrary promise provided by the caller may have originated 777 * in another frame that have since navigated away, rendering the 778 * frame's document inactive. Such a promise cannot be used with 779 * `await` or Promise.resolve(), as microtasks associated with it 780 * may be prevented from being run. See `issue 781 * 5319<https://github.com/whatwg/html/issues/5319>`_ for a 782 * particular case. 783 * 784 * In functions we define here, there is an expectation from the caller 785 * that the promise is from the current realm, that can always be used with 786 * `await`, etc. We therefore create a new promise in this realm that 787 * inherit the value and status from the given promise. 788 */ 789 790 function bring_promise_to_current_realm(promise) { 791 return new Promise(promise.then.bind(promise)); 792 } 793 794 /** 795 * Assert that a Promise is rejected with the right ECMAScript exception. 796 * 797 * @param {Test} test - the `Test` to use for the assertion. 798 * @param {Function} constructor - The expected exception constructor. 799 * @param {Promise} promise - The promise that's expected to 800 * reject with the given exception. 801 * @param {string} [description] Error message to add to assert in case of 802 * failure. 803 */ 804 function promise_rejects_js(test, constructor, promise, description) { 805 return bring_promise_to_current_realm(promise) 806 .then(test.unreached_func("Should have rejected: " + description)) 807 .catch(function(e) { 808 assert_throws_js_impl(constructor, function() { throw e }, 809 description, "promise_rejects_js"); 810 }); 811 } 812 813 /** 814 * Assert that a Promise is rejected with the right DOMException. 815 * 816 * For the remaining arguments, there are two ways of calling 817 * promise_rejects_dom: 818 * 819 * 1) If the DOMException is expected to come from the current global, the 820 * third argument should be the promise expected to reject, and a fourth, 821 * optional, argument is the assertion description. 822 * 823 * 2) If the DOMException is expected to come from some other global, the 824 * third argument should be the DOMException constructor from that global, 825 * the fourth argument the promise expected to reject, and the fifth, 826 * optional, argument the assertion description. 827 * 828 * @param {Test} test - the `Test` to use for the assertion. 829 * @param {number|string} type - See documentation for 830 * `assert_throws_dom <#assert_throws_dom>`_. 831 * @param {Function} promiseOrConstructor - Either the constructor 832 * for the expected exception (if the exception comes from another 833 * global), or the promise that's expected to reject (if the 834 * exception comes from the current global). 835 * @param {Function|string} descriptionOrPromise - Either the 836 * promise that's expected to reject (if the exception comes from 837 * another global), or the optional description of the condition 838 * being tested (if the exception comes from the current global). 839 * @param {string} [description] - Description of the condition 840 * being tested (if the exception comes from another global). 841 * 842 */ 843 function promise_rejects_dom(test, type, promiseOrConstructor, descriptionOrPromise, maybeDescription) { 844 let constructor, promise, description; 845 if (typeof promiseOrConstructor === "function" && 846 promiseOrConstructor.name === "DOMException") { 847 constructor = promiseOrConstructor; 848 promise = descriptionOrPromise; 849 description = maybeDescription; 850 } else { 851 constructor = self.DOMException; 852 promise = promiseOrConstructor; 853 description = descriptionOrPromise; 854 assert(maybeDescription === undefined, 855 "Too many args pased to no-constructor version of promise_rejects_dom"); 856 } 857 return bring_promise_to_current_realm(promise) 858 .then(test.unreached_func("Should have rejected: " + description)) 859 .catch(function(e) { 860 assert_throws_dom_impl(type, function() { throw e }, description, 861 "promise_rejects_dom", constructor); 862 }); 863 } 864 865 /** 866 * Assert that a Promise is rejected with the provided value. 867 * 868 * @param {Test} test - the `Test` to use for the assertion. 869 * @param {Any} exception - The expected value of the rejected promise. 870 * @param {Promise} promise - The promise that's expected to 871 * reject. 872 * @param {string} [description] Error message to add to assert in case of 873 * failure. 874 */ 875 function promise_rejects_exactly(test, exception, promise, description) { 876 return bring_promise_to_current_realm(promise) 877 .then(test.unreached_func("Should have rejected: " + description)) 878 .catch(function(e) { 879 assert_throws_exactly_impl(exception, function() { throw e }, 880 description, "promise_rejects_exactly"); 881 }); 882 } 883 884 /** 885 * Allow DOM events to be handled using Promises. 886 * 887 * This can make it a lot easier to test a very specific series of events, 888 * including ensuring that unexpected events are not fired at any point. 889 * 890 * `EventWatcher` will assert if an event occurs while there is no `wait_for` 891 * created Promise waiting to be fulfilled, or if the event is of a different type 892 * to the type currently expected. This ensures that only the events that are 893 * expected occur, in the correct order, and with the correct timing. 894 * 895 * @constructor 896 * @param {Test} test - The `Test` to use for the assertion. 897 * @param {EventTarget} watchedNode - The target expected to receive the events. 898 * @param {string[]} eventTypes - List of events to watch for. 899 * @param {Promise} timeoutPromise - Promise that will cause the 900 * test to be set to `TIMEOUT` once fulfilled. 901 * 902 */ 903 function EventWatcher(test, watchedNode, eventTypes, timeoutPromise) 904 { 905 if (typeof eventTypes == 'string') { 906 eventTypes = [eventTypes]; 907 } 908 909 var waitingFor = null; 910 911 // This is null unless we are recording all events, in which case it 912 // will be an Array object. 913 var recordedEvents = null; 914 915 var eventHandler = test.step_func(function(evt) { 916 assert_true(!!waitingFor, 917 'Not expecting event, but got ' + evt.type + ' event'); 918 assert_equals(evt.type, waitingFor.types[0], 919 'Expected ' + waitingFor.types[0] + ' event, but got ' + 920 evt.type + ' event instead'); 921 922 if (Array.isArray(recordedEvents)) { 923 recordedEvents.push(evt); 924 } 925 926 if (waitingFor.types.length > 1) { 927 // Pop first event from array 928 waitingFor.types.shift(); 929 return; 930 } 931 // We need to null out waitingFor before calling the resolve function 932 // since the Promise's resolve handlers may call wait_for() which will 933 // need to set waitingFor. 934 var resolveFunc = waitingFor.resolve; 935 waitingFor = null; 936 // Likewise, we should reset the state of recordedEvents. 937 var result = recordedEvents || evt; 938 recordedEvents = null; 939 resolveFunc(result); 940 }); 941 942 for (var i = 0; i < eventTypes.length; i++) { 943 watchedNode.addEventListener(eventTypes[i], eventHandler, false); 944 } 945 946 /** 947 * Returns a Promise that will resolve after the specified event or 948 * series of events has occurred. 949 * 950 * @param {Object} options An optional options object. If the 'record' property 951 * on this object has the value 'all', when the Promise 952 * returned by this function is resolved, *all* Event 953 * objects that were waited for will be returned as an 954 * array. 955 * 956 * @example 957 * const watcher = new EventWatcher(t, div, [ 'animationstart', 958 * 'animationiteration', 959 * 'animationend' ]); 960 * return watcher.wait_for([ 'animationstart', 'animationend' ], 961 * { record: 'all' }).then(evts => { 962 * assert_equals(evts[0].elapsedTime, 0.0); 963 * assert_equals(evts[1].elapsedTime, 2.0); 964 * }); 965 */ 966 this.wait_for = function(types, options) { 967 if (waitingFor) { 968 return Promise.reject('Already waiting for an event or events'); 969 } 970 if (typeof types == 'string') { 971 types = [types]; 972 } 973 if (options && options.record && options.record === 'all') { 974 recordedEvents = []; 975 } 976 return new Promise(function(resolve, reject) { 977 var timeout = test.step_func(function() { 978 // If the timeout fires after the events have been received 979 // or during a subsequent call to wait_for, ignore it. 980 if (!waitingFor || waitingFor.resolve !== resolve) 981 return; 982 983 // This should always fail, otherwise we should have 984 // resolved the promise. 985 assert_true(waitingFor.types.length == 0, 986 'Timed out waiting for ' + waitingFor.types.join(', ')); 987 var result = recordedEvents; 988 recordedEvents = null; 989 var resolveFunc = waitingFor.resolve; 990 waitingFor = null; 991 resolveFunc(result); 992 }); 993 994 if (timeoutPromise) { 995 timeoutPromise().then(timeout); 996 } 997 998 waitingFor = { 999 types: types, 1000 resolve: resolve, 1001 reject: reject 1002 }; 1003 }); 1004 }; 1005 1006 /** 1007 * Stop listening for events 1008 */ 1009 function stop_watching() { 1010 for (var i = 0; i < eventTypes.length; i++) { 1011 watchedNode.removeEventListener(eventTypes[i], eventHandler, false); 1012 } 1013 }; 1014 1015 test._add_cleanup(stop_watching); 1016 1017 return this; 1018 } 1019 expose(EventWatcher, 'EventWatcher'); 1020 1021 /** 1022 * @typedef {Object} SettingsObject 1023 * @property {bool} single_test - Use the single-page-test 1024 * mode. In this mode the Document represents a single 1025 * `async_test`. Asserts may be used directly without requiring 1026 * `Test.step` or similar wrappers, and any exceptions set the 1027 * status of the test rather than the status of the harness. 1028 * @property {bool} allow_uncaught_exception - don't treat an 1029 * uncaught exception as an error; needed when e.g. testing the 1030 * `window.onerror` handler. 1031 * @property {boolean} explicit_done - Wait for a call to `done()` 1032 * before declaring all tests complete (this is always true for 1033 * single-page tests). 1034 * @property hide_test_state - hide the test state output while 1035 * the test is running; This is helpful when the output of the test state 1036 * may interfere the test results. 1037 * @property {bool} explicit_timeout - disable file timeout; only 1038 * stop waiting for results when the `timeout()` function is 1039 * called This should typically only be set for manual tests, or 1040 * by a test runner that providees its own timeout mechanism. 1041 * @property {number} timeout_multiplier - Multiplier to apply to 1042 * per-test timeouts. This should only be set by a test runner. 1043 * @property {Document} output_document - The document to which 1044 * results should be logged. By default this is the current 1045 * document but could be an ancestor document in some cases e.g. a 1046 * SVG test loaded in an HTML wrapper 1047 * 1048 */ 1049 1050 /** 1051 * Configure the harness 1052 * 1053 * @param {Function|SettingsObject} funcOrProperties - Either a 1054 * setup function to run, or a set of properties. If this is a 1055 * function that function is run synchronously. Any exception in 1056 * the function will set the overall harness status to `ERROR`. 1057 * @param {SettingsObject} maybeProperties - An object containing 1058 * the settings to use, if the first argument is a function. 1059 * 1060 */ 1061 function setup(func_or_properties, maybe_properties) 1062 { 1063 var func = null; 1064 var properties = {}; 1065 if (arguments.length === 2) { 1066 func = func_or_properties; 1067 properties = maybe_properties; 1068 } else if (func_or_properties instanceof Function) { 1069 func = func_or_properties; 1070 } else { 1071 properties = func_or_properties; 1072 } 1073 tests.setup(func, properties); 1074 test_environment.on_new_harness_properties(properties); 1075 } 1076 1077 /** 1078 * Configure the harness, waiting for a promise to resolve 1079 * before running any `promise_test` tests. 1080 * 1081 * @param {Function} func - Function returning a promise that's 1082 * run synchronously. Promise tests are not run until after this 1083 * function has resolved. 1084 * @param {SettingsObject} [properties] - An object containing 1085 * the harness settings to use. 1086 * 1087 */ 1088 function promise_setup(func, properties={}) 1089 { 1090 if (typeof func !== "function") { 1091 tests.set_status(tests.status.ERROR, 1092 "promise_test invoked without a function"); 1093 tests.complete(); 1094 return; 1095 } 1096 tests.promise_setup_called = true; 1097 1098 if (!tests.promise_tests) { 1099 tests.promise_tests = Promise.resolve(); 1100 } 1101 1102 tests.promise_tests = tests.promise_tests 1103 .then(function() 1104 { 1105 var result; 1106 1107 tests.setup(null, properties); 1108 result = func(); 1109 test_environment.on_new_harness_properties(properties); 1110 1111 if (!result || typeof result.then !== "function") { 1112 throw "Non-thenable returned by function passed to `promise_setup`"; 1113 } 1114 return result; 1115 }) 1116 .catch(function(e) 1117 { 1118 tests.set_status(tests.status.ERROR, 1119 String(e), 1120 e && e.stack); 1121 tests.complete(); 1122 }); 1123 } 1124 1125 /** 1126 * Mark test loading as complete. 1127 * 1128 * Typically this function is called implicitly on page load; it's 1129 * only necessary for users to call this when either the 1130 * ``explicit_done`` or ``single_page`` properties have been set 1131 * via the :js:func:`setup` function. 1132 * 1133 * For single page tests this marks the test as complete and sets its status. 1134 * For other tests, this marks test loading as complete, but doesn't affect ongoing tests. 1135 */ 1136 function done() { 1137 if (tests.tests.length === 0) { 1138 // `done` is invoked after handling uncaught exceptions, so if the 1139 // harness status is already set, the corresponding message is more 1140 // descriptive than the generic message defined here. 1141 if (tests.status.status === null) { 1142 tests.status.status = tests.status.ERROR; 1143 tests.status.message = "done() was called without first defining any tests"; 1144 } 1145 1146 tests.complete(); 1147 return; 1148 } 1149 if (tests.file_is_test) { 1150 // file is test files never have asynchronous cleanup logic, 1151 // meaning the fully-synchronous `done` function can be used here. 1152 tests.tests[0].done(); 1153 } 1154 tests.end_wait(); 1155 } 1156 1157 /** 1158 * @deprecated generate a list of tests from a function and list of arguments 1159 * 1160 * This is deprecated because it runs all the tests outside of the test functions 1161 * and as a result any test throwing an exception will result in no tests being 1162 * run. In almost all cases, you should simply call test within the loop you would 1163 * use to generate the parameter list array. 1164 * 1165 * @param {Function} func - The function that will be called for each generated tests. 1166 * @param {Any[][]} args - An array of arrays. Each nested array 1167 * has the structure `[testName, ...testArgs]`. For each of these nested arrays 1168 * array, a test is generated with name `testName` and test function equivalent to 1169 * `func(..testArgs)`. 1170 */ 1171 function generate_tests(func, args, properties) { 1172 forEach(args, function(x, i) 1173 { 1174 var name = x[0]; 1175 test(function() 1176 { 1177 func.apply(this, x.slice(1)); 1178 }, 1179 name, 1180 Array.isArray(properties) ? properties[i] : properties); 1181 }); 1182 } 1183 1184 /** 1185 * @deprecated 1186 * 1187 * Register a function as a DOM event listener to the 1188 * given object for the event bubbling phase. 1189 * 1190 * @param {EventTarget} object - Event target 1191 * @param {string} event - Event name 1192 * @param {Function} callback - Event handler. 1193 */ 1194 function on_event(object, event, callback) 1195 { 1196 object.addEventListener(event, callback, false); 1197 } 1198 1199 /** 1200 * Global version of :js:func:`Test.step_timeout` for use in single page tests. 1201 * 1202 * @param {Function} func - Function to run after the timeout 1203 * @param {number} timeout - Time in ms to wait before running the 1204 * test step. The actual wait time is ``timeout`` x 1205 * ``timeout_multiplier``. 1206 */ 1207 function step_timeout(func, timeout) { 1208 var outer_this = this; 1209 var args = Array.prototype.slice.call(arguments, 2); 1210 return setTimeout(function() { 1211 func.apply(outer_this, args); 1212 }, timeout * tests.timeout_multiplier); 1213 } 1214 1215 expose(test, 'test'); 1216 expose(async_test, 'async_test'); 1217 expose(promise_test, 'promise_test'); 1218 expose(promise_rejects_js, 'promise_rejects_js'); 1219 expose(promise_rejects_dom, 'promise_rejects_dom'); 1220 expose(promise_rejects_exactly, 'promise_rejects_exactly'); 1221 expose(generate_tests, 'generate_tests'); 1222 expose(setup, 'setup'); 1223 expose(promise_setup, 'promise_setup'); 1224 expose(done, 'done'); 1225 expose(on_event, 'on_event'); 1226 expose(step_timeout, 'step_timeout'); 1227 1228 /* 1229 * Return a string truncated to the given length, with ... added at the end 1230 * if it was longer. 1231 */ 1232 function truncate(s, len) 1233 { 1234 if (s.length > len) { 1235 return s.substring(0, len - 3) + "..."; 1236 } 1237 return s; 1238 } 1239 1240 /* 1241 * Return true if object is probably a Node object. 1242 */ 1243 function is_node(object) 1244 { 1245 // I use duck-typing instead of instanceof, because 1246 // instanceof doesn't work if the node is from another window (like an 1247 // iframe's contentWindow): 1248 // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295 1249 try { 1250 var has_node_properties = ("nodeType" in object && 1251 "nodeName" in object && 1252 "nodeValue" in object && 1253 "childNodes" in object); 1254 } catch (e) { 1255 // We're probably cross-origin, which means we aren't a node 1256 return false; 1257 } 1258 1259 if (has_node_properties) { 1260 try { 1261 object.nodeType; 1262 } catch (e) { 1263 // The object is probably Node.prototype or another prototype 1264 // object that inherits from it, and not a Node instance. 1265 return false; 1266 } 1267 return true; 1268 } 1269 return false; 1270 } 1271 1272 var replacements = { 1273 "0": "0", 1274 "1": "x01", 1275 "2": "x02", 1276 "3": "x03", 1277 "4": "x04", 1278 "5": "x05", 1279 "6": "x06", 1280 "7": "x07", 1281 "8": "b", 1282 "9": "t", 1283 "10": "n", 1284 "11": "v", 1285 "12": "f", 1286 "13": "r", 1287 "14": "x0e", 1288 "15": "x0f", 1289 "16": "x10", 1290 "17": "x11", 1291 "18": "x12", 1292 "19": "x13", 1293 "20": "x14", 1294 "21": "x15", 1295 "22": "x16", 1296 "23": "x17", 1297 "24": "x18", 1298 "25": "x19", 1299 "26": "x1a", 1300 "27": "x1b", 1301 "28": "x1c", 1302 "29": "x1d", 1303 "30": "x1e", 1304 "31": "x1f", 1305 "0xfffd": "ufffd", 1306 "0xfffe": "ufffe", 1307 "0xffff": "uffff", 1308 }; 1309 1310 /** 1311 * Convert a value to a nice, human-readable string 1312 * 1313 * When many JavaScript Object values are coerced to a String, the 1314 * resulting value will be ``"[object Object]"``. This obscures 1315 * helpful information, making the coerced value unsuitable for 1316 * use in assertion messages, test names, and debugging 1317 * statements. `format_value` produces more distinctive string 1318 * representations of many kinds of objects, including arrays and 1319 * the more important DOM Node types. It also translates String 1320 * values containing control characters to include human-readable 1321 * representations. 1322 * 1323 * @example 1324 * // "Document node with 2 children" 1325 * format_value(document); 1326 * @example 1327 * // "\"foo\\uffffbar\"" 1328 * format_value("foo\uffffbar"); 1329 * @example 1330 * // "[-0, Infinity]" 1331 * format_value([-0, Infinity]); 1332 * @param {Any} val - The value to convert to a string. 1333 * @returns {string} - A string representation of ``val``, optimised for human readability. 1334 */ 1335 function format_value(val, seen) 1336 { 1337 if (!seen) { 1338 seen = []; 1339 } 1340 if (typeof val === "object" && val !== null) { 1341 if (seen.indexOf(val) >= 0) { 1342 return "[...]"; 1343 } 1344 seen.push(val); 1345 } 1346 if (Array.isArray(val)) { 1347 let output = "["; 1348 if (val.beginEllipsis !== undefined) { 1349 output += "…, "; 1350 } 1351 output += val.map(function(x) {return format_value(x, seen);}).join(", "); 1352 if (val.endEllipsis !== undefined) { 1353 output += ", …"; 1354 } 1355 return output + "]"; 1356 } 1357 1358 switch (typeof val) { 1359 case "string": 1360 val = val.replace(/\\/g, "\\\\"); 1361 for (var p in replacements) { 1362 var replace = "\\" + replacements[p]; 1363 val = val.replace(RegExp(String.fromCharCode(p), "g"), replace); 1364 } 1365 return '"' + val.replace(/"/g, '\\"') + '"'; 1366 case "boolean": 1367 case "undefined": 1368 return String(val); 1369 case "number": 1370 // In JavaScript, -0 === 0 and String(-0) == "0", so we have to 1371 // special-case. 1372 if (val === -0 && 1/val === -Infinity) { 1373 return "-0"; 1374 } 1375 return String(val); 1376 case "object": 1377 if (val === null) { 1378 return "null"; 1379 } 1380 1381 // Special-case Node objects, since those come up a lot in my tests. I 1382 // ignore namespaces. 1383 if (is_node(val)) { 1384 switch (val.nodeType) { 1385 case Node.ELEMENT_NODE: 1386 var ret = "<" + val.localName; 1387 for (var i = 0; i < val.attributes.length; i++) { 1388 ret += " " + val.attributes[i].name + '="' + val.attributes[i].value + '"'; 1389 } 1390 ret += ">" + val.innerHTML + "</" + val.localName + ">"; 1391 return "Element node " + truncate(ret, 60); 1392 case Node.TEXT_NODE: 1393 return 'Text node "' + truncate(val.data, 60) + '"'; 1394 case Node.PROCESSING_INSTRUCTION_NODE: 1395 return "ProcessingInstruction node with target " + format_value(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 60)); 1396 case Node.COMMENT_NODE: 1397 return "Comment node <!--" + truncate(val.data, 60) + "-->"; 1398 case Node.DOCUMENT_NODE: 1399 return "Document node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children"); 1400 case Node.DOCUMENT_TYPE_NODE: 1401 return "DocumentType node"; 1402 case Node.DOCUMENT_FRAGMENT_NODE: 1403 return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children"); 1404 default: 1405 return "Node object of unknown type"; 1406 } 1407 } 1408 1409 /* falls through */ 1410 default: 1411 try { 1412 return typeof val + ' "' + truncate(String(val), 1000) + '"'; 1413 } catch(e) { 1414 return ("[stringifying object threw " + String(e) + 1415 " with type " + String(typeof e) + "]"); 1416 } 1417 } 1418 } 1419 expose(format_value, "format_value"); 1420 1421 /* 1422 * Assertions 1423 */ 1424 1425 function expose_assert(f, name) { 1426 function assert_wrapper(...args) { 1427 let status = Test.statuses.TIMEOUT; 1428 let stack = null; 1429 try { 1430 if (settings.debug) { 1431 console.debug("ASSERT", name, tests.current_test && tests.current_test.name, args); 1432 } 1433 if (tests.output) { 1434 tests.set_assert(name, args); 1435 } 1436 const rv = f.apply(undefined, args); 1437 status = Test.statuses.PASS; 1438 return rv; 1439 } catch(e) { 1440 status = Test.statuses.FAIL; 1441 stack = e.stack ? e.stack : null; 1442 throw e; 1443 } finally { 1444 if (tests.output && !stack) { 1445 stack = get_stack(); 1446 } 1447 if (tests.output) { 1448 tests.set_assert_status(status, stack); 1449 } 1450 } 1451 } 1452 expose(assert_wrapper, name); 1453 } 1454 1455 /** 1456 * Assert that ``actual`` is strictly true 1457 * 1458 * @param {Any} actual - Value that is asserted to be true 1459 * @param {string} [description] - Description of the condition being tested 1460 */ 1461 function assert_true(actual, description) 1462 { 1463 assert(actual === true, "assert_true", description, 1464 "expected true got ${actual}", {actual:actual}); 1465 } 1466 expose_assert(assert_true, "assert_true"); 1467 1468 /** 1469 * Assert that ``actual`` is strictly false 1470 * 1471 * @param {Any} actual - Value that is asserted to be false 1472 * @param {string} [description] - Description of the condition being tested 1473 */ 1474 function assert_false(actual, description) 1475 { 1476 assert(actual === false, "assert_false", description, 1477 "expected false got ${actual}", {actual:actual}); 1478 } 1479 expose_assert(assert_false, "assert_false"); 1480 1481 function same_value(x, y) { 1482 if (y !== y) { 1483 //NaN case 1484 return x !== x; 1485 } 1486 if (x === 0 && y === 0) { 1487 //Distinguish +0 and -0 1488 return 1/x === 1/y; 1489 } 1490 return x === y; 1491 } 1492 1493 /** 1494 * Assert that ``actual`` is the same value as ``expected``. 1495 * 1496 * For objects this compares by cobject identity; for primitives 1497 * this distinguishes between 0 and -0, and has correct handling 1498 * of NaN. 1499 * 1500 * @param {Any} actual - Test value. 1501 * @param {Any} expected - Expected value. 1502 * @param {string} [description] - Description of the condition being tested. 1503 */ 1504 function assert_equals(actual, expected, description) 1505 { 1506 /* 1507 * Test if two primitives are equal or two objects 1508 * are the same object 1509 */ 1510 if (typeof actual != typeof expected) { 1511 assert(false, "assert_equals", description, 1512 "expected (" + typeof expected + ") ${expected} but got (" + typeof actual + ") ${actual}", 1513 {expected:expected, actual:actual}); 1514 return; 1515 } 1516 assert(same_value(actual, expected), "assert_equals", description, 1517 "expected ${expected} but got ${actual}", 1518 {expected:expected, actual:actual}); 1519 } 1520 expose_assert(assert_equals, "assert_equals"); 1521 1522 /** 1523 * Assert that ``actual`` is not the same value as ``expected``. 1524 * 1525 * Comparison is as for :js:func:`assert_equals`. 1526 * 1527 * @param {Any} actual - Test value. 1528 * @param {Any} expected - The value ``actual`` is expected to be different to. 1529 * @param {string} [description] - Description of the condition being tested. 1530 */ 1531 function assert_not_equals(actual, expected, description) 1532 { 1533 assert(!same_value(actual, expected), "assert_not_equals", description, 1534 "got disallowed value ${actual}", 1535 {actual:actual}); 1536 } 1537 expose_assert(assert_not_equals, "assert_not_equals"); 1538 1539 /** 1540 * Assert that ``expected`` is an array and ``actual`` is one of the members. 1541 * This is implemented using ``indexOf``, so doesn't handle NaN or ±0 correctly. 1542 * 1543 * @param {Any} actual - Test value. 1544 * @param {Array} expected - An array that ``actual`` is expected to 1545 * be a member of. 1546 * @param {string} [description] - Description of the condition being tested. 1547 */ 1548 function assert_in_array(actual, expected, description) 1549 { 1550 assert(expected.indexOf(actual) != -1, "assert_in_array", description, 1551 "value ${actual} not in array ${expected}", 1552 {actual:actual, expected:expected}); 1553 } 1554 expose_assert(assert_in_array, "assert_in_array"); 1555 1556 // This function was deprecated in July of 2015. 1557 // See https://github.com/web-platform-tests/wpt/issues/2033 1558 /** 1559 * @deprecated 1560 * Recursively compare two objects for equality. 1561 * 1562 * See `Issue 2033 1563 * <https://github.com/web-platform-tests/wpt/issues/2033>`_ for 1564 * more information. 1565 * 1566 * @param {Object} actual - Test value. 1567 * @param {Object} expected - Expected value. 1568 * @param {string} [description] - Description of the condition being tested. 1569 */ 1570 function assert_object_equals(actual, expected, description) 1571 { 1572 assert(typeof actual === "object" && actual !== null, "assert_object_equals", description, 1573 "value is ${actual}, expected object", 1574 {actual: actual}); 1575 //This needs to be improved a great deal 1576 function check_equal(actual, expected, stack) 1577 { 1578 stack.push(actual); 1579 1580 var p; 1581 for (p in actual) { 1582 assert(expected.hasOwnProperty(p), "assert_object_equals", description, 1583 "unexpected property ${p}", {p:p}); 1584 1585 if (typeof actual[p] === "object" && actual[p] !== null) { 1586 if (stack.indexOf(actual[p]) === -1) { 1587 check_equal(actual[p], expected[p], stack); 1588 } 1589 } else { 1590 assert(same_value(actual[p], expected[p]), "assert_object_equals", description, 1591 "property ${p} expected ${expected} got ${actual}", 1592 {p:p, expected:expected[p], actual:actual[p]}); 1593 } 1594 } 1595 for (p in expected) { 1596 assert(actual.hasOwnProperty(p), 1597 "assert_object_equals", description, 1598 "expected property ${p} missing", {p:p}); 1599 } 1600 stack.pop(); 1601 } 1602 check_equal(actual, expected, []); 1603 } 1604 expose_assert(assert_object_equals, "assert_object_equals"); 1605 1606 /** 1607 * Assert that ``actual`` and ``expected`` are both arrays, and that the array properties of 1608 * ``actual`` and ``expected`` are all the same value (as for :js:func:`assert_equals`). 1609 * 1610 * @param {Array} actual - Test array. 1611 * @param {Array} expected - Array that is expected to contain the same values as ``actual``. 1612 * @param {string} [description] - Description of the condition being tested. 1613 */ 1614 function assert_array_equals(actual, expected, description) 1615 { 1616 const max_array_length = 20; 1617 function shorten_array(arr, offset = 0) { 1618 // Make ", …" only show up when it would likely reduce the length, not accounting for 1619 // fonts. 1620 if (arr.length < max_array_length + 2) { 1621 return arr; 1622 } 1623 // By default we want half the elements after the offset and half before 1624 // But if that takes us past the end of the array, we have more before, and 1625 // if it takes us before the start we have more after. 1626 const length_after_offset = Math.floor(max_array_length / 2); 1627 let upper_bound = Math.min(length_after_offset + offset, arr.length); 1628 const lower_bound = Math.max(upper_bound - max_array_length, 0); 1629 1630 if (lower_bound === 0) { 1631 upper_bound = max_array_length; 1632 } 1633 1634 const output = arr.slice(lower_bound, upper_bound); 1635 if (lower_bound > 0) { 1636 output.beginEllipsis = true; 1637 } 1638 if (upper_bound < arr.length) { 1639 output.endEllipsis = true; 1640 } 1641 return output; 1642 } 1643 1644 assert(typeof actual === "object" && actual !== null && "length" in actual, 1645 "assert_array_equals", description, 1646 "value is ${actual}, expected array", 1647 {actual:actual}); 1648 assert(actual.length === expected.length, 1649 "assert_array_equals", description, 1650 "lengths differ, expected array ${expected} length ${expectedLength}, got ${actual} length ${actualLength}", 1651 {expected:shorten_array(expected, expected.length - 1), expectedLength:expected.length, 1652 actual:shorten_array(actual, actual.length - 1), actualLength:actual.length 1653 }); 1654 1655 for (var i = 0; i < actual.length; i++) { 1656 assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i), 1657 "assert_array_equals", description, 1658 "expected property ${i} to be ${expected} but was ${actual} (expected array ${arrayExpected} got ${arrayActual})", 1659 {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing", 1660 actual:actual.hasOwnProperty(i) ? "present" : "missing", 1661 arrayExpected:shorten_array(expected, i), arrayActual:shorten_array(actual, i)}); 1662 assert(same_value(expected[i], actual[i]), 1663 "assert_array_equals", description, 1664 "expected property ${i} to be ${expected} but got ${actual} (expected array ${arrayExpected} got ${arrayActual})", 1665 {i:i, expected:expected[i], actual:actual[i], 1666 arrayExpected:shorten_array(expected, i), arrayActual:shorten_array(actual, i)}); 1667 } 1668 } 1669 expose_assert(assert_array_equals, "assert_array_equals"); 1670 1671 /** 1672 * Assert that each array property in ``actual`` is a number within 1673 * ± `epsilon` of the corresponding property in `expected`. 1674 * 1675 * @param {Array} actual - Array of test values. 1676 * @param {Array} expected - Array of values expected to be close to the values in ``actual``. 1677 * @param {number} epsilon - Magnitude of allowed difference 1678 * between each value in ``actual`` and ``expected``. 1679 * @param {string} [description] - Description of the condition being tested. 1680 */ 1681 function assert_array_approx_equals(actual, expected, epsilon, description) 1682 { 1683 /* 1684 * Test if two primitive arrays are equal within +/- epsilon 1685 */ 1686 assert(actual.length === expected.length, 1687 "assert_array_approx_equals", description, 1688 "lengths differ, expected ${expected} got ${actual}", 1689 {expected:expected.length, actual:actual.length}); 1690 1691 for (var i = 0; i < actual.length; i++) { 1692 assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i), 1693 "assert_array_approx_equals", description, 1694 "property ${i}, property expected to be ${expected} but was ${actual}", 1695 {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing", 1696 actual:actual.hasOwnProperty(i) ? "present" : "missing"}); 1697 assert(typeof actual[i] === "number", 1698 "assert_array_approx_equals", description, 1699 "property ${i}, expected a number but got a ${type_actual}", 1700 {i:i, type_actual:typeof actual[i]}); 1701 assert(Math.abs(actual[i] - expected[i]) <= epsilon, 1702 "assert_array_approx_equals", description, 1703 "property ${i}, expected ${expected} +/- ${epsilon}, expected ${expected} but got ${actual}", 1704 {i:i, expected:expected[i], actual:actual[i], epsilon:epsilon}); 1705 } 1706 } 1707 expose_assert(assert_array_approx_equals, "assert_array_approx_equals"); 1708 1709 /** 1710 * Assert that ``actual`` is within ± ``epsilon`` of ``expected``. 1711 * 1712 * @param {number} actual - Test value. 1713 * @param {number} expected - Value number is expected to be close to. 1714 * @param {number} epsilon - Magnitude of allowed difference between ``actual`` and ``expected``. 1715 * @param {string} [description] - Description of the condition being tested. 1716 */ 1717 function assert_approx_equals(actual, expected, epsilon, description) 1718 { 1719 /* 1720 * Test if two primitive numbers are equal within +/- epsilon 1721 */ 1722 assert(typeof actual === "number", 1723 "assert_approx_equals", description, 1724 "expected a number but got a ${type_actual}", 1725 {type_actual:typeof actual}); 1726 1727 // The epsilon math below does not place nice with NaN and Infinity 1728 // But in this case Infinity = Infinity and NaN = NaN 1729 if (isFinite(actual) || isFinite(expected)) { 1730 assert(Math.abs(actual - expected) <= epsilon, 1731 "assert_approx_equals", description, 1732 "expected ${expected} +/- ${epsilon} but got ${actual}", 1733 {expected:expected, actual:actual, epsilon:epsilon}); 1734 } else { 1735 assert_equals(actual, expected); 1736 } 1737 } 1738 expose_assert(assert_approx_equals, "assert_approx_equals"); 1739 1740 /** 1741 * Assert that ``actual`` is a number less than ``expected``. 1742 * 1743 * @param {number} actual - Test value. 1744 * @param {number} expected - Number that ``actual`` must be less than. 1745 * @param {string} [description] - Description of the condition being tested. 1746 */ 1747 function assert_less_than(actual, expected, description) 1748 { 1749 /* 1750 * Test if a primitive number is less than another 1751 */ 1752 assert(typeof actual === "number", 1753 "assert_less_than", description, 1754 "expected a number but got a ${type_actual}", 1755 {type_actual:typeof actual}); 1756 1757 assert(actual < expected, 1758 "assert_less_than", description, 1759 "expected a number less than ${expected} but got ${actual}", 1760 {expected:expected, actual:actual}); 1761 } 1762 expose_assert(assert_less_than, "assert_less_than"); 1763 1764 /** 1765 * Assert that ``actual`` is a number greater than ``expected``. 1766 * 1767 * @param {number} actual - Test value. 1768 * @param {number} expected - Number that ``actual`` must be greater than. 1769 * @param {string} [description] - Description of the condition being tested. 1770 */ 1771 function assert_greater_than(actual, expected, description) 1772 { 1773 /* 1774 * Test if a primitive number is greater than another 1775 */ 1776 assert(typeof actual === "number", 1777 "assert_greater_than", description, 1778 "expected a number but got a ${type_actual}", 1779 {type_actual:typeof actual}); 1780 1781 assert(actual > expected, 1782 "assert_greater_than", description, 1783 "expected a number greater than ${expected} but got ${actual}", 1784 {expected:expected, actual:actual}); 1785 } 1786 expose_assert(assert_greater_than, "assert_greater_than"); 1787 1788 /** 1789 * Assert that ``actual`` is a number greater than ``lower`` and less 1790 * than ``upper`` but not equal to either. 1791 * 1792 * @param {number} actual - Test value. 1793 * @param {number} lower - Number that ``actual`` must be greater than. 1794 * @param {number} upper - Number that ``actual`` must be less than. 1795 * @param {string} [description] - Description of the condition being tested. 1796 */ 1797 function assert_between_exclusive(actual, lower, upper, description) 1798 { 1799 /* 1800 * Test if a primitive number is between two others 1801 */ 1802 assert(typeof actual === "number", 1803 "assert_between_exclusive", description, 1804 "expected a number but got a ${type_actual}", 1805 {type_actual:typeof actual}); 1806 1807 assert(actual > lower && actual < upper, 1808 "assert_between_exclusive", description, 1809 "expected a number greater than ${lower} " + 1810 "and less than ${upper} but got ${actual}", 1811 {lower:lower, upper:upper, actual:actual}); 1812 } 1813 expose_assert(assert_between_exclusive, "assert_between_exclusive"); 1814 1815 /** 1816 * Assert that ``actual`` is a number less than or equal to ``expected``. 1817 * 1818 * @param {number} actual - Test value. 1819 * @param {number} expected - Number that ``actual`` must be less 1820 * than or equal to. 1821 * @param {string} [description] - Description of the condition being tested. 1822 */ 1823 function assert_less_than_equal(actual, expected, description) 1824 { 1825 /* 1826 * Test if a primitive number is less than or equal to another 1827 */ 1828 assert(typeof actual === "number", 1829 "assert_less_than_equal", description, 1830 "expected a number but got a ${type_actual}", 1831 {type_actual:typeof actual}); 1832 1833 assert(actual <= expected, 1834 "assert_less_than_equal", description, 1835 "expected a number less than or equal to ${expected} but got ${actual}", 1836 {expected:expected, actual:actual}); 1837 } 1838 expose_assert(assert_less_than_equal, "assert_less_than_equal"); 1839 1840 /** 1841 * Assert that ``actual`` is a number greater than or equal to ``expected``. 1842 * 1843 * @param {number} actual - Test value. 1844 * @param {number} expected - Number that ``actual`` must be greater 1845 * than or equal to. 1846 * @param {string} [description] - Description of the condition being tested. 1847 */ 1848 function assert_greater_than_equal(actual, expected, description) 1849 { 1850 /* 1851 * Test if a primitive number is greater than or equal to another 1852 */ 1853 assert(typeof actual === "number", 1854 "assert_greater_than_equal", description, 1855 "expected a number but got a ${type_actual}", 1856 {type_actual:typeof actual}); 1857 1858 assert(actual >= expected, 1859 "assert_greater_than_equal", description, 1860 "expected a number greater than or equal to ${expected} but got ${actual}", 1861 {expected:expected, actual:actual}); 1862 } 1863 expose_assert(assert_greater_than_equal, "assert_greater_than_equal"); 1864 1865 /** 1866 * Assert that ``actual`` is a number greater than or equal to ``lower`` and less 1867 * than or equal to ``upper``. 1868 * 1869 * @param {number} actual - Test value. 1870 * @param {number} lower - Number that ``actual`` must be greater than or equal to. 1871 * @param {number} upper - Number that ``actual`` must be less than or equal to. 1872 * @param {string} [description] - Description of the condition being tested. 1873 */ 1874 function assert_between_inclusive(actual, lower, upper, description) 1875 { 1876 /* 1877 * Test if a primitive number is between to two others or equal to either of them 1878 */ 1879 assert(typeof actual === "number", 1880 "assert_between_inclusive", description, 1881 "expected a number but got a ${type_actual}", 1882 {type_actual:typeof actual}); 1883 1884 assert(actual >= lower && actual <= upper, 1885 "assert_between_inclusive", description, 1886 "expected a number greater than or equal to ${lower} " + 1887 "and less than or equal to ${upper} but got ${actual}", 1888 {lower:lower, upper:upper, actual:actual}); 1889 } 1890 expose_assert(assert_between_inclusive, "assert_between_inclusive"); 1891 1892 /** 1893 * Assert that ``actual`` matches the RegExp ``expected``. 1894 * 1895 * @param {String} actual - Test string. 1896 * @param {RegExp} expected - RegExp ``actual`` must match. 1897 * @param {string} [description] - Description of the condition being tested. 1898 */ 1899 function assert_regexp_match(actual, expected, description) { 1900 /* 1901 * Test if a string (actual) matches a regexp (expected) 1902 */ 1903 assert(expected.test(actual), 1904 "assert_regexp_match", description, 1905 "expected ${expected} but got ${actual}", 1906 {expected:expected, actual:actual}); 1907 } 1908 expose_assert(assert_regexp_match, "assert_regexp_match"); 1909 1910 /** 1911 * Assert that the class string of ``object`` as returned in 1912 * ``Object.prototype.toString`` is equal to ``class_name``. 1913 * 1914 * @param {Object} object - Object to stringify. 1915 * @param {string} class_string - Expected class string for ``object``. 1916 * @param {string} [description] - Description of the condition being tested. 1917 */ 1918 function assert_class_string(object, class_string, description) { 1919 var actual = {}.toString.call(object); 1920 var expected = "[object " + class_string + "]"; 1921 assert(same_value(actual, expected), "assert_class_string", description, 1922 "expected ${expected} but got ${actual}", 1923 {expected:expected, actual:actual}); 1924 } 1925 expose_assert(assert_class_string, "assert_class_string"); 1926 1927 /** 1928 * Assert that ``object`` has an own property with name ``property_name``. 1929 * 1930 * @param {Object} object - Object that should have the given property. 1931 * @param {string} property_name - Expected property name. 1932 * @param {string} [description] - Description of the condition being tested. 1933 */ 1934 function assert_own_property(object, property_name, description) { 1935 assert(object.hasOwnProperty(property_name), 1936 "assert_own_property", description, 1937 "expected property ${p} missing", {p:property_name}); 1938 } 1939 expose_assert(assert_own_property, "assert_own_property"); 1940 1941 /** 1942 * Assert that ``object`` does not have an own property with name ``property_name``. 1943 * 1944 * @param {Object} object - Object that should not have the given property. 1945 * @param {string} property_name - Property name to test. 1946 * @param {string} [description] - Description of the condition being tested. 1947 */ 1948 function assert_not_own_property(object, property_name, description) { 1949 assert(!object.hasOwnProperty(property_name), 1950 "assert_not_own_property", description, 1951 "unexpected property ${p} is found on object", {p:property_name}); 1952 } 1953 expose_assert(assert_not_own_property, "assert_not_own_property"); 1954 1955 function _assert_inherits(name) { 1956 return function (object, property_name, description) 1957 { 1958 assert((typeof object === "object" && object !== null) || 1959 typeof object === "function" || 1960 // Or has [[IsHTMLDDA]] slot 1961 String(object) === "[object HTMLAllCollection]", 1962 name, description, 1963 "provided value is not an object"); 1964 1965 assert("hasOwnProperty" in object, 1966 name, description, 1967 "provided value is an object but has no hasOwnProperty method"); 1968 1969 assert(!object.hasOwnProperty(property_name), 1970 name, description, 1971 "property ${p} found on object expected in prototype chain", 1972 {p:property_name}); 1973 1974 assert(property_name in object, 1975 name, description, 1976 "property ${p} not found in prototype chain", 1977 {p:property_name}); 1978 }; 1979 } 1980 1981 /** 1982 * Assert that ``object`` does not have an own property with name 1983 * ``property_name``, but inherits one through the prototype chain. 1984 * 1985 * @param {Object} object - Object that should have the given property in its prototype chain. 1986 * @param {string} property_name - Expected property name. 1987 * @param {string} [description] - Description of the condition being tested. 1988 */ 1989 function assert_inherits(object, property_name, description) { 1990 return _assert_inherits("assert_inherits")(object, property_name, description); 1991 } 1992 expose_assert(assert_inherits, "assert_inherits"); 1993 1994 /** 1995 * Alias for :js:func:`insert_inherits`. 1996 * 1997 * @param {Object} object - Object that should have the given property in its prototype chain. 1998 * @param {string} property_name - Expected property name. 1999 * @param {string} [description] - Description of the condition being tested. 2000 */ 2001 function assert_idl_attribute(object, property_name, description) { 2002 return _assert_inherits("assert_idl_attribute")(object, property_name, description); 2003 } 2004 expose_assert(assert_idl_attribute, "assert_idl_attribute"); 2005 2006 2007 /** 2008 * Assert that ``object`` has a property named ``property_name`` and that the property is readonly. 2009 * 2010 * Note: The implementation tries to update the named property, so 2011 * any side effects of updating will be triggered. Users are 2012 * encouraged to instead inspect the property descriptor of ``property_name`` on ``object``. 2013 * 2014 * @param {Object} object - Object that should have the given property in its prototype chain. 2015 * @param {string} property_name - Expected property name. 2016 * @param {string} [description] - Description of the condition being tested. 2017 */ 2018 function assert_readonly(object, property_name, description) 2019 { 2020 var initial_value = object[property_name]; 2021 try { 2022 //Note that this can have side effects in the case where 2023 //the property has PutForwards 2024 object[property_name] = initial_value + "a"; //XXX use some other value here? 2025 assert(same_value(object[property_name], initial_value), 2026 "assert_readonly", description, 2027 "changing property ${p} succeeded", 2028 {p:property_name}); 2029 } finally { 2030 object[property_name] = initial_value; 2031 } 2032 } 2033 expose_assert(assert_readonly, "assert_readonly"); 2034 2035 /** 2036 * Assert a JS Error with the expected constructor is thrown. 2037 * 2038 * @param {object} constructor The expected exception constructor. 2039 * @param {Function} func Function which should throw. 2040 * @param {string} [description] Error description for the case that the error is not thrown. 2041 */ 2042 function assert_throws_js(constructor, func, description) 2043 { 2044 assert_throws_js_impl(constructor, func, description, 2045 "assert_throws_js"); 2046 } 2047 expose_assert(assert_throws_js, "assert_throws_js"); 2048 2049 /** 2050 * Like assert_throws_js but allows specifying the assertion type 2051 * (assert_throws_js or promise_rejects_js, in practice). 2052 */ 2053 function assert_throws_js_impl(constructor, func, description, 2054 assertion_type) 2055 { 2056 try { 2057 func.call(this); 2058 assert(false, assertion_type, description, 2059 "${func} did not throw", {func:func}); 2060 } catch (e) { 2061 if (e instanceof AssertionError) { 2062 throw e; 2063 } 2064 2065 // Basic sanity-checks on the thrown exception. 2066 assert(typeof e === "object", 2067 assertion_type, description, 2068 "${func} threw ${e} with type ${type}, not an object", 2069 {func:func, e:e, type:typeof e}); 2070 2071 assert(e !== null, 2072 assertion_type, description, 2073 "${func} threw null, not an object", 2074 {func:func}); 2075 2076 // Basic sanity-check on the passed-in constructor 2077 assert(typeof constructor == "function", 2078 assertion_type, description, 2079 "${constructor} is not a constructor", 2080 {constructor:constructor}); 2081 var obj = constructor; 2082 while (obj) { 2083 if (typeof obj === "function" && 2084 obj.name === "Error") { 2085 break; 2086 } 2087 obj = Object.getPrototypeOf(obj); 2088 } 2089 assert(obj != null, 2090 assertion_type, description, 2091 "${constructor} is not an Error subtype", 2092 {constructor:constructor}); 2093 2094 // And checking that our exception is reasonable 2095 assert(e.constructor === constructor && 2096 e.name === constructor.name, 2097 assertion_type, description, 2098 "${func} threw ${actual} (${actual_name}) expected instance of ${expected} (${expected_name})", 2099 {func:func, actual:e, actual_name:e.name, 2100 expected:constructor, 2101 expected_name:constructor.name}); 2102 } 2103 } 2104 2105 // TODO: Figure out how to document the overloads better. 2106 // sphinx-js doesn't seem to handle @variation correctly, 2107 // and only expects a single JSDoc entry per function. 2108 /** 2109 * Assert a DOMException with the expected type is thrown. 2110 * 2111 * There are two ways of calling assert_throws_dom: 2112 * 2113 * 1) If the DOMException is expected to come from the current global, the 2114 * second argument should be the function expected to throw and a third, 2115 * optional, argument is the assertion description. 2116 * 2117 * 2) If the DOMException is expected to come from some other global, the 2118 * second argument should be the DOMException constructor from that global, 2119 * the third argument the function expected to throw, and the fourth, optional, 2120 * argument the assertion description. 2121 * 2122 * @param {number|string} type - The expected exception name or 2123 * code. See the `table of names and codes 2124 * <https://webidl.spec.whatwg.org/#dfn-error-names-table>`_. If a 2125 * number is passed it should be one of the numeric code values in 2126 * that table (e.g. 3, 4, etc). If a string is passed it can 2127 * either be an exception name (e.g. "HierarchyRequestError", 2128 * "WrongDocumentError") or the name of the corresponding error 2129 * code (e.g. "``HIERARCHY_REQUEST_ERR``", "``WRONG_DOCUMENT_ERR``"). 2130 * @param {Function} descriptionOrFunc - The function expected to 2131 * throw (if the exception comes from another global), or the 2132 * optional description of the condition being tested (if the 2133 * exception comes from the current global). 2134 * @param {string} [description] - Description of the condition 2135 * being tested (if the exception comes from another global). 2136 * 2137 */ 2138 function assert_throws_dom(type, funcOrConstructor, descriptionOrFunc, maybeDescription) 2139 { 2140 let constructor, func, description; 2141 if (funcOrConstructor.name === "DOMException") { 2142 constructor = funcOrConstructor; 2143 func = descriptionOrFunc; 2144 description = maybeDescription; 2145 } else { 2146 constructor = self.DOMException; 2147 func = funcOrConstructor; 2148 description = descriptionOrFunc; 2149 assert(maybeDescription === undefined, 2150 "Too many args pased to no-constructor version of assert_throws_dom"); 2151 } 2152 assert_throws_dom_impl(type, func, description, "assert_throws_dom", constructor) 2153 } 2154 expose_assert(assert_throws_dom, "assert_throws_dom"); 2155 2156 /** 2157 * Similar to assert_throws_dom but allows specifying the assertion type 2158 * (assert_throws_dom or promise_rejects_dom, in practice). The 2159 * "constructor" argument must be the DOMException constructor from the 2160 * global we expect the exception to come from. 2161 */ 2162 function assert_throws_dom_impl(type, func, description, assertion_type, constructor) 2163 { 2164 try { 2165 func.call(this); 2166 assert(false, assertion_type, description, 2167 "${func} did not throw", {func:func}); 2168 } catch (e) { 2169 if (e instanceof AssertionError) { 2170 throw e; 2171 } 2172 2173 // Basic sanity-checks on the thrown exception. 2174 assert(typeof e === "object", 2175 assertion_type, description, 2176 "${func} threw ${e} with type ${type}, not an object", 2177 {func:func, e:e, type:typeof e}); 2178 2179 assert(e !== null, 2180 assertion_type, description, 2181 "${func} threw null, not an object", 2182 {func:func}); 2183 2184 // Sanity-check our type 2185 assert(typeof type == "number" || 2186 typeof type == "string", 2187 assertion_type, description, 2188 "${type} is not a number or string", 2189 {type:type}); 2190 2191 var codename_name_map = { 2192 INDEX_SIZE_ERR: 'IndexSizeError', 2193 HIERARCHY_REQUEST_ERR: 'HierarchyRequestError', 2194 WRONG_DOCUMENT_ERR: 'WrongDocumentError', 2195 INVALID_CHARACTER_ERR: 'InvalidCharacterError', 2196 NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError', 2197 NOT_FOUND_ERR: 'NotFoundError', 2198 NOT_SUPPORTED_ERR: 'NotSupportedError', 2199 INUSE_ATTRIBUTE_ERR: 'InUseAttributeError', 2200 INVALID_STATE_ERR: 'InvalidStateError', 2201 SYNTAX_ERR: 'SyntaxError', 2202 INVALID_MODIFICATION_ERR: 'InvalidModificationError', 2203 NAMESPACE_ERR: 'NamespaceError', 2204 INVALID_ACCESS_ERR: 'InvalidAccessError', 2205 TYPE_MISMATCH_ERR: 'TypeMismatchError', 2206 SECURITY_ERR: 'SecurityError', 2207 NETWORK_ERR: 'NetworkError', 2208 ABORT_ERR: 'AbortError', 2209 URL_MISMATCH_ERR: 'URLMismatchError', 2210 QUOTA_EXCEEDED_ERR: 'QuotaExceededError', 2211 TIMEOUT_ERR: 'TimeoutError', 2212 INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError', 2213 DATA_CLONE_ERR: 'DataCloneError' 2214 }; 2215 2216 var name_code_map = { 2217 IndexSizeError: 1, 2218 HierarchyRequestError: 3, 2219 WrongDocumentError: 4, 2220 InvalidCharacterError: 5, 2221 NoModificationAllowedError: 7, 2222 NotFoundError: 8, 2223 NotSupportedError: 9, 2224 InUseAttributeError: 10, 2225 InvalidStateError: 11, 2226 SyntaxError: 12, 2227 InvalidModificationError: 13, 2228 NamespaceError: 14, 2229 InvalidAccessError: 15, 2230 TypeMismatchError: 17, 2231 SecurityError: 18, 2232 NetworkError: 19, 2233 AbortError: 20, 2234 URLMismatchError: 21, 2235 QuotaExceededError: 22, 2236 TimeoutError: 23, 2237 InvalidNodeTypeError: 24, 2238 DataCloneError: 25, 2239 2240 EncodingError: 0, 2241 NotReadableError: 0, 2242 UnknownError: 0, 2243 ConstraintError: 0, 2244 DataError: 0, 2245 TransactionInactiveError: 0, 2246 ReadOnlyError: 0, 2247 VersionError: 0, 2248 OperationError: 0, 2249 NotAllowedError: 0 2250 }; 2251 2252 var code_name_map = {}; 2253 for (var key in name_code_map) { 2254 if (name_code_map[key] > 0) { 2255 code_name_map[name_code_map[key]] = key; 2256 } 2257 } 2258 2259 var required_props = {}; 2260 var name; 2261 2262 if (typeof type === "number") { 2263 if (type === 0) { 2264 throw new AssertionError('Test bug: ambiguous DOMException code 0 passed to assert_throws_dom()'); 2265 } else if (!(type in code_name_map)) { 2266 throw new AssertionError('Test bug: unrecognized DOMException code "' + type + '" passed to assert_throws_dom()'); 2267 } 2268 name = code_name_map[type]; 2269 required_props.code = type; 2270 } else if (typeof type === "string") { 2271 name = type in codename_name_map ? codename_name_map[type] : type; 2272 if (!(name in name_code_map)) { 2273 throw new AssertionError('Test bug: unrecognized DOMException code name or name "' + type + '" passed to assert_throws_dom()'); 2274 } 2275 2276 required_props.code = name_code_map[name]; 2277 } 2278 2279 if (required_props.code === 0 || 2280 ("name" in e && 2281 e.name !== e.name.toUpperCase() && 2282 e.name !== "DOMException")) { 2283 // New style exception: also test the name property. 2284 required_props.name = name; 2285 } 2286 2287 for (var prop in required_props) { 2288 assert(prop in e && e[prop] == required_props[prop], 2289 assertion_type, description, 2290 "${func} threw ${e} that is not a DOMException " + type + ": property ${prop} is equal to ${actual}, expected ${expected}", 2291 {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]}); 2292 } 2293 2294 // Check that the exception is from the right global. This check is last 2295 // so more specific, and more informative, checks on the properties can 2296 // happen in case a totally incorrect exception is thrown. 2297 assert(e.constructor === constructor, 2298 assertion_type, description, 2299 "${func} threw an exception from the wrong global", 2300 {func}); 2301 2302 } 2303 } 2304 2305 /** 2306 * Assert the provided value is thrown. 2307 * 2308 * @param {value} exception The expected exception. 2309 * @param {Function} func Function which should throw. 2310 * @param {string} [description] Error description for the case that the error is not thrown. 2311 */ 2312 function assert_throws_exactly(exception, func, description) 2313 { 2314 assert_throws_exactly_impl(exception, func, description, 2315 "assert_throws_exactly"); 2316 } 2317 expose_assert(assert_throws_exactly, "assert_throws_exactly"); 2318 2319 /** 2320 * Like assert_throws_exactly but allows specifying the assertion type 2321 * (assert_throws_exactly or promise_rejects_exactly, in practice). 2322 */ 2323 function assert_throws_exactly_impl(exception, func, description, 2324 assertion_type) 2325 { 2326 try { 2327 func.call(this); 2328 assert(false, assertion_type, description, 2329 "${func} did not throw", {func:func}); 2330 } catch (e) { 2331 if (e instanceof AssertionError) { 2332 throw e; 2333 } 2334 2335 assert(same_value(e, exception), assertion_type, description, 2336 "${func} threw ${e} but we expected it to throw ${exception}", 2337 {func:func, e:e, exception:exception}); 2338 } 2339 } 2340 2341 /** 2342 * Asserts if called. Used to ensure that a specific codepath is 2343 * not taken e.g. that an error event isn't fired. 2344 * 2345 * @param {string} [description] - Description of the condition being tested. 2346 */ 2347 function assert_unreached(description) { 2348 assert(false, "assert_unreached", description, 2349 "Reached unreachable code"); 2350 } 2351 expose_assert(assert_unreached, "assert_unreached"); 2352 2353 /** 2354 * @callback AssertFunc 2355 * @param {Any} actual 2356 * @param {Any} expected 2357 * @param {Any[]} args 2358 */ 2359 2360 /** 2361 * Asserts that ``actual`` matches at least one value of ``expected`` 2362 * according to a comparison defined by ``assert_func``. 2363 * 2364 * Note that tests with multiple allowed pass conditions are bad 2365 * practice unless the spec specifically allows multiple 2366 * behaviours. Test authors should not use this method simply to 2367 * hide UA bugs. 2368 * 2369 * @param {AssertFunc} assert_func - Function to compare actual 2370 * and expected. It must throw when the comparison fails and 2371 * return when the comparison passes. 2372 * @param {Any} actual - Test value. 2373 * @param {Array} expected_array - Array of possible expected values. 2374 * @param {Any[]} args - Additional arguments to pass to ``assert_func``. 2375 */ 2376 function assert_any(assert_func, actual, expected_array, ...args) 2377 { 2378 var errors = []; 2379 var passed = false; 2380 forEach(expected_array, 2381 function(expected) 2382 { 2383 try { 2384 assert_func.apply(this, [actual, expected].concat(args)); 2385 passed = true; 2386 } catch (e) { 2387 errors.push(e.message); 2388 } 2389 }); 2390 if (!passed) { 2391 throw new AssertionError(errors.join("\n\n")); 2392 } 2393 } 2394 // FIXME: assert_any cannot use expose_assert, because assert_wrapper does 2395 // not support nested assert calls (e.g. to assert_func). We need to 2396 // support bypassing assert_wrapper for the inner asserts here. 2397 expose(assert_any, "assert_any"); 2398 2399 /** 2400 * Assert that a feature is implemented, based on a 'truthy' condition. 2401 * 2402 * This function should be used to early-exit from tests in which there is 2403 * no point continuing without support for a non-optional spec or spec 2404 * feature. For example: 2405 * 2406 * assert_implements(window.Foo, 'Foo is not supported'); 2407 * 2408 * @param {object} condition The truthy value to test 2409 * @param {string} [description] Error description for the case that the condition is not truthy. 2410 */ 2411 function assert_implements(condition, description) { 2412 assert(!!condition, "assert_implements", description); 2413 } 2414 expose_assert(assert_implements, "assert_implements") 2415 2416 /** 2417 * Assert that an optional feature is implemented, based on a 'truthy' condition. 2418 * 2419 * This function should be used to early-exit from tests in which there is 2420 * no point continuing without support for an explicitly optional spec or 2421 * spec feature. For example: 2422 * 2423 * assert_implements_optional(video.canPlayType("video/webm"), 2424 * "webm video playback not supported"); 2425 * 2426 * @param {object} condition The truthy value to test 2427 * @param {string} [description] Error description for the case that the condition is not truthy. 2428 */ 2429 function assert_implements_optional(condition, description) { 2430 if (!condition) { 2431 throw new OptionalFeatureUnsupportedError(description); 2432 } 2433 } 2434 expose_assert(assert_implements_optional, "assert_implements_optional"); 2435 2436 /** 2437 * @class 2438 * 2439 * A single subtest. A Test is not constructed directly but via the 2440 * :js:func:`test`, :js:func:`async_test` or :js:func:`promise_test` functions. 2441 * 2442 * @param {string} name - This must be unique in a given file and must be 2443 * invariant between runs. 2444 * 2445 */ 2446 function Test(name, properties) 2447 { 2448 if (tests.file_is_test && tests.tests.length) { 2449 throw new Error("Tried to create a test with file_is_test"); 2450 } 2451 /** The test name. */ 2452 this.name = name; 2453 2454 this.phase = (tests.is_aborted || tests.phase === tests.phases.COMPLETE) ? 2455 this.phases.COMPLETE : this.phases.INITIAL; 2456 2457 /** The test status code.*/ 2458 this.status = this.NOTRUN; 2459 this.timeout_id = null; 2460 this.index = null; 2461 2462 this.properties = properties || {}; 2463 this.timeout_length = settings.test_timeout; 2464 if (this.timeout_length !== null) { 2465 this.timeout_length *= tests.timeout_multiplier; 2466 } 2467 2468 /** A message indicating the reason for test failure. */ 2469 this.message = null; 2470 /** Stack trace in case of failure. */ 2471 this.stack = null; 2472 2473 this.steps = []; 2474 this._is_promise_test = false; 2475 2476 this.cleanup_callbacks = []; 2477 this._user_defined_cleanup_count = 0; 2478 this._done_callbacks = []; 2479 2480 // Tests declared following harness completion are likely an indication 2481 // of a programming error, but they cannot be reported 2482 // deterministically. 2483 if (tests.phase === tests.phases.COMPLETE) { 2484 return; 2485 } 2486 2487 tests.push(this); 2488 } 2489 2490 /** 2491 * Enum of possible test statuses. 2492 * 2493 * :values: 2494 * - ``PASS`` 2495 * - ``FAIL`` 2496 * - ``TIMEOUT`` 2497 * - ``NOTRUN`` 2498 * - ``PRECONDITION_FAILED`` 2499 */ 2500 Test.statuses = { 2501 PASS:0, 2502 FAIL:1, 2503 TIMEOUT:2, 2504 NOTRUN:3, 2505 PRECONDITION_FAILED:4 2506 }; 2507 2508 Test.prototype = merge({}, Test.statuses); 2509 2510 Test.prototype.phases = { 2511 INITIAL:0, 2512 STARTED:1, 2513 HAS_RESULT:2, 2514 CLEANING:3, 2515 COMPLETE:4 2516 }; 2517 2518 Test.prototype.status_formats = { 2519 0: "Pass", 2520 1: "Fail", 2521 2: "Timeout", 2522 3: "Not Run", 2523 4: "Optional Feature Unsupported", 2524 } 2525 2526 Test.prototype.format_status = function() { 2527 return this.status_formats[this.status]; 2528 } 2529 2530 Test.prototype.structured_clone = function() 2531 { 2532 if (!this._structured_clone) { 2533 var msg = this.message; 2534 msg = msg ? String(msg) : msg; 2535 this._structured_clone = merge({ 2536 name:String(this.name), 2537 properties:merge({}, this.properties), 2538 phases:merge({}, this.phases) 2539 }, Test.statuses); 2540 } 2541 this._structured_clone.status = this.status; 2542 this._structured_clone.message = this.message; 2543 this._structured_clone.stack = this.stack; 2544 this._structured_clone.index = this.index; 2545 this._structured_clone.phase = this.phase; 2546 return this._structured_clone; 2547 }; 2548 2549 /** 2550 * Run a single step of an ongoing test. 2551 * 2552 * @param {string} func - Callback function to run as a step. If 2553 * this throws an :js:func:`AssertionError`, or any other 2554 * exception, the :js:class:`Test` status is set to ``FAIL``. 2555 * @param {Object} [this_obj] - The object to use as the this 2556 * value when calling ``func``. Defaults to the :js:class:`Test` object. 2557 */ 2558 Test.prototype.step = function(func, this_obj) 2559 { 2560 if (this.phase > this.phases.STARTED) { 2561 return; 2562 } 2563 2564 if (settings.debug && this.phase !== this.phases.STARTED) { 2565 console.log("TEST START", this.name); 2566 } 2567 this.phase = this.phases.STARTED; 2568 //If we don't get a result before the harness times out that will be a test timeout 2569 this.set_status(this.TIMEOUT, "Test timed out"); 2570 2571 tests.started = true; 2572 tests.current_test = this; 2573 tests.notify_test_state(this); 2574 2575 if (this.timeout_id === null) { 2576 this.set_timeout(); 2577 } 2578 2579 this.steps.push(func); 2580 2581 if (arguments.length === 1) { 2582 this_obj = this; 2583 } 2584 2585 if (settings.debug) { 2586 console.debug("TEST STEP", this.name); 2587 } 2588 2589 try { 2590 return func.apply(this_obj, Array.prototype.slice.call(arguments, 2)); 2591 } catch (e) { 2592 if (this.phase >= this.phases.HAS_RESULT) { 2593 return; 2594 } 2595 var status = e instanceof OptionalFeatureUnsupportedError ? this.PRECONDITION_FAILED : this.FAIL; 2596 var message = String((typeof e === "object" && e !== null) ? e.message : e); 2597 var stack = e.stack ? e.stack : null; 2598 2599 this.set_status(status, message, stack); 2600 this.phase = this.phases.HAS_RESULT; 2601 this.done(); 2602 } finally { 2603 this.current_test = null; 2604 } 2605 }; 2606 2607 /** 2608 * Wrap a function so that it runs as a step of the current test. 2609 * 2610 * This allows creating a callback function that will run as a 2611 * test step. 2612 * 2613 * @example 2614 * let t = async_test("Example"); 2615 * onload = t.step_func(e => { 2616 * assert_equals(e.name, "load"); 2617 * // Mark the test as complete. 2618 * t.done(); 2619 * }) 2620 * 2621 * @param {string} func - Function to run as a step. If this 2622 * throws an :js:func:`AssertionError`, or any other exception, 2623 * the :js:class:`Test` status is set to ``FAIL``. 2624 * @param {Object} [this_obj] - The object to use as the this 2625 * value when calling ``func``. Defaults to the :js:class:`Test` object. 2626 */ 2627 Test.prototype.step_func = function(func, this_obj) 2628 { 2629 var test_this = this; 2630 2631 if (arguments.length === 1) { 2632 this_obj = test_this; 2633 } 2634 2635 return function() 2636 { 2637 return test_this.step.apply(test_this, [func, this_obj].concat( 2638 Array.prototype.slice.call(arguments))); 2639 }; 2640 }; 2641 2642 /** 2643 * Wrap a function so that it runs as a step of the current test, 2644 * and automatically marks the test as complete if the function 2645 * returns without error. 2646 * 2647 * @param {string} func - Function to run as a step. If this 2648 * throws an :js:func:`AssertionError`, or any other exception, 2649 * the :js:class:`Test` status is set to ``FAIL``. If it returns 2650 * without error the status is set to ``PASS``. 2651 * @param {Object} [this_obj] - The object to use as the this 2652 * value when calling `func`. Defaults to the :js:class:`Test` object. 2653 */ 2654 Test.prototype.step_func_done = function(func, this_obj) 2655 { 2656 var test_this = this; 2657 2658 if (arguments.length === 1) { 2659 this_obj = test_this; 2660 } 2661 2662 return function() 2663 { 2664 if (func) { 2665 test_this.step.apply(test_this, [func, this_obj].concat( 2666 Array.prototype.slice.call(arguments))); 2667 } 2668 test_this.done(); 2669 }; 2670 }; 2671 2672 /** 2673 * Return a function that automatically sets the current test to 2674 * ``FAIL`` if it's called. 2675 * 2676 * @param {string} [description] - Error message to add to assert 2677 * in case of failure. 2678 * 2679 */ 2680 Test.prototype.unreached_func = function(description) 2681 { 2682 return this.step_func(function() { 2683 assert_unreached(description); 2684 }); 2685 }; 2686 2687 /** 2688 * Run a function as a step of the test after a given timeout. 2689 * 2690 * This multiplies the timeout by the global timeout multiplier to 2691 * account for the expected execution speed of the current test 2692 * environment. For example ``test.step_timeout(f, 2000)`` with a 2693 * timeout multiplier of 2 will wait for 4000ms before calling ``f``. 2694 * 2695 * In general it's encouraged to use :js:func:`Test.step_wait` or 2696 * :js:func:`step_wait_func` in preference to this function where possible, 2697 * as they provide better test performance. 2698 * 2699 * @param {Function} func - Function to run as a test 2700 * step. 2701 * @param {number} timeout - Time in ms to wait before running the 2702 * test step. The actual wait time is ``timeout`` x 2703 * ``timeout_multiplier``. 2704 * 2705 */ 2706 Test.prototype.step_timeout = function(func, timeout) { 2707 var test_this = this; 2708 var args = Array.prototype.slice.call(arguments, 2); 2709 return setTimeout(this.step_func(function() { 2710 return func.apply(test_this, args); 2711 }), timeout * tests.timeout_multiplier); 2712 }; 2713 2714 /** 2715 * Poll for a function to return true, and call a callback 2716 * function once it does, or assert if a timeout is 2717 * reached. This is preferred over a simple step_timeout 2718 * whenever possible since it allows the timeout to be longer 2719 * to reduce intermittents without compromising test execution 2720 * speed when the condition is quickly met. 2721 * 2722 * @param {Function} cond A function taking no arguments and 2723 * returning a boolean. The callback is called 2724 * when this function returns true. 2725 * @param {Function} func A function taking no arguments to call once 2726 * the condition is met. 2727 * @param {string} [description] Error message to add to assert in case of 2728 * failure. 2729 * @param {number} timeout Timeout in ms. This is multiplied by the global 2730 * timeout_multiplier 2731 * @param {number} interval Polling interval in ms 2732 * 2733 */ 2734 Test.prototype.step_wait_func = function(cond, func, description, 2735 timeout=3000, interval=100) { 2736 var timeout_full = timeout * tests.timeout_multiplier; 2737 var remaining = Math.ceil(timeout_full / interval); 2738 var test_this = this; 2739 2740 var wait_for_inner = test_this.step_func(() => { 2741 if (cond()) { 2742 func(); 2743 } else { 2744 if(remaining === 0) { 2745 assert(false, "step_wait_func", description, 2746 "Timed out waiting on condition"); 2747 } 2748 remaining--; 2749 setTimeout(wait_for_inner, interval); 2750 } 2751 }); 2752 2753 wait_for_inner(); 2754 }; 2755 2756 /** 2757 * Poll for a function to return true, and invoke a callback 2758 * followed by this.done() once it does, or assert if a timeout 2759 * is reached. This is preferred over a simple step_timeout 2760 * whenever possible since it allows the timeout to be longer 2761 * to reduce intermittents without compromising test execution speed 2762 * when the condition is quickly met. 2763 * 2764 * @example 2765 * async_test(t => { 2766 * const popup = window.open("resources/coop-coep.py?coop=same-origin&coep=&navigate=about:blank"); 2767 * t.add_cleanup(() => popup.close()); 2768 * assert_equals(window, popup.opener); 2769 * 2770 * popup.onload = t.step_func(() => { 2771 * assert_true(popup.location.href.endsWith("&navigate=about:blank")); 2772 * // Use step_wait_func_done as about:blank cannot message back. 2773 * t.step_wait_func_done(() => popup.location.href === "about:blank"); 2774 * }); 2775 * }, "Navigating a popup to about:blank"); 2776 * 2777 * @param {Function} cond A function taking no arguments and 2778 * returning a boolean. The callback is called 2779 * when this function returns true. 2780 * @param {Function} func A function taking no arguments to call once 2781 * the condition is met. 2782 * @param {string} [description] Error message to add to assert in case of 2783 * failure. 2784 * @param {number} timeout Timeout in ms. This is multiplied by the global 2785 * timeout_multiplier 2786 * @param {number} interval Polling interval in ms 2787 * 2788 */ 2789 Test.prototype.step_wait_func_done = function(cond, func, description, 2790 timeout=3000, interval=100) { 2791 this.step_wait_func(cond, () => { 2792 if (func) { 2793 func(); 2794 } 2795 this.done(); 2796 }, description, timeout, interval); 2797 }; 2798 2799 /** 2800 * Poll for a function to return true, and resolve a promise 2801 * once it does, or assert if a timeout is reached. This is 2802 * preferred over a simple step_timeout whenever possible 2803 * since it allows the timeout to be longer to reduce 2804 * intermittents without compromising test execution speed 2805 * when the condition is quickly met. 2806 * 2807 * @example 2808 * promise_test(async t => { 2809 * // … 2810 * await t.step_wait(() => frame.contentDocument === null, "Frame navigated to a cross-origin document"); 2811 * // … 2812 * }, ""); 2813 * 2814 * @param {Function} cond A function taking no arguments and 2815 * returning a boolean. 2816 * @param {string} [description] Error message to add to assert in case of 2817 * failure. 2818 * @param {number} timeout Timeout in ms. This is multiplied by the global 2819 * timeout_multiplier 2820 * @param {number} interval Polling interval in ms 2821 * @returns {Promise} Promise resolved once cond is met. 2822 * 2823 */ 2824 Test.prototype.step_wait = function(cond, description, timeout=3000, interval=100) { 2825 return new Promise(resolve => { 2826 this.step_wait_func(cond, resolve, description, timeout, interval); 2827 }); 2828 } 2829 2830 /* 2831 * Private method for registering cleanup functions. `testharness.js` 2832 * internals should use this method instead of the public `add_cleanup` 2833 * method in order to hide implementation details from the harness status 2834 * message in the case errors. 2835 */ 2836 Test.prototype._add_cleanup = function(callback) { 2837 this.cleanup_callbacks.push(callback); 2838 }; 2839 2840 /** 2841 * Schedule a function to be run after the test result is known, regardless 2842 * of passing or failing state. 2843 * 2844 * The behavior of this function will not 2845 * influence the result of the test, but if an exception is thrown, the 2846 * test harness will report an error. 2847 * 2848 * @param {Function} callback - The cleanup function to run. This 2849 * is called with no arguments. 2850 */ 2851 Test.prototype.add_cleanup = function(callback) { 2852 this._user_defined_cleanup_count += 1; 2853 this._add_cleanup(callback); 2854 }; 2855 2856 Test.prototype.set_timeout = function() 2857 { 2858 if (this.timeout_length !== null) { 2859 var this_obj = this; 2860 this.timeout_id = setTimeout(function() 2861 { 2862 this_obj.timeout(); 2863 }, this.timeout_length); 2864 } 2865 }; 2866 2867 Test.prototype.set_status = function(status, message, stack) 2868 { 2869 this.status = status; 2870 this.message = message; 2871 this.stack = stack ? stack : null; 2872 }; 2873 2874 /** 2875 * Manually set the test status to ``TIMEOUT``. 2876 */ 2877 Test.prototype.timeout = function() 2878 { 2879 this.timeout_id = null; 2880 this.set_status(this.TIMEOUT, "Test timed out"); 2881 this.phase = this.phases.HAS_RESULT; 2882 this.done(); 2883 }; 2884 2885 /** 2886 * Manually set the test status to ``TIMEOUT``. 2887 * 2888 * Alias for `Test.timeout <#Test.timeout>`_. 2889 */ 2890 Test.prototype.force_timeout = function() { 2891 return this.timeout(); 2892 }; 2893 2894 /** 2895 * Mark the test as complete. 2896 * 2897 * This sets the test status to ``PASS`` if no other status was 2898 * already recorded. Any subsequent attempts to run additional 2899 * test steps will be ignored. 2900 * 2901 * After setting the test status any test cleanup functions will 2902 * be run. 2903 */ 2904 Test.prototype.done = function() 2905 { 2906 if (this.phase >= this.phases.CLEANING) { 2907 return; 2908 } 2909 2910 if (this.phase <= this.phases.STARTED) { 2911 this.set_status(this.PASS, null); 2912 } 2913 2914 if (global_scope.clearTimeout) { 2915 clearTimeout(this.timeout_id); 2916 } 2917 2918 if (settings.debug) { 2919 console.log("TEST DONE", 2920 this.status, 2921 this.name); 2922 } 2923 2924 this.cleanup(); 2925 }; 2926 2927 function add_test_done_callback(test, callback) 2928 { 2929 if (test.phase === test.phases.COMPLETE) { 2930 callback(); 2931 return; 2932 } 2933 2934 test._done_callbacks.push(callback); 2935 } 2936 2937 /* 2938 * Invoke all specified cleanup functions. If one or more produce an error, 2939 * the context is in an unpredictable state, so all further testing should 2940 * be cancelled. 2941 */ 2942 Test.prototype.cleanup = function() { 2943 var errors = []; 2944 var bad_value_count = 0; 2945 function on_error(e) { 2946 errors.push(e); 2947 // Abort tests immediately so that tests declared within subsequent 2948 // cleanup functions are not run. 2949 tests.abort(); 2950 } 2951 var this_obj = this; 2952 var results = []; 2953 2954 this.phase = this.phases.CLEANING; 2955 2956 forEach(this.cleanup_callbacks, 2957 function(cleanup_callback) { 2958 var result; 2959 2960 try { 2961 result = cleanup_callback(); 2962 } catch (e) { 2963 on_error(e); 2964 return; 2965 } 2966 2967 if (!is_valid_cleanup_result(this_obj, result)) { 2968 bad_value_count += 1; 2969 // Abort tests immediately so that tests declared 2970 // within subsequent cleanup functions are not run. 2971 tests.abort(); 2972 } 2973 2974 results.push(result); 2975 }); 2976 2977 if (!this._is_promise_test) { 2978 cleanup_done(this_obj, errors, bad_value_count); 2979 } else { 2980 all_async(results, 2981 function(result, done) { 2982 if (result && typeof result.then === "function") { 2983 result 2984 .then(null, on_error) 2985 .then(done); 2986 } else { 2987 done(); 2988 } 2989 }, 2990 function() { 2991 cleanup_done(this_obj, errors, bad_value_count); 2992 }); 2993 } 2994 }; 2995 2996 /* 2997 * Determine if the return value of a cleanup function is valid for a given 2998 * test. Any test may return the value `undefined`. Tests created with 2999 * `promise_test` may alternatively return "thenable" object values. 3000 */ 3001 function is_valid_cleanup_result(test, result) { 3002 if (result === undefined) { 3003 return true; 3004 } 3005 3006 if (test._is_promise_test) { 3007 return result && typeof result.then === "function"; 3008 } 3009 3010 return false; 3011 } 3012 3013 function cleanup_done(test, errors, bad_value_count) { 3014 if (errors.length || bad_value_count) { 3015 var total = test._user_defined_cleanup_count; 3016 3017 tests.status.status = tests.status.ERROR; 3018 tests.status.stack = null; 3019 tests.status.message = "Test named '" + test.name + 3020 "' specified " + total + 3021 " 'cleanup' function" + (total > 1 ? "s" : ""); 3022 3023 if (errors.length) { 3024 tests.status.message += ", and " + errors.length + " failed"; 3025 tests.status.stack = ((typeof errors[0] === "object" && 3026 errors[0].hasOwnProperty("stack")) ? 3027 errors[0].stack : null); 3028 } 3029 3030 if (bad_value_count) { 3031 var type = test._is_promise_test ? 3032 "non-thenable" : "non-undefined"; 3033 tests.status.message += ", and " + bad_value_count + 3034 " returned a " + type + " value"; 3035 } 3036 3037 tests.status.message += "."; 3038 } 3039 3040 test.phase = test.phases.COMPLETE; 3041 tests.result(test); 3042 forEach(test._done_callbacks, 3043 function(callback) { 3044 callback(); 3045 }); 3046 test._done_callbacks.length = 0; 3047 } 3048 3049 /** 3050 * A RemoteTest object mirrors a Test object on a remote worker. The 3051 * associated RemoteWorker updates the RemoteTest object in response to 3052 * received events. In turn, the RemoteTest object replicates these events 3053 * on the local document. This allows listeners (test result reporting 3054 * etc..) to transparently handle local and remote events. 3055 */ 3056 function RemoteTest(clone) { 3057 var this_obj = this; 3058 Object.keys(clone).forEach( 3059 function(key) { 3060 this_obj[key] = clone[key]; 3061 }); 3062 this.index = null; 3063 this.phase = this.phases.INITIAL; 3064 this.update_state_from(clone); 3065 this._done_callbacks = []; 3066 tests.push(this); 3067 } 3068 3069 RemoteTest.prototype.structured_clone = function() { 3070 var clone = {}; 3071 Object.keys(this).forEach( 3072 (function(key) { 3073 var value = this[key]; 3074 // `RemoteTest` instances are responsible for managing 3075 // their own "done" callback functions, so those functions 3076 // are not relevant in other execution contexts. Because of 3077 // this (and because Function values cannot be serialized 3078 // for cross-realm transmittance), the property should not 3079 // be considered when cloning instances. 3080 if (key === '_done_callbacks' ) { 3081 return; 3082 } 3083 3084 if (typeof value === "object" && value !== null) { 3085 clone[key] = merge({}, value); 3086 } else { 3087 clone[key] = value; 3088 } 3089 }).bind(this)); 3090 clone.phases = merge({}, this.phases); 3091 return clone; 3092 }; 3093 3094 /** 3095 * `RemoteTest` instances are objects which represent tests running in 3096 * another realm. They do not define "cleanup" functions (if necessary, 3097 * such functions are defined on the associated `Test` instance within the 3098 * external realm). However, `RemoteTests` may have "done" callbacks (e.g. 3099 * as attached by the `Tests` instance responsible for tracking the overall 3100 * test status in the parent realm). The `cleanup` method delegates to 3101 * `done` in order to ensure that such callbacks are invoked following the 3102 * completion of the `RemoteTest`. 3103 */ 3104 RemoteTest.prototype.cleanup = function() { 3105 this.done(); 3106 }; 3107 RemoteTest.prototype.phases = Test.prototype.phases; 3108 RemoteTest.prototype.update_state_from = function(clone) { 3109 this.status = clone.status; 3110 this.message = clone.message; 3111 this.stack = clone.stack; 3112 if (this.phase === this.phases.INITIAL) { 3113 this.phase = this.phases.STARTED; 3114 } 3115 }; 3116 RemoteTest.prototype.done = function() { 3117 this.phase = this.phases.COMPLETE; 3118 3119 forEach(this._done_callbacks, 3120 function(callback) { 3121 callback(); 3122 }); 3123 } 3124 3125 RemoteTest.prototype.format_status = function() { 3126 return Test.prototype.status_formats[this.status]; 3127 } 3128 3129 /* 3130 * A RemoteContext listens for test events from a remote test context, such 3131 * as another window or a worker. These events are then used to construct 3132 * and maintain RemoteTest objects that mirror the tests running in the 3133 * remote context. 3134 * 3135 * An optional third parameter can be used as a predicate to filter incoming 3136 * MessageEvents. 3137 */ 3138 function RemoteContext(remote, message_target, message_filter) { 3139 this.running = true; 3140 this.started = false; 3141 this.tests = new Array(); 3142 this.early_exception = null; 3143 3144 var this_obj = this; 3145 // If remote context is cross origin assigning to onerror is not 3146 // possible, so silently catch those errors. 3147 try { 3148 remote.onerror = function(error) { this_obj.remote_error(error); }; 3149 } catch (e) { 3150 // Ignore. 3151 } 3152 3153 // Keeping a reference to the remote object and the message handler until 3154 // remote_done() is seen prevents the remote object and its message channel 3155 // from going away before all the messages are dispatched. 3156 this.remote = remote; 3157 this.message_target = message_target; 3158 this.message_handler = function(message) { 3159 var passesFilter = !message_filter || message_filter(message); 3160 // The reference to the `running` property in the following 3161 // condition is unnecessary because that value is only set to 3162 // `false` after the `message_handler` function has been 3163 // unsubscribed. 3164 // TODO: Simplify the condition by removing the reference. 3165 if (this_obj.running && message.data && passesFilter && 3166 (message.data.type in this_obj.message_handlers)) { 3167 this_obj.message_handlers[message.data.type].call(this_obj, message.data); 3168 } 3169 }; 3170 3171 if (self.Promise) { 3172 this.done = new Promise(function(resolve) { 3173 this_obj.doneResolve = resolve; 3174 }); 3175 } 3176 3177 this.message_target.addEventListener("message", this.message_handler); 3178 } 3179 3180 RemoteContext.prototype.remote_error = function(error) { 3181 if (error.preventDefault) { 3182 error.preventDefault(); 3183 } 3184 3185 // Defer interpretation of errors until the testing protocol has 3186 // started and the remote test's `allow_uncaught_exception` property 3187 // is available. 3188 if (!this.started) { 3189 this.early_exception = error; 3190 } else if (!this.allow_uncaught_exception) { 3191 this.report_uncaught(error); 3192 } 3193 }; 3194 3195 RemoteContext.prototype.report_uncaught = function(error) { 3196 var message = error.message || String(error); 3197 var filename = (error.filename ? " " + error.filename: ""); 3198 // FIXME: Display remote error states separately from main document 3199 // error state. 3200 tests.set_status(tests.status.ERROR, 3201 "Error in remote" + filename + ": " + message, 3202 error.stack); 3203 }; 3204 3205 RemoteContext.prototype.start = function(data) { 3206 this.started = true; 3207 this.allow_uncaught_exception = data.properties.allow_uncaught_exception; 3208 3209 if (this.early_exception && !this.allow_uncaught_exception) { 3210 this.report_uncaught(this.early_exception); 3211 } 3212 }; 3213 3214 RemoteContext.prototype.test_state = function(data) { 3215 var remote_test = this.tests[data.test.index]; 3216 if (!remote_test) { 3217 remote_test = new RemoteTest(data.test); 3218 this.tests[data.test.index] = remote_test; 3219 } 3220 remote_test.update_state_from(data.test); 3221 tests.notify_test_state(remote_test); 3222 }; 3223 3224 RemoteContext.prototype.test_done = function(data) { 3225 var remote_test = this.tests[data.test.index]; 3226 remote_test.update_state_from(data.test); 3227 remote_test.done(); 3228 tests.result(remote_test); 3229 }; 3230 3231 RemoteContext.prototype.remote_done = function(data) { 3232 if (tests.status.status === null && 3233 data.status.status !== data.status.OK) { 3234 tests.set_status(data.status.status, data.status.message, data.status.stack); 3235 } 3236 3237 for (let assert of data.asserts) { 3238 var record = new AssertRecord(); 3239 record.assert_name = assert.assert_name; 3240 record.args = assert.args; 3241 record.test = assert.test != null ? this.tests[assert.test.index] : null; 3242 record.status = assert.status; 3243 record.stack = assert.stack; 3244 tests.asserts_run.push(record); 3245 } 3246 3247 this.message_target.removeEventListener("message", this.message_handler); 3248 this.running = false; 3249 3250 // If remote context is cross origin assigning to onerror is not 3251 // possible, so silently catch those errors. 3252 try { 3253 this.remote.onerror = null; 3254 } catch (e) { 3255 // Ignore. 3256 } 3257 3258 this.remote = null; 3259 this.message_target = null; 3260 if (this.doneResolve) { 3261 this.doneResolve(); 3262 } 3263 3264 if (tests.all_done()) { 3265 tests.complete(); 3266 } 3267 }; 3268 3269 RemoteContext.prototype.message_handlers = { 3270 start: RemoteContext.prototype.start, 3271 test_state: RemoteContext.prototype.test_state, 3272 result: RemoteContext.prototype.test_done, 3273 complete: RemoteContext.prototype.remote_done 3274 }; 3275 3276 /** 3277 * @class 3278 * Status of the overall harness 3279 */ 3280 function TestsStatus() 3281 { 3282 /** The status code */ 3283 this.status = null; 3284 /** Message in case of failure */ 3285 this.message = null; 3286 /** Stack trace in case of an exception. */ 3287 this.stack = null; 3288 } 3289 3290 /** 3291 * Enum of possible harness statuses. 3292 * 3293 * :values: 3294 * - ``OK`` 3295 * - ``ERROR`` 3296 * - ``TIMEOUT`` 3297 * - ``PRECONDITION_FAILED`` 3298 */ 3299 TestsStatus.statuses = { 3300 OK:0, 3301 ERROR:1, 3302 TIMEOUT:2, 3303 PRECONDITION_FAILED:3 3304 }; 3305 3306 TestsStatus.prototype = merge({}, TestsStatus.statuses); 3307 3308 TestsStatus.prototype.formats = { 3309 0: "OK", 3310 1: "Error", 3311 2: "Timeout", 3312 3: "Optional Feature Unsupported" 3313 }; 3314 3315 TestsStatus.prototype.structured_clone = function() 3316 { 3317 if (!this._structured_clone) { 3318 var msg = this.message; 3319 msg = msg ? String(msg) : msg; 3320 this._structured_clone = merge({ 3321 status:this.status, 3322 message:msg, 3323 stack:this.stack 3324 }, TestsStatus.statuses); 3325 } 3326 return this._structured_clone; 3327 }; 3328 3329 TestsStatus.prototype.format_status = function() { 3330 return this.formats[this.status]; 3331 }; 3332 3333 /** 3334 * @class 3335 * Record of an assert that ran. 3336 * 3337 * @param {Test} test - The test which ran the assert. 3338 * @param {string} assert_name - The function name of the assert. 3339 * @param {Any} args - The arguments passed to the assert function. 3340 */ 3341 function AssertRecord(test, assert_name, args = []) { 3342 /** Name of the assert that ran */ 3343 this.assert_name = assert_name; 3344 /** Test that ran the assert */ 3345 this.test = test; 3346 // Avoid keeping complex objects alive 3347 /** Stringification of the arguments that were passed to the assert function */ 3348 this.args = args.map(x => format_value(x).replace(/\n/g, " ")); 3349 /** Status of the assert */ 3350 this.status = null; 3351 } 3352 3353 AssertRecord.prototype.structured_clone = function() { 3354 return { 3355 assert_name: this.assert_name, 3356 test: this.test ? this.test.structured_clone() : null, 3357 args: this.args, 3358 status: this.status, 3359 }; 3360 }; 3361 3362 function Tests() 3363 { 3364 this.tests = []; 3365 this.num_pending = 0; 3366 3367 this.phases = { 3368 INITIAL:0, 3369 SETUP:1, 3370 HAVE_TESTS:2, 3371 HAVE_RESULTS:3, 3372 COMPLETE:4 3373 }; 3374 this.phase = this.phases.INITIAL; 3375 3376 this.properties = {}; 3377 3378 this.wait_for_finish = false; 3379 this.processing_callbacks = false; 3380 3381 this.allow_uncaught_exception = false; 3382 3383 this.file_is_test = false; 3384 // This value is lazily initialized in order to avoid introducing a 3385 // dependency on ECMAScript 2015 Promises to all tests. 3386 this.promise_tests = null; 3387 this.promise_setup_called = false; 3388 3389 this.timeout_multiplier = 1; 3390 this.timeout_length = test_environment.test_timeout(); 3391 this.timeout_id = null; 3392 3393 this.start_callbacks = []; 3394 this.test_state_callbacks = []; 3395 this.test_done_callbacks = []; 3396 this.all_done_callbacks = []; 3397 3398 this.hide_test_state = false; 3399 this.pending_remotes = []; 3400 3401 this.current_test = null; 3402 this.asserts_run = []; 3403 3404 // Track whether output is enabled, and thus whether or not we should 3405 // track asserts. 3406 // 3407 // On workers we don't get properties set from testharnessreport.js, so 3408 // we don't know whether or not to track asserts. To avoid the 3409 // resulting performance hit, we assume we are not meant to. This means 3410 // that assert tracking does not function on workers. 3411 this.output = settings.output && 'document' in global_scope; 3412 3413 this.status = new TestsStatus(); 3414 3415 var this_obj = this; 3416 3417 test_environment.add_on_loaded_callback(function() { 3418 if (this_obj.all_done()) { 3419 this_obj.complete(); 3420 } 3421 }); 3422 3423 this.set_timeout(); 3424 } 3425 3426 Tests.prototype.setup = function(func, properties) 3427 { 3428 if (this.phase >= this.phases.HAVE_RESULTS) { 3429 return; 3430 } 3431 3432 if (this.phase < this.phases.SETUP) { 3433 this.phase = this.phases.SETUP; 3434 } 3435 3436 this.properties = properties; 3437 3438 for (var p in properties) { 3439 if (properties.hasOwnProperty(p)) { 3440 var value = properties[p]; 3441 if (p == "allow_uncaught_exception") { 3442 this.allow_uncaught_exception = value; 3443 } else if (p == "explicit_done" && value) { 3444 this.wait_for_finish = true; 3445 } else if (p == "explicit_timeout" && value) { 3446 this.timeout_length = null; 3447 if (this.timeout_id) 3448 { 3449 clearTimeout(this.timeout_id); 3450 } 3451 } else if (p == "single_test" && value) { 3452 this.set_file_is_test(); 3453 } else if (p == "timeout_multiplier") { 3454 this.timeout_multiplier = value; 3455 if (this.timeout_length) { 3456 this.timeout_length *= this.timeout_multiplier; 3457 } 3458 } else if (p == "hide_test_state") { 3459 this.hide_test_state = value; 3460 } else if (p == "output") { 3461 this.output = value; 3462 } else if (p === "debug") { 3463 settings.debug = value; 3464 } 3465 } 3466 } 3467 3468 if (func) { 3469 try { 3470 func(); 3471 } catch (e) { 3472 this.status.status = e instanceof OptionalFeatureUnsupportedError ? this.status.PRECONDITION_FAILED : this.status.ERROR; 3473 this.status.message = String(e); 3474 this.status.stack = e.stack ? e.stack : null; 3475 this.complete(); 3476 } 3477 } 3478 this.set_timeout(); 3479 }; 3480 3481 Tests.prototype.set_file_is_test = function() { 3482 if (this.tests.length > 0) { 3483 throw new Error("Tried to set file as test after creating a test"); 3484 } 3485 this.wait_for_finish = true; 3486 this.file_is_test = true; 3487 // Create the test, which will add it to the list of tests 3488 tests.current_test = async_test(); 3489 }; 3490 3491 Tests.prototype.set_status = function(status, message, stack) 3492 { 3493 this.status.status = status; 3494 this.status.message = message; 3495 this.status.stack = stack ? stack : null; 3496 }; 3497 3498 Tests.prototype.set_timeout = function() { 3499 if (global_scope.clearTimeout) { 3500 var this_obj = this; 3501 clearTimeout(this.timeout_id); 3502 if (this.timeout_length !== null) { 3503 this.timeout_id = setTimeout(function() { 3504 this_obj.timeout(); 3505 }, this.timeout_length); 3506 } 3507 } 3508 }; 3509 3510 Tests.prototype.timeout = function() { 3511 var test_in_cleanup = null; 3512 3513 if (this.status.status === null) { 3514 forEach(this.tests, 3515 function(test) { 3516 // No more than one test is expected to be in the 3517 // "CLEANUP" phase at any time 3518 if (test.phase === test.phases.CLEANING) { 3519 test_in_cleanup = test; 3520 } 3521 3522 test.phase = test.phases.COMPLETE; 3523 }); 3524 3525 // Timeouts that occur while a test is in the "cleanup" phase 3526 // indicate that some global state was not properly reverted. This 3527 // invalidates the overall test execution, so the timeout should be 3528 // reported as an error and cancel the execution of any remaining 3529 // tests. 3530 if (test_in_cleanup) { 3531 this.status.status = this.status.ERROR; 3532 this.status.message = "Timeout while running cleanup for " + 3533 "test named \"" + test_in_cleanup.name + "\"."; 3534 tests.status.stack = null; 3535 } else { 3536 this.status.status = this.status.TIMEOUT; 3537 } 3538 } 3539 3540 this.complete(); 3541 }; 3542 3543 Tests.prototype.end_wait = function() 3544 { 3545 this.wait_for_finish = false; 3546 if (this.all_done()) { 3547 this.complete(); 3548 } 3549 }; 3550 3551 Tests.prototype.push = function(test) 3552 { 3553 if (this.phase < this.phases.HAVE_TESTS) { 3554 this.start(); 3555 } 3556 this.num_pending++; 3557 test.index = this.tests.push(test); 3558 this.notify_test_state(test); 3559 }; 3560 3561 Tests.prototype.notify_test_state = function(test) { 3562 var this_obj = this; 3563 forEach(this.test_state_callbacks, 3564 function(callback) { 3565 callback(test, this_obj); 3566 }); 3567 }; 3568 3569 Tests.prototype.all_done = function() { 3570 return (this.tests.length > 0 || this.pending_remotes.length > 0) && 3571 test_environment.all_loaded && 3572 (this.num_pending === 0 || this.is_aborted) && !this.wait_for_finish && 3573 !this.processing_callbacks && 3574 !this.pending_remotes.some(function(w) { return w.running; }); 3575 }; 3576 3577 Tests.prototype.start = function() { 3578 this.phase = this.phases.HAVE_TESTS; 3579 this.notify_start(); 3580 }; 3581 3582 Tests.prototype.notify_start = function() { 3583 var this_obj = this; 3584 forEach (this.start_callbacks, 3585 function(callback) 3586 { 3587 callback(this_obj.properties); 3588 }); 3589 }; 3590 3591 Tests.prototype.result = function(test) 3592 { 3593 // If the harness has already transitioned beyond the `HAVE_RESULTS` 3594 // phase, subsequent tests should not cause it to revert. 3595 if (this.phase <= this.phases.HAVE_RESULTS) { 3596 this.phase = this.phases.HAVE_RESULTS; 3597 } 3598 this.num_pending--; 3599 this.notify_result(test); 3600 }; 3601 3602 Tests.prototype.notify_result = function(test) { 3603 var this_obj = this; 3604 this.processing_callbacks = true; 3605 forEach(this.test_done_callbacks, 3606 function(callback) 3607 { 3608 callback(test, this_obj); 3609 }); 3610 this.processing_callbacks = false; 3611 if (this_obj.all_done()) { 3612 this_obj.complete(); 3613 } 3614 }; 3615 3616 Tests.prototype.complete = function() { 3617 if (this.phase === this.phases.COMPLETE) { 3618 return; 3619 } 3620 var this_obj = this; 3621 var all_complete = function() { 3622 this_obj.phase = this_obj.phases.COMPLETE; 3623 this_obj.notify_complete(); 3624 }; 3625 var incomplete = filter(this.tests, 3626 function(test) { 3627 return test.phase < test.phases.COMPLETE; 3628 }); 3629 3630 /** 3631 * To preserve legacy behavior, overall test completion must be 3632 * signaled synchronously. 3633 */ 3634 if (incomplete.length === 0) { 3635 all_complete(); 3636 return; 3637 } 3638 3639 all_async(incomplete, 3640 function(test, testDone) 3641 { 3642 if (test.phase === test.phases.INITIAL) { 3643 test.phase = test.phases.COMPLETE; 3644 testDone(); 3645 } else { 3646 add_test_done_callback(test, testDone); 3647 test.cleanup(); 3648 } 3649 }, 3650 all_complete); 3651 }; 3652 3653 Tests.prototype.set_assert = function(assert_name, args) { 3654 this.asserts_run.push(new AssertRecord(this.current_test, assert_name, args)) 3655 } 3656 3657 Tests.prototype.set_assert_status = function(status, stack) { 3658 let assert_record = this.asserts_run[this.asserts_run.length - 1]; 3659 assert_record.status = status; 3660 assert_record.stack = stack; 3661 } 3662 3663 /** 3664 * Update the harness status to reflect an unrecoverable harness error that 3665 * should cancel all further testing. Update all previously-defined tests 3666 * which have not yet started to indicate that they will not be executed. 3667 */ 3668 Tests.prototype.abort = function() { 3669 this.status.status = this.status.ERROR; 3670 this.is_aborted = true; 3671 3672 forEach(this.tests, 3673 function(test) { 3674 if (test.phase === test.phases.INITIAL) { 3675 test.phase = test.phases.COMPLETE; 3676 } 3677 }); 3678 }; 3679 3680 /* 3681 * Determine if any tests share the same `name` property. Return an array 3682 * containing the names of any such duplicates. 3683 */ 3684 Tests.prototype.find_duplicates = function() { 3685 var names = Object.create(null); 3686 var duplicates = []; 3687 3688 forEach (this.tests, 3689 function(test) 3690 { 3691 if (test.name in names && duplicates.indexOf(test.name) === -1) { 3692 duplicates.push(test.name); 3693 } 3694 names[test.name] = true; 3695 }); 3696 3697 return duplicates; 3698 }; 3699 3700 function code_unit_str(char) { 3701 return 'U+' + char.charCodeAt(0).toString(16); 3702 } 3703 3704 function sanitize_unpaired_surrogates(str) { 3705 return str.replace( 3706 /([\ud800-\udbff]+)(?![\udc00-\udfff])|(^|[^\ud800-\udbff])([\udc00-\udfff]+)/g, 3707 function(_, low, prefix, high) { 3708 var output = prefix || ""; // prefix may be undefined 3709 var string = low || high; // only one of these alternates can match 3710 for (var i = 0; i < string.length; i++) { 3711 output += code_unit_str(string[i]); 3712 } 3713 return output; 3714 }); 3715 } 3716 3717 function sanitize_all_unpaired_surrogates(tests) { 3718 forEach (tests, 3719 function (test) 3720 { 3721 var sanitized = sanitize_unpaired_surrogates(test.name); 3722 3723 if (test.name !== sanitized) { 3724 test.name = sanitized; 3725 delete test._structured_clone; 3726 } 3727 }); 3728 } 3729 3730 Tests.prototype.notify_complete = function() { 3731 var this_obj = this; 3732 var duplicates; 3733 3734 if (this.status.status === null) { 3735 duplicates = this.find_duplicates(); 3736 3737 // Some transports adhere to UTF-8's restriction on unpaired 3738 // surrogates. Sanitize the titles so that the results can be 3739 // consistently sent via all transports. 3740 sanitize_all_unpaired_surrogates(this.tests); 3741 3742 // Test names are presumed to be unique within test files--this 3743 // allows consumers to use them for identification purposes. 3744 // Duplicated names violate this expectation and should therefore 3745 // be reported as an error. 3746 if (duplicates.length) { 3747 this.status.status = this.status.ERROR; 3748 this.status.message = 3749 duplicates.length + ' duplicate test name' + 3750 (duplicates.length > 1 ? 's' : '') + ': "' + 3751 duplicates.join('", "') + '"'; 3752 } else { 3753 this.status.status = this.status.OK; 3754 } 3755 } 3756 3757 forEach (this.all_done_callbacks, 3758 function(callback) 3759 { 3760 callback(this_obj.tests, this_obj.status, this_obj.asserts_run); 3761 }); 3762 }; 3763 3764 /* 3765 * Constructs a RemoteContext that tracks tests from a specific worker. 3766 */ 3767 Tests.prototype.create_remote_worker = function(worker) { 3768 var message_port; 3769 3770 if (is_service_worker(worker)) { 3771 message_port = navigator.serviceWorker; 3772 worker.postMessage({type: "connect"}); 3773 } else if (is_shared_worker(worker)) { 3774 message_port = worker.port; 3775 message_port.start(); 3776 } else { 3777 message_port = worker; 3778 } 3779 3780 return new RemoteContext(worker, message_port); 3781 }; 3782 3783 /* 3784 * Constructs a RemoteContext that tracks tests from a specific window. 3785 */ 3786 Tests.prototype.create_remote_window = function(remote) { 3787 remote.postMessage({type: "getmessages"}, "*"); 3788 return new RemoteContext( 3789 remote, 3790 window, 3791 function(msg) { 3792 return msg.source === remote; 3793 } 3794 ); 3795 }; 3796 3797 Tests.prototype.fetch_tests_from_worker = function(worker) { 3798 if (this.phase >= this.phases.COMPLETE) { 3799 return; 3800 } 3801 3802 var remoteContext = this.create_remote_worker(worker); 3803 this.pending_remotes.push(remoteContext); 3804 return remoteContext.done; 3805 }; 3806 3807 /** 3808 * Get test results from a worker and include them in the current test. 3809 * 3810 * @param {Worker|SharedWorker|ServiceWorker|MessagePort} port - 3811 * Either a worker object or a port connected to a worker which is 3812 * running tests.. 3813 * @returns {Promise} - A promise that's resolved once all the remote tests are complete. 3814 */ 3815 function fetch_tests_from_worker(port) { 3816 return tests.fetch_tests_from_worker(port); 3817 } 3818 expose(fetch_tests_from_worker, 'fetch_tests_from_worker'); 3819 3820 Tests.prototype.fetch_tests_from_window = function(remote) { 3821 if (this.phase >= this.phases.COMPLETE) { 3822 return; 3823 } 3824 3825 this.pending_remotes.push(this.create_remote_window(remote)); 3826 }; 3827 3828 /** 3829 * Aggregate tests from separate windows or iframes 3830 * into the current document as if they were all part of the same test file. 3831 * 3832 * The document of the second window (or iframe) should include 3833 * ``testharness.js``, but not ``testharnessreport.js``, and use 3834 * :js:func:`test`, :js:func:`async_test`, and :js:func:`promise_test` in 3835 * the usual manner. 3836 * 3837 * @param {Window} window - The window to fetch tests from. 3838 */ 3839 function fetch_tests_from_window(window) { 3840 tests.fetch_tests_from_window(window); 3841 } 3842 expose(fetch_tests_from_window, 'fetch_tests_from_window'); 3843 3844 /** 3845 * Get test results from a shadow realm and include them in the current test. 3846 * 3847 * @param {ShadowRealm} realm - A shadow realm also running the test harness 3848 * @returns {Promise} - A promise that's resolved once all the remote tests are complete. 3849 */ 3850 function fetch_tests_from_shadow_realm(realm) { 3851 var chan = new MessageChannel(); 3852 function receiveMessage(msg_json) { 3853 chan.port1.postMessage(JSON.parse(msg_json)); 3854 } 3855 var done = tests.fetch_tests_from_worker(chan.port2); 3856 realm.evaluate("begin_shadow_realm_tests")(receiveMessage); 3857 chan.port2.start(); 3858 return done; 3859 } 3860 expose(fetch_tests_from_shadow_realm, 'fetch_tests_from_shadow_realm'); 3861 3862 /** 3863 * Begin running tests in this shadow realm test harness. 3864 * 3865 * To be called after all tests have been loaded; it is an error to call 3866 * this more than once or in a non-Shadow Realm environment 3867 * 3868 * @param {Function} postMessage - A function to send test updates to the 3869 * incubating realm-- accepts JSON-encoded messages in the format used by 3870 * RemoteContext 3871 */ 3872 function begin_shadow_realm_tests(postMessage) { 3873 if (!(test_environment instanceof ShadowRealmTestEnvironment)) { 3874 throw new Error("beign_shadow_realm_tests called in non-Shadow Realm environment"); 3875 } 3876 3877 test_environment.begin(function (msg) { 3878 postMessage(JSON.stringify(msg)); 3879 }); 3880 } 3881 expose(begin_shadow_realm_tests, 'begin_shadow_realm_tests'); 3882 3883 /** 3884 * Timeout the tests. 3885 * 3886 * This only has an effect when ``explicit_timeout`` has been set 3887 * in :js:func:`setup`. In other cases any call is a no-op. 3888 * 3889 */ 3890 function timeout() { 3891 if (tests.timeout_length === null) { 3892 tests.timeout(); 3893 } 3894 } 3895 expose(timeout, 'timeout'); 3896 3897 /** 3898 * Add a callback that's triggered when the first :js:class:`Test` is created. 3899 * 3900 * @param {Function} callback - Callback function. This is called 3901 * without arguments. 3902 */ 3903 function add_start_callback(callback) { 3904 tests.start_callbacks.push(callback); 3905 } 3906 3907 /** 3908 * Add a callback that's triggered when a test state changes. 3909 * 3910 * @param {Function} callback - Callback function, called with the 3911 * :js:class:`Test` as the only argument. 3912 */ 3913 function add_test_state_callback(callback) { 3914 tests.test_state_callbacks.push(callback); 3915 } 3916 3917 /** 3918 * Add a callback that's triggered when a test result is received. 3919 * 3920 * @param {Function} callback - Callback function, called with the 3921 * :js:class:`Test` as the only argument. 3922 */ 3923 function add_result_callback(callback) { 3924 tests.test_done_callbacks.push(callback); 3925 } 3926 3927 /** 3928 * Add a callback that's triggered when all tests are complete. 3929 * 3930 * @param {Function} callback - Callback function, called with an 3931 * array of :js:class:`Test` objects, a :js:class:`TestsStatus` 3932 * object and an array of :js:class:`AssertRecord` objects. If the 3933 * debug setting is ``false`` the final argument will be an empty 3934 * array. 3935 * 3936 * For performance reasons asserts are only tracked when the debug 3937 * setting is ``true``. In other cases the array of asserts will be 3938 * empty. 3939 */ 3940 function add_completion_callback(callback) { 3941 tests.all_done_callbacks.push(callback); 3942 } 3943 3944 expose(add_start_callback, 'add_start_callback'); 3945 expose(add_test_state_callback, 'add_test_state_callback'); 3946 expose(add_result_callback, 'add_result_callback'); 3947 expose(add_completion_callback, 'add_completion_callback'); 3948 3949 function remove(array, item) { 3950 var index = array.indexOf(item); 3951 if (index > -1) { 3952 array.splice(index, 1); 3953 } 3954 } 3955 3956 function remove_start_callback(callback) { 3957 remove(tests.start_callbacks, callback); 3958 } 3959 3960 function remove_test_state_callback(callback) { 3961 remove(tests.test_state_callbacks, callback); 3962 } 3963 3964 function remove_result_callback(callback) { 3965 remove(tests.test_done_callbacks, callback); 3966 } 3967 3968 function remove_completion_callback(callback) { 3969 remove(tests.all_done_callbacks, callback); 3970 } 3971 3972 /* 3973 * Output listener 3974 */ 3975 3976 function Output() { 3977 this.output_document = document; 3978 this.output_node = null; 3979 this.enabled = settings.output; 3980 this.phase = this.INITIAL; 3981 } 3982 3983 Output.prototype.INITIAL = 0; 3984 Output.prototype.STARTED = 1; 3985 Output.prototype.HAVE_RESULTS = 2; 3986 Output.prototype.COMPLETE = 3; 3987 3988 Output.prototype.setup = function(properties) { 3989 if (this.phase > this.INITIAL) { 3990 return; 3991 } 3992 3993 //If output is disabled in testharnessreport.js the test shouldn't be 3994 //able to override that 3995 this.enabled = this.enabled && (properties.hasOwnProperty("output") ? 3996 properties.output : settings.output); 3997 }; 3998 3999 Output.prototype.init = function(properties) { 4000 if (this.phase >= this.STARTED) { 4001 return; 4002 } 4003 if (properties.output_document) { 4004 this.output_document = properties.output_document; 4005 } else { 4006 this.output_document = document; 4007 } 4008 this.phase = this.STARTED; 4009 }; 4010 4011 Output.prototype.resolve_log = function() { 4012 var output_document; 4013 if (this.output_node) { 4014 return; 4015 } 4016 if (typeof this.output_document === "function") { 4017 output_document = this.output_document.apply(undefined); 4018 } else { 4019 output_document = this.output_document; 4020 } 4021 if (!output_document) { 4022 return; 4023 } 4024 var node = output_document.getElementById("log"); 4025 if (!node) { 4026 if (output_document.readyState === "loading") { 4027 return; 4028 } 4029 node = output_document.createElementNS("http://www.w3.org/1999/xhtml", "div"); 4030 node.id = "log"; 4031 if (output_document.body) { 4032 output_document.body.appendChild(node); 4033 } else { 4034 var root = output_document.documentElement; 4035 var is_html = (root && 4036 root.namespaceURI == "http://www.w3.org/1999/xhtml" && 4037 root.localName == "html"); 4038 var is_svg = (output_document.defaultView && 4039 "SVGSVGElement" in output_document.defaultView && 4040 root instanceof output_document.defaultView.SVGSVGElement); 4041 if (is_svg) { 4042 var foreignObject = output_document.createElementNS("http://www.w3.org/2000/svg", "foreignObject"); 4043 foreignObject.setAttribute("width", "100%"); 4044 foreignObject.setAttribute("height", "100%"); 4045 root.appendChild(foreignObject); 4046 foreignObject.appendChild(node); 4047 } else if (is_html) { 4048 root.appendChild(output_document.createElementNS("http://www.w3.org/1999/xhtml", "body")) 4049 .appendChild(node); 4050 } else { 4051 root.appendChild(node); 4052 } 4053 } 4054 } 4055 this.output_document = output_document; 4056 this.output_node = node; 4057 }; 4058 4059 Output.prototype.show_status = function() { 4060 if (this.phase < this.STARTED) { 4061 this.init({}); 4062 } 4063 if (!this.enabled || this.phase === this.COMPLETE) { 4064 return; 4065 } 4066 this.resolve_log(); 4067 if (this.phase < this.HAVE_RESULTS) { 4068 this.phase = this.HAVE_RESULTS; 4069 } 4070 var done_count = tests.tests.length - tests.num_pending; 4071 if (this.output_node && !tests.hide_test_state) { 4072 if (done_count < 100 || 4073 (done_count < 1000 && done_count % 100 === 0) || 4074 done_count % 1000 === 0) { 4075 this.output_node.textContent = "Running, " + 4076 done_count + " complete, " + 4077 tests.num_pending + " remain"; 4078 } 4079 } 4080 }; 4081 4082 Output.prototype.show_results = function (tests, harness_status, asserts_run) { 4083 if (this.phase >= this.COMPLETE) { 4084 return; 4085 } 4086 if (!this.enabled) { 4087 return; 4088 } 4089 if (!this.output_node) { 4090 this.resolve_log(); 4091 } 4092 this.phase = this.COMPLETE; 4093 4094 var log = this.output_node; 4095 if (!log) { 4096 return; 4097 } 4098 var output_document = this.output_document; 4099 4100 while (log.lastChild) { 4101 log.removeChild(log.lastChild); 4102 } 4103 4104 var stylesheet = output_document.createElementNS(xhtml_ns, "style"); 4105 stylesheet.textContent = stylesheetContent; 4106 var heads = output_document.getElementsByTagName("head"); 4107 if (heads.length) { 4108 heads[0].appendChild(stylesheet); 4109 } 4110 4111 var status_number = {}; 4112 forEach(tests, 4113 function(test) { 4114 var status = test.format_status(); 4115 if (status_number.hasOwnProperty(status)) { 4116 status_number[status] += 1; 4117 } else { 4118 status_number[status] = 1; 4119 } 4120 }); 4121 4122 function status_class(status) 4123 { 4124 return status.replace(/\s/g, '').toLowerCase(); 4125 } 4126 4127 var summary_template = ["section", {"id":"summary"}, 4128 ["h2", {}, "Summary"], 4129 function() 4130 { 4131 var status = harness_status.format_status(); 4132 var rv = [["section", {}, 4133 ["p", {}, 4134 "Harness status: ", 4135 ["span", {"class":status_class(status)}, 4136 status 4137 ], 4138 ], 4139 ["button", 4140 {"onclick": "let evt = new Event('__test_restart'); " + 4141 "let canceled = !window.dispatchEvent(evt);" + 4142 "if (!canceled) { location.reload() }"}, 4143 "Rerun"] 4144 ]]; 4145 4146 if (harness_status.status === harness_status.ERROR) { 4147 rv[0].push(["pre", {}, harness_status.message]); 4148 if (harness_status.stack) { 4149 rv[0].push(["pre", {}, harness_status.stack]); 4150 } 4151 } 4152 return rv; 4153 }, 4154 ["p", {}, "Found ${num_tests} tests"], 4155 function() { 4156 var rv = [["div", {}]]; 4157 var i = 0; 4158 while (Test.prototype.status_formats.hasOwnProperty(i)) { 4159 if (status_number.hasOwnProperty(Test.prototype.status_formats[i])) { 4160 var status = Test.prototype.status_formats[i]; 4161 rv[0].push(["div", {}, 4162 ["label", {}, 4163 ["input", {type:"checkbox", checked:"checked"}], 4164 status_number[status] + " ", 4165 ["span", {"class":status_class(status)}, status]]]); 4166 } 4167 i++; 4168 } 4169 return rv; 4170 }, 4171 ]; 4172 4173 log.appendChild(render(summary_template, {num_tests:tests.length}, output_document)); 4174 4175 forEach(output_document.querySelectorAll("section#summary label"), 4176 function(element) 4177 { 4178 on_event(element, "click", 4179 function(e) 4180 { 4181 if (output_document.getElementById("results") === null) { 4182 e.preventDefault(); 4183 return; 4184 } 4185 var result_class = element.querySelector("span[class]").getAttribute("class"); 4186 var style_element = output_document.querySelector("style#hide-" + result_class); 4187 var input_element = element.querySelector("input"); 4188 if (!style_element && !input_element.checked) { 4189 style_element = output_document.createElementNS(xhtml_ns, "style"); 4190 style_element.id = "hide-" + result_class; 4191 style_element.textContent = "table#results > tbody > tr.overall-"+result_class+"{display:none}"; 4192 output_document.body.appendChild(style_element); 4193 } else if (style_element && input_element.checked) { 4194 style_element.parentNode.removeChild(style_element); 4195 } 4196 }); 4197 }); 4198 4199 // This use of innerHTML plus manual escaping is not recommended in 4200 // general, but is necessary here for performance. Using textContent 4201 // on each individual <td> adds tens of seconds of execution time for 4202 // large test suites (tens of thousands of tests). 4203 function escape_html(s) 4204 { 4205 return s.replace(/\&/g, "&") 4206 .replace(/</g, "<") 4207 .replace(/"/g, """) 4208 .replace(/'/g, "'"); 4209 } 4210 4211 function has_assertions() 4212 { 4213 for (var i = 0; i < tests.length; i++) { 4214 if (tests[i].properties.hasOwnProperty("assert")) { 4215 return true; 4216 } 4217 } 4218 return false; 4219 } 4220 4221 function get_assertion(test) 4222 { 4223 if (test.properties.hasOwnProperty("assert")) { 4224 if (Array.isArray(test.properties.assert)) { 4225 return test.properties.assert.join(' '); 4226 } 4227 return test.properties.assert; 4228 } 4229 return ''; 4230 } 4231 4232 var asserts_run_by_test = new Map(); 4233 asserts_run.forEach(assert => { 4234 if (!asserts_run_by_test.has(assert.test)) { 4235 asserts_run_by_test.set(assert.test, []); 4236 } 4237 asserts_run_by_test.get(assert.test).push(assert); 4238 }); 4239 4240 function get_asserts_output(test) { 4241 var asserts = asserts_run_by_test.get(test); 4242 if (!asserts) { 4243 return "No asserts ran"; 4244 } 4245 rv = "<table>"; 4246 rv += asserts.map(assert => { 4247 var output_fn = "<strong>" + escape_html(assert.assert_name) + "</strong>("; 4248 var prefix_len = output_fn.length; 4249 var output_args = assert.args; 4250 var output_len = output_args.reduce((prev, current) => prev+current, prefix_len); 4251 if (output_len[output_len.length - 1] > 50) { 4252 output_args = output_args.map((x, i) => 4253 (i > 0 ? " ".repeat(prefix_len) : "" )+ x + (i < output_args.length - 1 ? ",\n" : "")); 4254 } else { 4255 output_args = output_args.map((x, i) => x + (i < output_args.length - 1 ? ", " : "")); 4256 } 4257 output_fn += escape_html(output_args.join("")); 4258 output_fn += ')'; 4259 var output_location; 4260 if (assert.stack) { 4261 output_location = assert.stack.split("\n", 1)[0].replace(/@?\w+:\/\/[^ "\/]+(?::\d+)?/g, " "); 4262 } 4263 return "<tr class='overall-" + 4264 status_class(Test.prototype.status_formats[assert.status]) + "'>" + 4265 "<td class='" + 4266 status_class(Test.prototype.status_formats[assert.status]) + "'>" + 4267 Test.prototype.status_formats[assert.status] + "</td>" + 4268 "<td><pre>" + 4269 output_fn + 4270 (output_location ? "\n" + escape_html(output_location) : "") + 4271 "</pre></td></tr>"; 4272 } 4273 ).join("\n"); 4274 rv += "</table>"; 4275 return rv; 4276 } 4277 4278 log.appendChild(document.createElementNS(xhtml_ns, "section")); 4279 var assertions = has_assertions(); 4280 var html = "<h2>Details</h2><table id='results' " + (assertions ? "class='assertions'" : "" ) + ">" + 4281 "<thead><tr><th>Result</th><th>Test Name</th>" + 4282 (assertions ? "<th>Assertion</th>" : "") + 4283 "<th>Message</th></tr></thead>" + 4284 "<tbody>"; 4285 for (var i = 0; i < tests.length; i++) { 4286 var test = tests[i]; 4287 html += '<tr class="overall-' + 4288 status_class(test.format_status()) + 4289 '">' + 4290 '<td class="' + 4291 status_class(test.format_status()) + 4292 '">' + 4293 test.format_status() + 4294 "</td><td>" + 4295 escape_html(test.name) + 4296 "</td><td>" + 4297 (assertions ? escape_html(get_assertion(test)) + "</td><td>" : "") + 4298 escape_html(test.message ? tests[i].message : " ") + 4299 (tests[i].stack ? "<pre>" + 4300 escape_html(tests[i].stack) + 4301 "</pre>": ""); 4302 if (!(test instanceof RemoteTest)) { 4303 html += "<details><summary>Asserts run</summary>" + get_asserts_output(test) + "</details>" 4304 } 4305 html += "</td></tr>"; 4306 } 4307 html += "</tbody></table>"; 4308 try { 4309 log.lastChild.innerHTML = html; 4310 } catch (e) { 4311 log.appendChild(document.createElementNS(xhtml_ns, "p")) 4312 .textContent = "Setting innerHTML for the log threw an exception."; 4313 log.appendChild(document.createElementNS(xhtml_ns, "pre")) 4314 .textContent = html; 4315 } 4316 }; 4317 4318 /* 4319 * Template code 4320 * 4321 * A template is just a JavaScript structure. An element is represented as: 4322 * 4323 * [tag_name, {attr_name:attr_value}, child1, child2] 4324 * 4325 * the children can either be strings (which act like text nodes), other templates or 4326 * functions (see below) 4327 * 4328 * A text node is represented as 4329 * 4330 * ["{text}", value] 4331 * 4332 * String values have a simple substitution syntax; ${foo} represents a variable foo. 4333 * 4334 * It is possible to embed logic in templates by using a function in a place where a 4335 * node would usually go. The function must either return part of a template or null. 4336 * 4337 * In cases where a set of nodes are required as output rather than a single node 4338 * with children it is possible to just use a list 4339 * [node1, node2, node3] 4340 * 4341 * Usage: 4342 * 4343 * render(template, substitutions) - take a template and an object mapping 4344 * variable names to parameters and return either a DOM node or a list of DOM nodes 4345 * 4346 * substitute(template, substitutions) - take a template and variable mapping object, 4347 * make the variable substitutions and return the substituted template 4348 * 4349 */ 4350 4351 function is_single_node(template) 4352 { 4353 return typeof template[0] === "string"; 4354 } 4355 4356 function substitute(template, substitutions) 4357 { 4358 if (typeof template === "function") { 4359 var replacement = template(substitutions); 4360 if (!replacement) { 4361 return null; 4362 } 4363 4364 return substitute(replacement, substitutions); 4365 } 4366 4367 if (is_single_node(template)) { 4368 return substitute_single(template, substitutions); 4369 } 4370 4371 return filter(map(template, function(x) { 4372 return substitute(x, substitutions); 4373 }), function(x) {return x !== null;}); 4374 } 4375 4376 function substitute_single(template, substitutions) 4377 { 4378 var substitution_re = /\$\{([^ }]*)\}/g; 4379 4380 function do_substitution(input) { 4381 var components = input.split(substitution_re); 4382 var rv = []; 4383 for (var i = 0; i < components.length; i += 2) { 4384 rv.push(components[i]); 4385 if (components[i + 1]) { 4386 rv.push(String(substitutions[components[i + 1]])); 4387 } 4388 } 4389 return rv; 4390 } 4391 4392 function substitute_attrs(attrs, rv) 4393 { 4394 rv[1] = {}; 4395 for (var name in template[1]) { 4396 if (attrs.hasOwnProperty(name)) { 4397 var new_name = do_substitution(name).join(""); 4398 var new_value = do_substitution(attrs[name]).join(""); 4399 rv[1][new_name] = new_value; 4400 } 4401 } 4402 } 4403 4404 function substitute_children(children, rv) 4405 { 4406 for (var i = 0; i < children.length; i++) { 4407 if (children[i] instanceof Object) { 4408 var replacement = substitute(children[i], substitutions); 4409 if (replacement !== null) { 4410 if (is_single_node(replacement)) { 4411 rv.push(replacement); 4412 } else { 4413 extend(rv, replacement); 4414 } 4415 } 4416 } else { 4417 extend(rv, do_substitution(String(children[i]))); 4418 } 4419 } 4420 return rv; 4421 } 4422 4423 var rv = []; 4424 rv.push(do_substitution(String(template[0])).join("")); 4425 4426 if (template[0] === "{text}") { 4427 substitute_children(template.slice(1), rv); 4428 } else { 4429 substitute_attrs(template[1], rv); 4430 substitute_children(template.slice(2), rv); 4431 } 4432 4433 return rv; 4434 } 4435 4436 function make_dom_single(template, doc) 4437 { 4438 var output_document = doc || document; 4439 var element; 4440 if (template[0] === "{text}") { 4441 element = output_document.createTextNode(""); 4442 for (var i = 1; i < template.length; i++) { 4443 element.data += template[i]; 4444 } 4445 } else { 4446 element = output_document.createElementNS(xhtml_ns, template[0]); 4447 for (var name in template[1]) { 4448 if (template[1].hasOwnProperty(name)) { 4449 element.setAttribute(name, template[1][name]); 4450 } 4451 } 4452 for (var i = 2; i < template.length; i++) { 4453 if (template[i] instanceof Object) { 4454 var sub_element = make_dom(template[i]); 4455 element.appendChild(sub_element); 4456 } else { 4457 var text_node = output_document.createTextNode(template[i]); 4458 element.appendChild(text_node); 4459 } 4460 } 4461 } 4462 4463 return element; 4464 } 4465 4466 function make_dom(template, substitutions, output_document) 4467 { 4468 if (is_single_node(template)) { 4469 return make_dom_single(template, output_document); 4470 } 4471 4472 return map(template, function(x) { 4473 return make_dom_single(x, output_document); 4474 }); 4475 } 4476 4477 function render(template, substitutions, output_document) 4478 { 4479 return make_dom(substitute(template, substitutions), output_document); 4480 } 4481 4482 /* 4483 * Utility functions 4484 */ 4485 function assert(expected_true, function_name, description, error, substitutions) 4486 { 4487 if (expected_true !== true) { 4488 var msg = make_message(function_name, description, 4489 error, substitutions); 4490 throw new AssertionError(msg); 4491 } 4492 } 4493 4494 /** 4495 * @class 4496 * Exception type that represents a failing assert. 4497 * 4498 * @param {string} message - Error message. 4499 */ 4500 function AssertionError(message) 4501 { 4502 if (typeof message == "string") { 4503 message = sanitize_unpaired_surrogates(message); 4504 } 4505 this.message = message; 4506 this.stack = get_stack(); 4507 } 4508 expose(AssertionError, "AssertionError"); 4509 4510 AssertionError.prototype = Object.create(Error.prototype); 4511 4512 const get_stack = function() { 4513 var stack = new Error().stack; 4514 4515 // 'Error.stack' is not supported in all browsers/versions 4516 if (!stack) { 4517 return "(Stack trace unavailable)"; 4518 } 4519 4520 var lines = stack.split("\n"); 4521 4522 // Create a pattern to match stack frames originating within testharness.js. These include the 4523 // script URL, followed by the line/col (e.g., '/resources/testharness.js:120:21'). 4524 // Escape the URL per http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript 4525 // in case it contains RegExp characters. 4526 var script_url = get_script_url(); 4527 var re_text = script_url ? script_url.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') : "\\btestharness.js"; 4528 var re = new RegExp(re_text + ":\\d+:\\d+"); 4529 4530 // Some browsers include a preamble that specifies the type of the error object. Skip this by 4531 // advancing until we find the first stack frame originating from testharness.js. 4532 var i = 0; 4533 while (!re.test(lines[i]) && i < lines.length) { 4534 i++; 4535 } 4536 4537 // Then skip the top frames originating from testharness.js to begin the stack at the test code. 4538 while (re.test(lines[i]) && i < lines.length) { 4539 i++; 4540 } 4541 4542 // Paranoid check that we didn't skip all frames. If so, return the original stack unmodified. 4543 if (i >= lines.length) { 4544 return stack; 4545 } 4546 4547 return lines.slice(i).join("\n"); 4548 } 4549 4550 function OptionalFeatureUnsupportedError(message) 4551 { 4552 AssertionError.call(this, message); 4553 } 4554 OptionalFeatureUnsupportedError.prototype = Object.create(AssertionError.prototype); 4555 expose(OptionalFeatureUnsupportedError, "OptionalFeatureUnsupportedError"); 4556 4557 function make_message(function_name, description, error, substitutions) 4558 { 4559 for (var p in substitutions) { 4560 if (substitutions.hasOwnProperty(p)) { 4561 substitutions[p] = format_value(substitutions[p]); 4562 } 4563 } 4564 var node_form = substitute(["{text}", "${function_name}: ${description}" + error], 4565 merge({function_name:function_name, 4566 description:(description?description + " ":"")}, 4567 substitutions)); 4568 return node_form.slice(1).join(""); 4569 } 4570 4571 function filter(array, callable, thisObj) { 4572 var rv = []; 4573 for (var i = 0; i < array.length; i++) { 4574 if (array.hasOwnProperty(i)) { 4575 var pass = callable.call(thisObj, array[i], i, array); 4576 if (pass) { 4577 rv.push(array[i]); 4578 } 4579 } 4580 } 4581 return rv; 4582 } 4583 4584 function map(array, callable, thisObj) 4585 { 4586 var rv = []; 4587 rv.length = array.length; 4588 for (var i = 0; i < array.length; i++) { 4589 if (array.hasOwnProperty(i)) { 4590 rv[i] = callable.call(thisObj, array[i], i, array); 4591 } 4592 } 4593 return rv; 4594 } 4595 4596 function extend(array, items) 4597 { 4598 Array.prototype.push.apply(array, items); 4599 } 4600 4601 function forEach(array, callback, thisObj) 4602 { 4603 for (var i = 0; i < array.length; i++) { 4604 if (array.hasOwnProperty(i)) { 4605 callback.call(thisObj, array[i], i, array); 4606 } 4607 } 4608 } 4609 4610 /** 4611 * Immediately invoke a "iteratee" function with a series of values in 4612 * parallel and invoke a final "done" function when all of the "iteratee" 4613 * invocations have signaled completion. 4614 * 4615 * If all callbacks complete synchronously (or if no callbacks are 4616 * specified), the ``done_callback`` will be invoked synchronously. It is the 4617 * responsibility of the caller to ensure asynchronicity in cases where 4618 * that is desired. 4619 * 4620 * @param {array} value Zero or more values to use in the invocation of 4621 * ``iter_callback`` 4622 * @param {function} iter_callback A function that will be invoked 4623 * once for each of the values min 4624 * ``value``. Two arguments will 4625 * be available in each 4626 * invocation: the value from 4627 * ``value`` and a function that 4628 * must be invoked to signal 4629 * completion 4630 * @param {function} done_callback A function that will be invoked after 4631 * all operations initiated by the 4632 * ``iter_callback`` function have signaled 4633 * completion 4634 */ 4635 function all_async(values, iter_callback, done_callback) 4636 { 4637 var remaining = values.length; 4638 4639 if (remaining === 0) { 4640 done_callback(); 4641 } 4642 4643 forEach(values, 4644 function(element) { 4645 var invoked = false; 4646 var elDone = function() { 4647 if (invoked) { 4648 return; 4649 } 4650 4651 invoked = true; 4652 remaining -= 1; 4653 4654 if (remaining === 0) { 4655 done_callback(); 4656 } 4657 }; 4658 4659 iter_callback(element, elDone); 4660 }); 4661 } 4662 4663 function merge(a,b) 4664 { 4665 var rv = {}; 4666 var p; 4667 for (p in a) { 4668 rv[p] = a[p]; 4669 } 4670 for (p in b) { 4671 rv[p] = b[p]; 4672 } 4673 return rv; 4674 } 4675 4676 function expose(object, name) 4677 { 4678 var components = name.split("."); 4679 var target = global_scope; 4680 for (var i = 0; i < components.length - 1; i++) { 4681 if (!(components[i] in target)) { 4682 target[components[i]] = {}; 4683 } 4684 target = target[components[i]]; 4685 } 4686 target[components[components.length - 1]] = object; 4687 } 4688 4689 function is_same_origin(w) { 4690 try { 4691 'random_prop' in w; 4692 return true; 4693 } catch (e) { 4694 return false; 4695 } 4696 } 4697 4698 /** Returns the 'src' URL of the first <script> tag in the page to include the file 'testharness.js'. */ 4699 function get_script_url() 4700 { 4701 if (!('document' in global_scope)) { 4702 return undefined; 4703 } 4704 4705 var scripts = document.getElementsByTagName("script"); 4706 for (var i = 0; i < scripts.length; i++) { 4707 var src; 4708 if (scripts[i].src) { 4709 src = scripts[i].src; 4710 } else if (scripts[i].href) { 4711 //SVG case 4712 src = scripts[i].href.baseVal; 4713 } 4714 4715 var matches = src && src.match(/^(.*\/|)testharness\.js$/); 4716 if (matches) { 4717 return src; 4718 } 4719 } 4720 return undefined; 4721 } 4722 4723 /** Returns the <title> or filename or "Untitled" */ 4724 function get_title() 4725 { 4726 if ('document' in global_scope) { 4727 //Don't use document.title to work around an Opera/Presto bug in XHTML documents 4728 var title = document.getElementsByTagName("title")[0]; 4729 if (title && title.firstChild && title.firstChild.data) { 4730 return title.firstChild.data; 4731 } 4732 } 4733 if ('META_TITLE' in global_scope && META_TITLE) { 4734 return META_TITLE; 4735 } 4736 if ('location' in global_scope) { 4737 return location.pathname.substring(location.pathname.lastIndexOf('/') + 1, location.pathname.indexOf('.')); 4738 } 4739 return "Untitled"; 4740 } 4741 4742 /** 4743 * Setup globals 4744 */ 4745 4746 var tests = new Tests(); 4747 4748 if (global_scope.addEventListener) { 4749 var error_handler = function(error, message, stack) { 4750 var optional_unsupported = error instanceof OptionalFeatureUnsupportedError; 4751 if (tests.file_is_test) { 4752 var test = tests.tests[0]; 4753 if (test.phase >= test.phases.HAS_RESULT) { 4754 return; 4755 } 4756 var status = optional_unsupported ? test.PRECONDITION_FAILED : test.FAIL; 4757 test.set_status(status, message, stack); 4758 test.phase = test.phases.HAS_RESULT; 4759 } else if (!tests.allow_uncaught_exception) { 4760 var status = optional_unsupported ? tests.status.PRECONDITION_FAILED : tests.status.ERROR; 4761 tests.status.status = status; 4762 tests.status.message = message; 4763 tests.status.stack = stack; 4764 } 4765 4766 // Do not transition to the "complete" phase if the test has been 4767 // configured to allow uncaught exceptions. This gives the test an 4768 // opportunity to define subtests based on the exception reporting 4769 // behavior. 4770 if (!tests.allow_uncaught_exception) { 4771 done(); 4772 } 4773 }; 4774 4775 addEventListener("error", function(e) { 4776 var message = e.message; 4777 var stack; 4778 if (e.error && e.error.stack) { 4779 stack = e.error.stack; 4780 } else { 4781 stack = e.filename + ":" + e.lineno + ":" + e.colno; 4782 } 4783 error_handler(e.error, message, stack); 4784 }, false); 4785 4786 addEventListener("unhandledrejection", function(e) { 4787 var message; 4788 if (e.reason && e.reason.message) { 4789 message = "Unhandled rejection: " + e.reason.message; 4790 } else { 4791 message = "Unhandled rejection"; 4792 } 4793 var stack; 4794 if (e.reason && e.reason.stack) { 4795 stack = e.reason.stack; 4796 } 4797 error_handler(e.reason, message, stack); 4798 }, false); 4799 } 4800 4801 test_environment.on_tests_ready(); 4802 4803 /** 4804 * Stylesheet 4805 */ 4806 var stylesheetContent = "\ 4807html {\ 4808 font-family:DejaVu Sans, Bitstream Vera Sans, Arial, Sans;\ 4809}\ 4810\ 4811#log .warning,\ 4812#log .warning a {\ 4813 color: black;\ 4814 background: yellow;\ 4815}\ 4816\ 4817#log .error,\ 4818#log .error a {\ 4819 color: white;\ 4820 background: red;\ 4821}\ 4822\ 4823section#summary {\ 4824 margin-bottom:1em;\ 4825}\ 4826\ 4827table#results {\ 4828 border-collapse:collapse;\ 4829 table-layout:fixed;\ 4830 width:100%;\ 4831}\ 4832\ 4833table#results > thead > tr > th:first-child,\ 4834table#results > tbody > tr > td:first-child {\ 4835 width:8em;\ 4836}\ 4837\ 4838table#results > thead > tr > th:last-child,\ 4839table#results > thead > tr > td:last-child {\ 4840 width:50%;\ 4841}\ 4842\ 4843table#results.assertions > thead > tr > th:last-child,\ 4844table#results.assertions > tbody > tr > td:last-child {\ 4845 width:35%;\ 4846}\ 4847\ 4848table#results > thead > > tr > th {\ 4849 padding:0;\ 4850 padding-bottom:0.5em;\ 4851 border-bottom:medium solid black;\ 4852}\ 4853\ 4854table#results > tbody > tr> td {\ 4855 padding:1em;\ 4856 padding-bottom:0.5em;\ 4857 border-bottom:thin solid black;\ 4858}\ 4859\ 4860.pass {\ 4861 color:green;\ 4862}\ 4863\ 4864.fail {\ 4865 color:red;\ 4866}\ 4867\ 4868tr.timeout {\ 4869 color:red;\ 4870}\ 4871\ 4872tr.notrun {\ 4873 color:blue;\ 4874}\ 4875\ 4876tr.optionalunsupported {\ 4877 color:blue;\ 4878}\ 4879\ 4880.ok {\ 4881 color:green;\ 4882}\ 4883\ 4884.error {\ 4885 color:red;\ 4886}\ 4887\ 4888.pass, .fail, .timeout, .notrun, .optionalunsupported .ok, .timeout, .error {\ 4889 font-variant:small-caps;\ 4890}\ 4891\ 4892table#results span {\ 4893 display:block;\ 4894}\ 4895\ 4896table#results span.expected {\ 4897 font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;\ 4898 white-space:pre;\ 4899}\ 4900\ 4901table#results span.actual {\ 4902 font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;\ 4903 white-space:pre;\ 4904}\ 4905"; 4906 4907})(self); 4908// vim: set expandtab shiftwidth=4 tabstop=4: 4909