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.

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

mercurial