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