toolkit/modules/tests/xpcshell/test_Promise.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* Any copyright is dedicated to the Public Domain.
michael@0 2 * http://creativecommons.org/publicdomain/zero/1.0/ */
michael@0 3 "use strict";
michael@0 4
michael@0 5 Components.utils.import("resource://gre/modules/Promise.jsm");
michael@0 6 Components.utils.import("resource://gre/modules/Services.jsm");
michael@0 7 Components.utils.import("resource://gre/modules/Task.jsm");
michael@0 8
michael@0 9 // Deactivate the standard xpcshell observer, as it turns uncaught
michael@0 10 // rejections into failures, which we don't want here.
michael@0 11 Promise.Debugging.clearUncaughtErrorObservers();
michael@0 12
michael@0 13 ////////////////////////////////////////////////////////////////////////////////
michael@0 14 //// Test runner
michael@0 15
michael@0 16 let run_promise_tests = function run_promise_tests(tests, cb) {
michael@0 17 let loop = function loop(index) {
michael@0 18 if (index >= tests.length) {
michael@0 19 if (cb) {
michael@0 20 cb.call();
michael@0 21 }
michael@0 22 return;
michael@0 23 }
michael@0 24 do_print("Launching test " + (index + 1) + "/" + tests.length);
michael@0 25 let test = tests[index];
michael@0 26 // Execute from an empty stack
michael@0 27 let next = function next() {
michael@0 28 do_print("Test " + (index + 1) + "/" + tests.length + " complete");
michael@0 29 do_execute_soon(function() {
michael@0 30 loop(index + 1);
michael@0 31 });
michael@0 32 };
michael@0 33 let result = test();
michael@0 34 result.then(next, next);
michael@0 35 };
michael@0 36 return loop(0);
michael@0 37 };
michael@0 38
michael@0 39 let make_promise_test = function(test) {
michael@0 40 return function runtest() {
michael@0 41 do_print("Test starting: " + test.name);
michael@0 42 try {
michael@0 43 let result = test();
michael@0 44 if (result && "promise" in result) {
michael@0 45 result = result.promise;
michael@0 46 }
michael@0 47 if (!result || !("then" in result)) {
michael@0 48 let exn;
michael@0 49 try {
michael@0 50 do_throw("Test " + test.name + " did not return a promise: " + result);
michael@0 51 } catch (x) {
michael@0 52 exn = x;
michael@0 53 }
michael@0 54 return Promise.reject(exn);
michael@0 55 }
michael@0 56 // The test returns a promise
michael@0 57 result = result.then(
michael@0 58 // Test complete
michael@0 59 function onResolve() {
michael@0 60 do_print("Test complete: " + test.name);
michael@0 61 },
michael@0 62 // The test failed with an unexpected error
michael@0 63 function onReject(err) {
michael@0 64 let detail;
michael@0 65 if (err && typeof err == "object" && "stack" in err) {
michael@0 66 detail = err.stack;
michael@0 67 } else {
michael@0 68 detail = "(no stack)";
michael@0 69 }
michael@0 70 do_throw("Test " + test.name + " rejected with the following reason: "
michael@0 71 + err + detail);
michael@0 72 });
michael@0 73 return result;
michael@0 74 } catch (x) {
michael@0 75 // The test failed because of an error outside of a promise
michael@0 76 do_throw("Error in body of test " + test.name + ": " + x + " at " + x.stack);
michael@0 77 return Promise.reject();
michael@0 78 }
michael@0 79 };
michael@0 80 };
michael@0 81
michael@0 82 ////////////////////////////////////////////////////////////////////////////////
michael@0 83 //// Tests
michael@0 84
michael@0 85 let tests = [];
michael@0 86
michael@0 87 // Utility function to observe an failures in a promise
michael@0 88 // This function is useful if the promise itself is
michael@0 89 // not returned.
michael@0 90 let observe_failures = function observe_failures(promise) {
michael@0 91 promise.catch(function onReject(reason) {
michael@0 92 test.do_throw("Observed failure in test " + test + ": " + reason);
michael@0 93 });
michael@0 94 };
michael@0 95
michael@0 96 // Test that all observers are notified
michael@0 97 tests.push(make_promise_test(
michael@0 98 function notification(test) {
michael@0 99 // The size of the test
michael@0 100 const SIZE = 10;
michael@0 101 const RESULT = "this is an arbitrary value";
michael@0 102
michael@0 103 // Number of observers that yet need to be notified
michael@0 104 let expected = SIZE;
michael@0 105
michael@0 106 // |true| once an observer has been notified
michael@0 107 let notified = [];
michael@0 108
michael@0 109 // The promise observed
michael@0 110 let source = Promise.defer();
michael@0 111 let result = Promise.defer();
michael@0 112
michael@0 113 let install_observer = function install_observer(i) {
michael@0 114 observe_failures(source.promise.then(
michael@0 115 function onSuccess(value) {
michael@0 116 do_check_true(!notified[i], "Ensuring that observer is notified at most once");
michael@0 117 notified[i] = true;
michael@0 118
michael@0 119 do_check_eq(value, RESULT, "Ensuring that the observed value is correct");
michael@0 120 if (--expected == 0) {
michael@0 121 result.resolve();
michael@0 122 }
michael@0 123 }));
michael@0 124 };
michael@0 125
michael@0 126 // Install a number of observers before resolving
michael@0 127 let i;
michael@0 128 for (i = 0; i < SIZE/2; ++i) {
michael@0 129 install_observer(i);
michael@0 130 }
michael@0 131
michael@0 132 source.resolve(RESULT);
michael@0 133
michael@0 134 // Install remaining observers
michael@0 135 for(;i < SIZE; ++i) {
michael@0 136 install_observer(i);
michael@0 137 }
michael@0 138
michael@0 139 return result;
michael@0 140 }));
michael@0 141
michael@0 142 // Test that observers get the correct "this" value in strict mode.
michael@0 143 tests.push(
michael@0 144 make_promise_test(function handlers_this_value(test) {
michael@0 145 return Promise.resolve().then(
michael@0 146 function onResolve() {
michael@0 147 // Since this file is in strict mode, the correct value is "undefined".
michael@0 148 do_check_eq(this, undefined);
michael@0 149 throw "reject";
michael@0 150 }
michael@0 151 ).then(
michael@0 152 null,
michael@0 153 function onReject() {
michael@0 154 // Since this file is in strict mode, the correct value is "undefined".
michael@0 155 do_check_eq(this, undefined);
michael@0 156 }
michael@0 157 );
michael@0 158 }));
michael@0 159
michael@0 160 // Test that observers registered on a pending promise are notified in order.
michael@0 161 tests.push(
michael@0 162 make_promise_test(function then_returns_before_callbacks(test) {
michael@0 163 let deferred = Promise.defer();
michael@0 164 let promise = deferred.promise;
michael@0 165
michael@0 166 let order = 0;
michael@0 167
michael@0 168 promise.then(
michael@0 169 function onResolve() {
michael@0 170 do_check_eq(order, 0);
michael@0 171 order++;
michael@0 172 }
michael@0 173 );
michael@0 174
michael@0 175 promise.then(
michael@0 176 function onResolve() {
michael@0 177 do_check_eq(order, 1);
michael@0 178 order++;
michael@0 179 }
michael@0 180 );
michael@0 181
michael@0 182 let newPromise = promise.then(
michael@0 183 function onResolve() {
michael@0 184 do_check_eq(order, 2);
michael@0 185 }
michael@0 186 );
michael@0 187
michael@0 188 deferred.resolve();
michael@0 189
michael@0 190 // This test finishes after the last handler succeeds.
michael@0 191 return newPromise;
michael@0 192 }));
michael@0 193
michael@0 194 // Test that observers registered on a resolved promise are notified in order.
michael@0 195 tests.push(
michael@0 196 make_promise_test(function then_returns_before_callbacks(test) {
michael@0 197 let promise = Promise.resolve();
michael@0 198
michael@0 199 let order = 0;
michael@0 200
michael@0 201 promise.then(
michael@0 202 function onResolve() {
michael@0 203 do_check_eq(order, 0);
michael@0 204 order++;
michael@0 205 }
michael@0 206 );
michael@0 207
michael@0 208 promise.then(
michael@0 209 function onResolve() {
michael@0 210 do_check_eq(order, 1);
michael@0 211 order++;
michael@0 212 }
michael@0 213 );
michael@0 214
michael@0 215 // This test finishes after the last handler succeeds.
michael@0 216 return promise.then(
michael@0 217 function onResolve() {
michael@0 218 do_check_eq(order, 2);
michael@0 219 }
michael@0 220 );
michael@0 221 }));
michael@0 222
michael@0 223 // Test that all observers are notified at most once, even if source
michael@0 224 // is resolved/rejected several times
michael@0 225 tests.push(make_promise_test(
michael@0 226 function notification_once(test) {
michael@0 227 // The size of the test
michael@0 228 const SIZE = 10;
michael@0 229 const RESULT = "this is an arbitrary value";
michael@0 230
michael@0 231 // Number of observers that yet need to be notified
michael@0 232 let expected = SIZE;
michael@0 233
michael@0 234 // |true| once an observer has been notified
michael@0 235 let notified = [];
michael@0 236
michael@0 237 // The promise observed
michael@0 238 let observed = Promise.defer();
michael@0 239 let result = Promise.defer();
michael@0 240
michael@0 241 let install_observer = function install_observer(i) {
michael@0 242 observe_failures(observed.promise.then(
michael@0 243 function onSuccess(value) {
michael@0 244 do_check_true(!notified[i], "Ensuring that observer is notified at most once");
michael@0 245 notified[i] = true;
michael@0 246
michael@0 247 do_check_eq(value, RESULT, "Ensuring that the observed value is correct");
michael@0 248 if (--expected == 0) {
michael@0 249 result.resolve();
michael@0 250 }
michael@0 251 }));
michael@0 252 };
michael@0 253
michael@0 254 // Install a number of observers before resolving
michael@0 255 let i;
michael@0 256 for (i = 0; i < SIZE/2; ++i) {
michael@0 257 install_observer(i);
michael@0 258 }
michael@0 259
michael@0 260 observed.resolve(RESULT);
michael@0 261
michael@0 262 // Install remaining observers
michael@0 263 for(;i < SIZE; ++i) {
michael@0 264 install_observer(i);
michael@0 265 }
michael@0 266
michael@0 267 // Resolve some more
michael@0 268 for (i = 0; i < 10; ++i) {
michael@0 269 observed.resolve(RESULT);
michael@0 270 observed.reject();
michael@0 271 }
michael@0 272
michael@0 273 return result;
michael@0 274 }));
michael@0 275
michael@0 276 // Test that throwing an exception from a onResolve listener
michael@0 277 // does not prevent other observers from receiving the notification
michael@0 278 // of success.
michael@0 279 tests.push(
michael@0 280 make_promise_test(function exceptions_do_not_stop_notifications(test) {
michael@0 281 let source = Promise.defer();
michael@0 282
michael@0 283 let exception_thrown = false;
michael@0 284 let exception_content = new Error("Boom!");
michael@0 285
michael@0 286 let observer_1 = source.promise.then(
michael@0 287 function onResolve() {
michael@0 288 exception_thrown = true;
michael@0 289 throw exception_content;
michael@0 290 });
michael@0 291
michael@0 292 let observer_2 = source.promise.then(
michael@0 293 function onResolve() {
michael@0 294 do_check_true(exception_thrown, "Second observer called after first observer has thrown");
michael@0 295 }
michael@0 296 );
michael@0 297
michael@0 298 let result = observer_1.then(
michael@0 299 function onResolve() {
michael@0 300 do_throw("observer_1 should not have resolved");
michael@0 301 },
michael@0 302 function onReject(reason) {
michael@0 303 do_check_true(reason == exception_content, "Obtained correct rejection");
michael@0 304 }
michael@0 305 );
michael@0 306
michael@0 307 source.resolve();
michael@0 308 return result;
michael@0 309 }
michael@0 310 ));
michael@0 311
michael@0 312 // Test that, once a promise is resolved, further resolve/reject
michael@0 313 // are ignored.
michael@0 314 tests.push(
michael@0 315 make_promise_test(function subsequent_resolves_are_ignored(test) {
michael@0 316 let deferred = Promise.defer();
michael@0 317 deferred.resolve(1);
michael@0 318 deferred.resolve(2);
michael@0 319 deferred.reject(3);
michael@0 320
michael@0 321 let result = deferred.promise.then(
michael@0 322 function onResolve(value) {
michael@0 323 do_check_eq(value, 1, "Resolution chose the first value");
michael@0 324 },
michael@0 325 function onReject(reason) {
michael@0 326 do_throw("Obtained a rejection while the promise was already resolved");
michael@0 327 }
michael@0 328 );
michael@0 329
michael@0 330 return result;
michael@0 331 }));
michael@0 332
michael@0 333 // Test that, once a promise is rejected, further resolve/reject
michael@0 334 // are ignored.
michael@0 335 tests.push(
michael@0 336 make_promise_test(function subsequent_rejects_are_ignored(test) {
michael@0 337 let deferred = Promise.defer();
michael@0 338 deferred.reject(1);
michael@0 339 deferred.reject(2);
michael@0 340 deferred.resolve(3);
michael@0 341
michael@0 342 let result = deferred.promise.then(
michael@0 343 function onResolve() {
michael@0 344 do_throw("Obtained a resolution while the promise was already rejected");
michael@0 345 },
michael@0 346 function onReject(reason) {
michael@0 347 do_check_eq(reason, 1, "Rejection chose the first value");
michael@0 348 }
michael@0 349 );
michael@0 350
michael@0 351 return result;
michael@0 352 }));
michael@0 353
michael@0 354 // Test that returning normally from a rejection recovers from the error
michael@0 355 // and that listeners are informed of a success.
michael@0 356 tests.push(
michael@0 357 make_promise_test(function recovery(test) {
michael@0 358 let boom = new Error("Boom!");
michael@0 359 let deferred = Promise.defer();
michael@0 360 const RESULT = "An arbitrary value";
michael@0 361
michael@0 362 let promise = deferred.promise.then(
michael@0 363 function onResolve() {
michael@0 364 do_throw("A rejected promise should not resolve");
michael@0 365 },
michael@0 366 function onReject(reason) {
michael@0 367 do_check_true(reason == boom, "Promise was rejected with the correct error");
michael@0 368 return RESULT;
michael@0 369 }
michael@0 370 );
michael@0 371
michael@0 372 promise = promise.then(
michael@0 373 function onResolve(value) {
michael@0 374 do_check_eq(value, RESULT, "Promise was recovered with the correct value");
michael@0 375 }
michael@0 376 );
michael@0 377
michael@0 378 deferred.reject(boom);
michael@0 379 return promise;
michael@0 380 }));
michael@0 381
michael@0 382 // Test that returning a resolved promise from a onReject causes a resolution
michael@0 383 // (recovering from the error) and that returning a rejected promise
michael@0 384 // from a onResolve listener causes a rejection (raising an error).
michael@0 385 tests.push(
michael@0 386 make_promise_test(function recovery_with_promise(test) {
michael@0 387 let boom = new Error("Arbitrary error");
michael@0 388 let deferred = Promise.defer();
michael@0 389 const RESULT = "An arbitrary value";
michael@0 390 const boom2 = new Error("Another arbitrary error");
michael@0 391
michael@0 392 // return a resolved promise from a onReject listener
michael@0 393 let promise = deferred.promise.then(
michael@0 394 function onResolve() {
michael@0 395 do_throw("A rejected promise should not resolve");
michael@0 396 },
michael@0 397 function onReject(reason) {
michael@0 398 do_check_true(reason == boom, "Promise was rejected with the correct error");
michael@0 399 return Promise.resolve(RESULT);
michael@0 400 }
michael@0 401 );
michael@0 402
michael@0 403 // return a rejected promise from a onResolve listener
michael@0 404 promise = promise.then(
michael@0 405 function onResolve(value) {
michael@0 406 do_check_eq(value, RESULT, "Promise was recovered with the correct value");
michael@0 407 return Promise.reject(boom2);
michael@0 408 }
michael@0 409 );
michael@0 410
michael@0 411 promise = promise.catch(
michael@0 412 function onReject(reason) {
michael@0 413 do_check_eq(reason, boom2, "Rejection was propagated with the correct " +
michael@0 414 "reason, through a promise");
michael@0 415 }
michael@0 416 );
michael@0 417
michael@0 418 deferred.reject(boom);
michael@0 419 return promise;
michael@0 420 }));
michael@0 421
michael@0 422 // Test that we can resolve with promises of promises
michael@0 423 tests.push(
michael@0 424 make_promise_test(function test_propagation(test) {
michael@0 425 const RESULT = "Yet another arbitrary value";
michael@0 426 let d1 = Promise.defer();
michael@0 427 let d2 = Promise.defer();
michael@0 428 let d3 = Promise.defer();
michael@0 429
michael@0 430 d3.resolve(d2.promise);
michael@0 431 d2.resolve(d1.promise);
michael@0 432 d1.resolve(RESULT);
michael@0 433
michael@0 434 return d3.promise.then(
michael@0 435 function onSuccess(value) {
michael@0 436 do_check_eq(value, RESULT, "Resolution with a promise eventually yielded "
michael@0 437 + " the correct result");
michael@0 438 }
michael@0 439 );
michael@0 440 }));
michael@0 441
michael@0 442 // Test sequences of |then| and |catch|
michael@0 443 tests.push(
michael@0 444 make_promise_test(function test_chaining(test) {
michael@0 445 let error_1 = new Error("Error 1");
michael@0 446 let error_2 = new Error("Error 2");
michael@0 447 let result_1 = "First result";
michael@0 448 let result_2 = "Second result";
michael@0 449 let result_3 = "Third result";
michael@0 450
michael@0 451 let source = Promise.defer();
michael@0 452
michael@0 453 let promise = source.promise.then().then();
michael@0 454
michael@0 455 source.resolve(result_1);
michael@0 456
michael@0 457 // Check that result_1 is correctly propagated
michael@0 458 promise = promise.then(
michael@0 459 function onSuccess(result) {
michael@0 460 do_check_eq(result, result_1, "Result was propagated correctly through " +
michael@0 461 " several applications of |then|");
michael@0 462 return result_2;
michael@0 463 }
michael@0 464 );
michael@0 465
michael@0 466 // Check that returning from the promise produces a resolution
michael@0 467 promise = promise.catch(
michael@0 468 function onReject() {
michael@0 469 do_throw("Incorrect rejection");
michael@0 470 }
michael@0 471 );
michael@0 472
michael@0 473 // ... and that the check did not alter the value
michael@0 474 promise = promise.then(
michael@0 475 function onResolve(value) {
michael@0 476 do_check_eq(value, result_2, "Result was propagated correctly once again");
michael@0 477 }
michael@0 478 );
michael@0 479
michael@0 480 // Now the same kind of tests for rejections
michael@0 481 promise = promise.then(
michael@0 482 function onResolve() {
michael@0 483 throw error_1;
michael@0 484 }
michael@0 485 );
michael@0 486
michael@0 487 promise = promise.then(
michael@0 488 function onResolve() {
michael@0 489 do_throw("Incorrect resolution: the exception should have caused a rejection");
michael@0 490 }
michael@0 491 );
michael@0 492
michael@0 493 promise = promise.catch(
michael@0 494 function onReject(reason) {
michael@0 495 do_check_true(reason == error_1, "Reason was propagated correctly");
michael@0 496 throw error_2;
michael@0 497 }
michael@0 498 );
michael@0 499
michael@0 500 promise = promise.catch(
michael@0 501 function onReject(reason) {
michael@0 502 do_check_true(reason == error_2, "Throwing an error altered the reason " +
michael@0 503 "as expected");
michael@0 504 return result_3;
michael@0 505 }
michael@0 506 );
michael@0 507
michael@0 508 promise = promise.then(
michael@0 509 function onResolve(result) {
michael@0 510 do_check_eq(result, result_3, "Error was correctly recovered");
michael@0 511 }
michael@0 512 );
michael@0 513
michael@0 514 return promise;
michael@0 515 }));
michael@0 516
michael@0 517 // Test that resolving with a rejected promise actually rejects
michael@0 518 tests.push(
michael@0 519 make_promise_test(function resolve_to_rejected(test) {
michael@0 520 let source = Promise.defer();
michael@0 521 let error = new Error("Boom");
michael@0 522
michael@0 523 let promise = source.promise.then(
michael@0 524 function onResolve() {
michael@0 525 do_throw("Incorrect call to onResolve listener");
michael@0 526 },
michael@0 527 function onReject(reason) {
michael@0 528 do_check_eq(reason, error, "Rejection lead to the expected reason");
michael@0 529 }
michael@0 530 );
michael@0 531
michael@0 532 source.resolve(Promise.reject(error));
michael@0 533
michael@0 534 return promise;
michael@0 535 }));
michael@0 536
michael@0 537 // Test that Promise.resolve resolves as expected
michael@0 538 tests.push(
michael@0 539 make_promise_test(function test_resolve(test) {
michael@0 540 const RESULT = "arbitrary value";
michael@0 541 let p1 = Promise.resolve(RESULT);
michael@0 542 let p2 = Promise.resolve(p1);
michael@0 543 do_check_eq(p1, p2, "Promise.resolve used on a promise just returns the promise");
michael@0 544
michael@0 545 return p1.then(
michael@0 546 function onResolve(result) {
michael@0 547 do_check_eq(result, RESULT, "Promise.resolve propagated the correct result");
michael@0 548 }
michael@0 549 );
michael@0 550 }));
michael@0 551
michael@0 552 // Test that Promise.resolve throws when its argument is an async function.
michael@0 553 tests.push(
michael@0 554 make_promise_test(function test_promise_resolve_throws_with_async_function(test) {
michael@0 555 Assert.throws(() => Promise.resolve(Task.async(function* () {})),
michael@0 556 /Cannot resolve a promise with an async function/);
michael@0 557 return Promise.resolve();
michael@0 558 }));
michael@0 559
michael@0 560 // Test that the code after "then" is always executed before the callbacks
michael@0 561 tests.push(
michael@0 562 make_promise_test(function then_returns_before_callbacks(test) {
michael@0 563 let promise = Promise.resolve();
michael@0 564
michael@0 565 let thenExecuted = false;
michael@0 566
michael@0 567 promise = promise.then(
michael@0 568 function onResolve() {
michael@0 569 thenExecuted = true;
michael@0 570 }
michael@0 571 );
michael@0 572
michael@0 573 do_check_false(thenExecuted);
michael@0 574
michael@0 575 return promise;
michael@0 576 }));
michael@0 577
michael@0 578 // Test that chaining promises does not generate long stack traces
michael@0 579 tests.push(
michael@0 580 make_promise_test(function chaining_short_stack(test) {
michael@0 581 let source = Promise.defer();
michael@0 582 let promise = source.promise;
michael@0 583
michael@0 584 const NUM_ITERATIONS = 100;
michael@0 585
michael@0 586 for (let i = 0; i < NUM_ITERATIONS; i++) {
michael@0 587 promise = promise.then(
michael@0 588 function onResolve(result) {
michael@0 589 return result + ".";
michael@0 590 }
michael@0 591 );
michael@0 592 }
michael@0 593
michael@0 594 promise = promise.then(
michael@0 595 function onResolve(result) {
michael@0 596 // Check that the execution went as expected.
michael@0 597 let expectedString = new Array(1 + NUM_ITERATIONS).join(".");
michael@0 598 do_check_true(result == expectedString);
michael@0 599
michael@0 600 // Check that we didn't generate one or more stack frames per iteration.
michael@0 601 let stackFrameCount = 0;
michael@0 602 let stackFrame = Components.stack;
michael@0 603 while (stackFrame) {
michael@0 604 stackFrameCount++;
michael@0 605 stackFrame = stackFrame.caller;
michael@0 606 }
michael@0 607
michael@0 608 do_check_true(stackFrameCount < NUM_ITERATIONS);
michael@0 609 }
michael@0 610 );
michael@0 611
michael@0 612 source.resolve("");
michael@0 613
michael@0 614 return promise;
michael@0 615 }));
michael@0 616
michael@0 617 // Test that the values of the promise return by Promise.all() are kept in the
michael@0 618 // given order even if the given promises are resolved in arbitrary order
michael@0 619 tests.push(
michael@0 620 make_promise_test(function all_resolve(test) {
michael@0 621 let d1 = Promise.defer();
michael@0 622 let d2 = Promise.defer();
michael@0 623 let d3 = Promise.defer();
michael@0 624
michael@0 625 d3.resolve(4);
michael@0 626 d2.resolve(2);
michael@0 627 do_execute_soon(() => d1.resolve(1));
michael@0 628
michael@0 629 let promises = [d1.promise, d2.promise, 3, d3.promise];
michael@0 630
michael@0 631 return Promise.all(promises).then(
michael@0 632 function onResolve([val1, val2, val3, val4]) {
michael@0 633 do_check_eq(val1, 1);
michael@0 634 do_check_eq(val2, 2);
michael@0 635 do_check_eq(val3, 3);
michael@0 636 do_check_eq(val4, 4);
michael@0 637 }
michael@0 638 );
michael@0 639 }));
michael@0 640
michael@0 641 // Test that rejecting one of the promises passed to Promise.all()
michael@0 642 // rejects the promise return by Promise.all()
michael@0 643 tests.push(
michael@0 644 make_promise_test(function all_reject(test) {
michael@0 645 let error = new Error("Boom");
michael@0 646
michael@0 647 let d1 = Promise.defer();
michael@0 648 let d2 = Promise.defer();
michael@0 649 let d3 = Promise.defer();
michael@0 650
michael@0 651 d3.resolve(3);
michael@0 652 d2.resolve(2);
michael@0 653 do_execute_soon(() => d1.reject(error));
michael@0 654
michael@0 655 let promises = [d1.promise, d2.promise, d3.promise];
michael@0 656
michael@0 657 return Promise.all(promises).then(
michael@0 658 function onResolve() {
michael@0 659 do_throw("Incorrect call to onResolve listener");
michael@0 660 },
michael@0 661 function onReject(reason) {
michael@0 662 do_check_eq(reason, error, "Rejection lead to the expected reason");
michael@0 663 }
michael@0 664 );
michael@0 665 }));
michael@0 666
michael@0 667 // Test that passing only values (not promises) to Promise.all()
michael@0 668 // forwards them all as resolution values.
michael@0 669 tests.push(
michael@0 670 make_promise_test(function all_resolve_no_promises(test) {
michael@0 671 try {
michael@0 672 Promise.all(null);
michael@0 673 do_check_true(false, "all() should only accept iterables");
michael@0 674 } catch (e) {
michael@0 675 do_check_true(true, "all() fails when first the arg is not an iterable");
michael@0 676 }
michael@0 677
michael@0 678 let p1 = Promise.all([]).then(
michael@0 679 function onResolve(val) {
michael@0 680 do_check_true(Array.isArray(val) && val.length == 0);
michael@0 681 }
michael@0 682 );
michael@0 683
michael@0 684 let p2 = Promise.all([1, 2, 3]).then(
michael@0 685 function onResolve([val1, val2, val3]) {
michael@0 686 do_check_eq(val1, 1);
michael@0 687 do_check_eq(val2, 2);
michael@0 688 do_check_eq(val3, 3);
michael@0 689 }
michael@0 690 );
michael@0 691
michael@0 692 return Promise.all([p1, p2]);
michael@0 693 }));
michael@0 694
michael@0 695 // Test that Promise.all() handles non-array iterables
michael@0 696 tests.push(
michael@0 697 make_promise_test(function all_iterable(test) {
michael@0 698 function* iterable() {
michael@0 699 yield 1;
michael@0 700 yield 2;
michael@0 701 yield 3;
michael@0 702 }
michael@0 703
michael@0 704 return Promise.all(iterable()).then(
michael@0 705 function onResolve([val1, val2, val3]) {
michael@0 706 do_check_eq(val1, 1);
michael@0 707 do_check_eq(val2, 2);
michael@0 708 do_check_eq(val3, 3);
michael@0 709 },
michael@0 710 function onReject() {
michael@0 711 do_throw("all() unexpectedly rejected");
michael@0 712 }
michael@0 713 );
michael@0 714 }));
michael@0 715
michael@0 716 // Test that throwing from the iterable passed to Promise.all() rejects the
michael@0 717 // promise returned by Promise.all()
michael@0 718 tests.push(
michael@0 719 make_promise_test(function all_iterable_throws(test) {
michael@0 720 function* iterable() {
michael@0 721 throw 1;
michael@0 722 }
michael@0 723
michael@0 724 return Promise.all(iterable()).then(
michael@0 725 function onResolve() {
michael@0 726 do_throw("all() unexpectedly resolved");
michael@0 727 },
michael@0 728 function onReject(reason) {
michael@0 729 do_check_eq(reason, 1, "all() rejects when the iterator throws");
michael@0 730 }
michael@0 731 );
michael@0 732 }));
michael@0 733
michael@0 734 // Test that Promise.race() resolves with the first available resolution value
michael@0 735 tests.push(
michael@0 736 make_promise_test(function race_resolve(test) {
michael@0 737 let p1 = Promise.resolve(1);
michael@0 738 let p2 = Promise.resolve().then(() => 2);
michael@0 739
michael@0 740 return Promise.race([p1, p2]).then(
michael@0 741 function onResolve(value) {
michael@0 742 do_check_eq(value, 1);
michael@0 743 }
michael@0 744 );
michael@0 745 }));
michael@0 746
michael@0 747 // Test that passing only values (not promises) to Promise.race() works
michael@0 748 tests.push(
michael@0 749 make_promise_test(function race_resolve_no_promises(test) {
michael@0 750 try {
michael@0 751 Promise.race(null);
michael@0 752 do_check_true(false, "race() should only accept iterables");
michael@0 753 } catch (e) {
michael@0 754 do_check_true(true, "race() fails when first the arg is not an iterable");
michael@0 755 }
michael@0 756
michael@0 757 return Promise.race([1, 2, 3]).then(
michael@0 758 function onResolve(value) {
michael@0 759 do_check_eq(value, 1);
michael@0 760 }
michael@0 761 );
michael@0 762 }));
michael@0 763
michael@0 764 // Test that Promise.race() never resolves when passed an empty iterable
michael@0 765 tests.push(
michael@0 766 make_promise_test(function race_resolve_never(test) {
michael@0 767 return new Promise(resolve => {
michael@0 768 Promise.race([]).then(
michael@0 769 function onResolve() {
michael@0 770 do_throw("race() unexpectedly resolved");
michael@0 771 },
michael@0 772 function onReject() {
michael@0 773 do_throw("race() unexpectedly rejected");
michael@0 774 }
michael@0 775 );
michael@0 776
michael@0 777 // Approximate "never" so we don't have to solve the halting problem.
michael@0 778 do_timeout(200, resolve);
michael@0 779 });
michael@0 780 }));
michael@0 781
michael@0 782 // Test that Promise.race() handles non-array iterables.
michael@0 783 tests.push(
michael@0 784 make_promise_test(function race_iterable(test) {
michael@0 785 function* iterable() {
michael@0 786 yield 1;
michael@0 787 yield 2;
michael@0 788 yield 3;
michael@0 789 }
michael@0 790
michael@0 791 return Promise.race(iterable()).then(
michael@0 792 function onResolve(value) {
michael@0 793 do_check_eq(value, 1);
michael@0 794 },
michael@0 795 function onReject() {
michael@0 796 do_throw("race() unexpectedly rejected");
michael@0 797 }
michael@0 798 );
michael@0 799 }));
michael@0 800
michael@0 801 // Test that throwing from the iterable passed to Promise.race() rejects the
michael@0 802 // promise returned by Promise.race()
michael@0 803 tests.push(
michael@0 804 make_promise_test(function race_iterable_throws(test) {
michael@0 805 function* iterable() {
michael@0 806 throw 1;
michael@0 807 }
michael@0 808
michael@0 809 return Promise.race(iterable()).then(
michael@0 810 function onResolve() {
michael@0 811 do_throw("race() unexpectedly resolved");
michael@0 812 },
michael@0 813 function onReject(reason) {
michael@0 814 do_check_eq(reason, 1, "race() rejects when the iterator throws");
michael@0 815 }
michael@0 816 );
michael@0 817 }));
michael@0 818
michael@0 819 // Test that rejecting one of the promises passed to Promise.race() rejects the
michael@0 820 // promise returned by Promise.race()
michael@0 821 tests.push(
michael@0 822 make_promise_test(function race_reject(test) {
michael@0 823 let p1 = Promise.reject(1);
michael@0 824 let p2 = Promise.resolve(2);
michael@0 825 let p3 = Promise.resolve(3);
michael@0 826
michael@0 827 return Promise.race([p1, p2, p3]).then(
michael@0 828 function onResolve() {
michael@0 829 do_throw("race() unexpectedly resolved");
michael@0 830 },
michael@0 831 function onReject(reason) {
michael@0 832 do_check_eq(reason, 1, "race() rejects when given a rejected promise");
michael@0 833 }
michael@0 834 );
michael@0 835 }));
michael@0 836
michael@0 837 // Test behavior of the Promise constructor.
michael@0 838 tests.push(
michael@0 839 make_promise_test(function test_constructor(test) {
michael@0 840 try {
michael@0 841 new Promise(null);
michael@0 842 do_check_true(false, "Constructor should fail when not passed a function");
michael@0 843 } catch (e) {
michael@0 844 do_check_true(true, "Constructor fails when not passed a function");
michael@0 845 }
michael@0 846
michael@0 847 let executorRan = false;
michael@0 848 let promise = new Promise(
michael@0 849 function executor(resolve, reject) {
michael@0 850 executorRan = true;
michael@0 851 do_check_eq(this, undefined);
michael@0 852 do_check_eq(typeof resolve, "function",
michael@0 853 "resolve function should be passed to the executor");
michael@0 854 do_check_eq(typeof reject, "function",
michael@0 855 "reject function should be passed to the executor");
michael@0 856 }
michael@0 857 );
michael@0 858 do_check_instanceof(promise, Promise);
michael@0 859 do_check_true(executorRan, "Executor should execute synchronously");
michael@0 860
michael@0 861 // resolve a promise from the executor
michael@0 862 let resolvePromise = new Promise(
michael@0 863 function executor(resolve) {
michael@0 864 resolve(1);
michael@0 865 }
michael@0 866 ).then(
michael@0 867 function onResolve(value) {
michael@0 868 do_check_eq(value, 1, "Executor resolved with correct value");
michael@0 869 },
michael@0 870 function onReject() {
michael@0 871 do_throw("Executor unexpectedly rejected");
michael@0 872 }
michael@0 873 );
michael@0 874
michael@0 875 // reject a promise from the executor
michael@0 876 let rejectPromise = new Promise(
michael@0 877 function executor(_, reject) {
michael@0 878 reject(1);
michael@0 879 }
michael@0 880 ).then(
michael@0 881 function onResolve() {
michael@0 882 do_throw("Executor unexpectedly resolved");
michael@0 883 },
michael@0 884 function onReject(reason) {
michael@0 885 do_check_eq(reason, 1, "Executor rejected with correct value");
michael@0 886 }
michael@0 887 );
michael@0 888
michael@0 889 // throw from the executor, causing a rejection
michael@0 890 let throwPromise = new Promise(
michael@0 891 function executor() {
michael@0 892 throw 1;
michael@0 893 }
michael@0 894 ).then(
michael@0 895 function onResolve() {
michael@0 896 do_throw("Throwing inside an executor should not resolve the promise");
michael@0 897 },
michael@0 898 function onReject(reason) {
michael@0 899 do_check_eq(reason, 1, "Executor rejected with correct value");
michael@0 900 }
michael@0 901 );
michael@0 902
michael@0 903 return Promise.all([resolvePromise, rejectPromise, throwPromise]);
michael@0 904 }));
michael@0 905
michael@0 906 // Test deadlock in Promise.jsm with nested event loops
michael@0 907 // The scenario being tested is:
michael@0 908 // promise_1.then({
michael@0 909 // do some work that will asynchronously signal done
michael@0 910 // start an event loop waiting for the done signal
michael@0 911 // }
michael@0 912 // where the async work uses resolution of a second promise to
michael@0 913 // trigger the "done" signal. While this would likely work in a
michael@0 914 // naive implementation, our constant-stack implementation needs
michael@0 915 // a special case to avoid deadlock. Note that this test is
michael@0 916 // sensitive to the implementation-dependent order in which then()
michael@0 917 // clauses for two different promises are executed, so it is
michael@0 918 // possible for other implementations to pass this test and still
michael@0 919 // have similar deadlocks.
michael@0 920 tests.push(
michael@0 921 make_promise_test(function promise_nested_eventloop_deadlock(test) {
michael@0 922 // Set up a (long enough to be noticeable) timeout to
michael@0 923 // exit the nested event loop and throw if the test run is hung
michael@0 924 let shouldExitNestedEventLoop = false;
michael@0 925
michael@0 926 function event_loop() {
michael@0 927 let thr = Services.tm.mainThread;
michael@0 928 while(!shouldExitNestedEventLoop) {
michael@0 929 thr.processNextEvent(true);
michael@0 930 }
michael@0 931 }
michael@0 932
michael@0 933 // I wish there was a way to cancel xpcshell do_timeout()s
michael@0 934 do_timeout(2000, () => {
michael@0 935 if (!shouldExitNestedEventLoop) {
michael@0 936 shouldExitNestedEventLoop = true;
michael@0 937 do_throw("Test timed out");
michael@0 938 }
michael@0 939 });
michael@0 940
michael@0 941 let promise1 = Promise.resolve(1);
michael@0 942 let promise2 = Promise.resolve(2);
michael@0 943
michael@0 944 do_print("Setting wait for first promise");
michael@0 945 promise1.then(value => {
michael@0 946 do_print("Starting event loop");
michael@0 947 event_loop();
michael@0 948 }, null);
michael@0 949
michael@0 950 do_print("Setting wait for second promise");
michael@0 951 return promise2.catch(error => {return 3;})
michael@0 952 .then(
michael@0 953 count => {
michael@0 954 shouldExitNestedEventLoop = true;
michael@0 955 });
michael@0 956 }));
michael@0 957
michael@0 958 function wait_for_uncaught(aMustAppear, aTimeout = undefined) {
michael@0 959 let remaining = new Set();
michael@0 960 for (let k of aMustAppear) {
michael@0 961 remaining.add(k);
michael@0 962 }
michael@0 963 let deferred = Promise.defer();
michael@0 964 let print = do_print;
michael@0 965 let execute_soon = do_execute_soon;
michael@0 966 let observer = function({message, stack}) {
michael@0 967 let data = message + stack;
michael@0 968 print("Observing " + message + ", looking for " + aMustAppear.join(", "));
michael@0 969 for (let expected of remaining) {
michael@0 970 if (data.indexOf(expected) != -1) {
michael@0 971 print("I found " + expected);
michael@0 972 remaining.delete(expected);
michael@0 973 }
michael@0 974 if (remaining.size == 0 && observer) {
michael@0 975 Promise.Debugging.removeUncaughtErrorObserver(observer);
michael@0 976 observer = null;
michael@0 977 deferred.resolve();
michael@0 978 }
michael@0 979 }
michael@0 980 };
michael@0 981 Promise.Debugging.addUncaughtErrorObserver(observer);
michael@0 982 if (aTimeout) {
michael@0 983 do_timeout(aTimeout, function timeout() {
michael@0 984 if (observer) {
michael@0 985 Promise.Debugging.removeUncaughtErrorObserver(observer);
michael@0 986 observer = null;
michael@0 987 }
michael@0 988 deferred.reject(new Error("Timeout"));
michael@0 989 });
michael@0 990 }
michael@0 991 return deferred.promise;
michael@0 992 }
michael@0 993
michael@0 994 // Test that uncaught errors are reported as uncaught
michael@0 995 (function() {
michael@0 996 let make_string_rejection = function make_string_rejection() {
michael@0 997 let salt = (Math.random() * ( Math.pow(2, 24) - 1 ));
michael@0 998 let string = "This is an uncaught rejection " + salt;
michael@0 999 // Our error is not Error-like nor an nsIException, so the stack will
michael@0 1000 // include the closure doing the actual rejection.
michael@0 1001 return {mustFind: ["test_rejection_closure", string], error: string};
michael@0 1002 };
michael@0 1003 let make_num_rejection = function make_num_rejection() {
michael@0 1004 let salt = (Math.random() * ( Math.pow(2, 24) - 1 ));
michael@0 1005 // Our error is not Error-like nor an nsIException, so the stack will
michael@0 1006 // include the closure doing the actual rejection.
michael@0 1007 return {mustFind: ["test_rejection_closure", salt], error: salt};
michael@0 1008 };
michael@0 1009 let make_undefined_rejection = function make_undefined_rejection() {
michael@0 1010 // Our error is not Error-like nor an nsIException, so the stack will
michael@0 1011 // include the closure doing the actual rejection.
michael@0 1012 return {mustFind: ["test_rejection_closure"], error: undefined};
michael@0 1013 };
michael@0 1014 let make_error_rejection = function make_error_rejection() {
michael@0 1015 let salt = (Math.random() * ( Math.pow(2, 24) - 1 ));
michael@0 1016 let error = new Error("This is an uncaught error " + salt);
michael@0 1017 return {
michael@0 1018 mustFind: [error.message, error.fileName, error.lineNumber, error.stack],
michael@0 1019 error: error
michael@0 1020 };
michael@0 1021 };
michael@0 1022 let make_exception_rejection = function make_exception_rejection() {
michael@0 1023 let salt = (Math.random() * ( Math.pow(2, 24) - 1 ));
michael@0 1024 let exn = new Components.Exception("This is an uncaught exception " + salt,
michael@0 1025 Components.results.NS_ERROR_NOT_AVAILABLE);
michael@0 1026 return {
michael@0 1027 mustFind: [exn.message, exn.filename, exn.lineNumber, exn.location.toString()],
michael@0 1028 error: exn
michael@0 1029 };
michael@0 1030 };
michael@0 1031 for (let make_rejection of [make_string_rejection,
michael@0 1032 make_num_rejection,
michael@0 1033 make_undefined_rejection,
michael@0 1034 make_error_rejection,
michael@0 1035 make_exception_rejection]) {
michael@0 1036 let {mustFind, error} = make_rejection();
michael@0 1037 let name = make_rejection.name;
michael@0 1038 tests.push(make_promise_test(function test_uncaught_is_reported() {
michael@0 1039 do_print("Testing with rejection " + name);
michael@0 1040 let promise = wait_for_uncaught(mustFind);
michael@0 1041 (function test_rejection_closure() {
michael@0 1042 // For the moment, we cannot be absolutely certain that a value is
michael@0 1043 // garbage-collected, even if it is not referenced anymore, due to
michael@0 1044 // the conservative stack-scanning algorithm.
michael@0 1045 //
michael@0 1046 // To be _almost_ certain that a value will be garbage-collected, we
michael@0 1047 // 1. isolate that value in an anonymous closure;
michael@0 1048 // 2. allocate 100 values instead of 1 (gc-ing a single value from
michael@0 1049 // these is sufficient for the test);
michael@0 1050 // 3. place everything in a loop, as the JIT typically reuses memory;
michael@0 1051 // 4. call all the GC methods we can.
michael@0 1052 //
michael@0 1053 // Unfortunately, we might still have intermittent failures,
michael@0 1054 // materialized as timeouts.
michael@0 1055 //
michael@0 1056 for (let i = 0; i < 100; ++i) {
michael@0 1057 Promise.reject(error);
michael@0 1058 }
michael@0 1059 })();
michael@0 1060 do_print("Posted all rejections");
michael@0 1061 Components.utils.forceGC();
michael@0 1062 Components.utils.forceCC();
michael@0 1063 Components.utils.forceShrinkingGC();
michael@0 1064 return promise;
michael@0 1065 }));
michael@0 1066 }
michael@0 1067 })();
michael@0 1068
michael@0 1069
michael@0 1070 // Test that caught errors are not reported as uncaught
michael@0 1071 tests.push(
michael@0 1072 make_promise_test(function test_caught_is_not_reported() {
michael@0 1073 let salt = (Math.random() * ( Math.pow(2, 24) - 1 ));
michael@0 1074 let promise = wait_for_uncaught([salt], 500);
michael@0 1075 (function() {
michael@0 1076 let uncaught = Promise.reject("This error, on the other hand, is caught " + salt);
michael@0 1077 uncaught.catch(function() { /* ignore rejection */});
michael@0 1078 uncaught = null;
michael@0 1079 })();
michael@0 1080 // Isolate this in a function to increase likelihood that the gc will
michael@0 1081 // realise that |uncaught| has remained uncaught.
michael@0 1082 Components.utils.forceGC();
michael@0 1083
michael@0 1084 return promise.then(function onSuccess() {
michael@0 1085 throw new Error("This error was caught and should not have been reported");
michael@0 1086 }, function onError() {
michael@0 1087 do_print("The caught error was not reported, all is fine");
michael@0 1088 }
michael@0 1089 );
michael@0 1090 }));
michael@0 1091
michael@0 1092 function run_test()
michael@0 1093 {
michael@0 1094 do_test_pending();
michael@0 1095 run_promise_tests(tests, do_test_finished);
michael@0 1096 }

mercurial