toolkit/modules/tests/xpcshell/test_Promise.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/modules/tests/xpcshell/test_Promise.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1096 @@
     1.4 +/* Any copyright is dedicated to the Public Domain.
     1.5 + * http://creativecommons.org/publicdomain/zero/1.0/ */
     1.6 +"use strict";
     1.7 +
     1.8 +Components.utils.import("resource://gre/modules/Promise.jsm");
     1.9 +Components.utils.import("resource://gre/modules/Services.jsm");
    1.10 +Components.utils.import("resource://gre/modules/Task.jsm");
    1.11 +
    1.12 +// Deactivate the standard xpcshell observer, as it turns uncaught
    1.13 +// rejections into failures, which we don't want here.
    1.14 +Promise.Debugging.clearUncaughtErrorObservers();
    1.15 +
    1.16 +////////////////////////////////////////////////////////////////////////////////
    1.17 +//// Test runner
    1.18 +
    1.19 +let run_promise_tests = function run_promise_tests(tests, cb) {
    1.20 +  let loop = function loop(index) {
    1.21 +    if (index >= tests.length) {
    1.22 +      if (cb) {
    1.23 +        cb.call();
    1.24 +      }
    1.25 +      return;
    1.26 +    }
    1.27 +    do_print("Launching test " + (index + 1) + "/" + tests.length);
    1.28 +    let test = tests[index];
    1.29 +    // Execute from an empty stack
    1.30 +    let next = function next() {
    1.31 +      do_print("Test " + (index + 1) + "/" + tests.length + " complete");
    1.32 +      do_execute_soon(function() {
    1.33 +        loop(index + 1);
    1.34 +      });
    1.35 +    };
    1.36 +    let result = test();
    1.37 +    result.then(next, next);
    1.38 +  };
    1.39 +  return loop(0);
    1.40 +};
    1.41 +
    1.42 +let make_promise_test = function(test) {
    1.43 +  return function runtest() {
    1.44 +    do_print("Test starting: " + test.name);
    1.45 +    try {
    1.46 +      let result = test();
    1.47 +      if (result && "promise" in result) {
    1.48 +        result = result.promise;
    1.49 +      }
    1.50 +      if (!result || !("then" in result)) {
    1.51 +        let exn;
    1.52 +        try {
    1.53 +          do_throw("Test " + test.name + " did not return a promise: " + result);
    1.54 +        } catch (x) {
    1.55 +          exn = x;
    1.56 +        }
    1.57 +        return Promise.reject(exn);
    1.58 +      }
    1.59 +      // The test returns a promise
    1.60 +      result = result.then(
    1.61 +        // Test complete
    1.62 +        function onResolve() {
    1.63 +          do_print("Test complete: " + test.name);
    1.64 +        },
    1.65 +        // The test failed with an unexpected error
    1.66 +        function onReject(err) {
    1.67 +          let detail;
    1.68 +          if (err && typeof err == "object" && "stack" in err) {
    1.69 +            detail = err.stack;
    1.70 +          } else {
    1.71 +            detail = "(no stack)";
    1.72 +          }
    1.73 +          do_throw("Test " + test.name + " rejected with the following reason: "
    1.74 +              + err + detail);
    1.75 +      });
    1.76 +      return result;
    1.77 +    } catch (x) {
    1.78 +      // The test failed because of an error outside of a promise
    1.79 +      do_throw("Error in body of test " + test.name + ": " + x + " at " + x.stack);
    1.80 +      return Promise.reject();
    1.81 +    }
    1.82 +  };
    1.83 +};
    1.84 +
    1.85 +////////////////////////////////////////////////////////////////////////////////
    1.86 +//// Tests
    1.87 +
    1.88 +let tests = [];
    1.89 +
    1.90 +// Utility function to observe an failures in a promise
    1.91 +// This function is useful if the promise itself is
    1.92 +// not returned.
    1.93 +let observe_failures = function observe_failures(promise) {
    1.94 +  promise.catch(function onReject(reason) {
    1.95 +    test.do_throw("Observed failure in test " + test + ": " + reason);
    1.96 +  });
    1.97 +};
    1.98 +
    1.99 +// Test that all observers are notified
   1.100 +tests.push(make_promise_test(
   1.101 +  function notification(test) {
   1.102 +    // The size of the test
   1.103 +    const SIZE = 10;
   1.104 +    const RESULT = "this is an arbitrary value";
   1.105 +
   1.106 +    // Number of observers that yet need to be notified
   1.107 +    let expected = SIZE;
   1.108 +
   1.109 +    // |true| once an observer has been notified
   1.110 +    let notified = [];
   1.111 +
   1.112 +    // The promise observed
   1.113 +    let source = Promise.defer();
   1.114 +    let result = Promise.defer();
   1.115 +
   1.116 +    let install_observer = function install_observer(i) {
   1.117 +      observe_failures(source.promise.then(
   1.118 +        function onSuccess(value) {
   1.119 +          do_check_true(!notified[i], "Ensuring that observer is notified at most once");
   1.120 +          notified[i] = true;
   1.121 +
   1.122 +          do_check_eq(value, RESULT, "Ensuring that the observed value is correct");
   1.123 +          if (--expected == 0) {
   1.124 +            result.resolve();
   1.125 +          }
   1.126 +        }));
   1.127 +    };
   1.128 +
   1.129 +    // Install a number of observers before resolving
   1.130 +    let i;
   1.131 +    for (i = 0; i < SIZE/2; ++i) {
   1.132 +      install_observer(i);
   1.133 +    }
   1.134 +
   1.135 +    source.resolve(RESULT);
   1.136 +
   1.137 +    // Install remaining observers
   1.138 +    for(;i < SIZE; ++i) {
   1.139 +      install_observer(i);
   1.140 +    }
   1.141 +
   1.142 +    return result;
   1.143 +  }));
   1.144 +
   1.145 +// Test that observers get the correct "this" value in strict mode.
   1.146 +tests.push(
   1.147 +  make_promise_test(function handlers_this_value(test) {
   1.148 +    return Promise.resolve().then(
   1.149 +      function onResolve() {
   1.150 +        // Since this file is in strict mode, the correct value is "undefined".
   1.151 +        do_check_eq(this, undefined);
   1.152 +        throw "reject";
   1.153 +      }
   1.154 +    ).then(
   1.155 +      null,
   1.156 +      function onReject() {
   1.157 +        // Since this file is in strict mode, the correct value is "undefined".
   1.158 +        do_check_eq(this, undefined);
   1.159 +      }
   1.160 +    );
   1.161 +  }));
   1.162 +
   1.163 +// Test that observers registered on a pending promise are notified in order.
   1.164 +tests.push(
   1.165 +  make_promise_test(function then_returns_before_callbacks(test) {
   1.166 +    let deferred = Promise.defer();
   1.167 +    let promise = deferred.promise;
   1.168 +
   1.169 +    let order = 0;
   1.170 +
   1.171 +    promise.then(
   1.172 +      function onResolve() {
   1.173 +        do_check_eq(order, 0);
   1.174 +        order++;
   1.175 +      }
   1.176 +    );
   1.177 +
   1.178 +    promise.then(
   1.179 +      function onResolve() {
   1.180 +        do_check_eq(order, 1);
   1.181 +        order++;
   1.182 +      }
   1.183 +    );
   1.184 +
   1.185 +    let newPromise = promise.then(
   1.186 +      function onResolve() {
   1.187 +        do_check_eq(order, 2);
   1.188 +      }
   1.189 +    );
   1.190 +
   1.191 +    deferred.resolve();
   1.192 +
   1.193 +    // This test finishes after the last handler succeeds.
   1.194 +    return newPromise;
   1.195 +  }));
   1.196 +
   1.197 +// Test that observers registered on a resolved promise are notified in order.
   1.198 +tests.push(
   1.199 +  make_promise_test(function then_returns_before_callbacks(test) {
   1.200 +    let promise = Promise.resolve();
   1.201 +
   1.202 +    let order = 0;
   1.203 +
   1.204 +    promise.then(
   1.205 +      function onResolve() {
   1.206 +        do_check_eq(order, 0);
   1.207 +        order++;
   1.208 +      }
   1.209 +    );
   1.210 +
   1.211 +    promise.then(
   1.212 +      function onResolve() {
   1.213 +        do_check_eq(order, 1);
   1.214 +        order++;
   1.215 +      }
   1.216 +    );
   1.217 +
   1.218 +    // This test finishes after the last handler succeeds.
   1.219 +    return promise.then(
   1.220 +      function onResolve() {
   1.221 +        do_check_eq(order, 2);
   1.222 +      }
   1.223 +    );
   1.224 +  }));
   1.225 +
   1.226 +// Test that all observers are notified at most once, even if source
   1.227 +// is resolved/rejected several times
   1.228 +tests.push(make_promise_test(
   1.229 +  function notification_once(test) {
   1.230 +    // The size of the test
   1.231 +    const SIZE = 10;
   1.232 +    const RESULT = "this is an arbitrary value";
   1.233 +
   1.234 +    // Number of observers that yet need to be notified
   1.235 +    let expected = SIZE;
   1.236 +
   1.237 +    // |true| once an observer has been notified
   1.238 +    let notified = [];
   1.239 +
   1.240 +    // The promise observed
   1.241 +    let observed = Promise.defer();
   1.242 +    let result = Promise.defer();
   1.243 +
   1.244 +    let install_observer = function install_observer(i) {
   1.245 +      observe_failures(observed.promise.then(
   1.246 +        function onSuccess(value) {
   1.247 +          do_check_true(!notified[i], "Ensuring that observer is notified at most once");
   1.248 +          notified[i] = true;
   1.249 +
   1.250 +          do_check_eq(value, RESULT, "Ensuring that the observed value is correct");
   1.251 +          if (--expected == 0) {
   1.252 +            result.resolve();
   1.253 +          }
   1.254 +        }));
   1.255 +    };
   1.256 +
   1.257 +    // Install a number of observers before resolving
   1.258 +    let i;
   1.259 +    for (i = 0; i < SIZE/2; ++i) {
   1.260 +      install_observer(i);
   1.261 +    }
   1.262 +
   1.263 +    observed.resolve(RESULT);
   1.264 +
   1.265 +    // Install remaining observers
   1.266 +    for(;i < SIZE; ++i) {
   1.267 +      install_observer(i);
   1.268 +    }
   1.269 +
   1.270 +    // Resolve some more
   1.271 +    for (i = 0; i < 10; ++i) {
   1.272 +      observed.resolve(RESULT);
   1.273 +      observed.reject();
   1.274 +    }
   1.275 +
   1.276 +    return result;
   1.277 +  }));
   1.278 +
   1.279 +// Test that throwing an exception from a onResolve listener
   1.280 +// does not prevent other observers from receiving the notification
   1.281 +// of success.
   1.282 +tests.push(
   1.283 +  make_promise_test(function exceptions_do_not_stop_notifications(test)  {
   1.284 +    let source = Promise.defer();
   1.285 +
   1.286 +    let exception_thrown = false;
   1.287 +    let exception_content = new Error("Boom!");
   1.288 +
   1.289 +    let observer_1 = source.promise.then(
   1.290 +      function onResolve() {
   1.291 +        exception_thrown = true;
   1.292 +        throw exception_content;
   1.293 +      });
   1.294 +
   1.295 +    let observer_2 = source.promise.then(
   1.296 +      function onResolve() {
   1.297 +        do_check_true(exception_thrown, "Second observer called after first observer has thrown");
   1.298 +      }
   1.299 +    );
   1.300 +
   1.301 +    let result = observer_1.then(
   1.302 +      function onResolve() {
   1.303 +        do_throw("observer_1 should not have resolved");
   1.304 +      },
   1.305 +      function onReject(reason) {
   1.306 +        do_check_true(reason == exception_content, "Obtained correct rejection");
   1.307 +      }
   1.308 +    );
   1.309 +
   1.310 +    source.resolve();
   1.311 +    return result;
   1.312 +  }
   1.313 +));
   1.314 +
   1.315 +// Test that, once a promise is resolved, further resolve/reject
   1.316 +// are ignored.
   1.317 +tests.push(
   1.318 +  make_promise_test(function subsequent_resolves_are_ignored(test) {
   1.319 +    let deferred = Promise.defer();
   1.320 +    deferred.resolve(1);
   1.321 +    deferred.resolve(2);
   1.322 +    deferred.reject(3);
   1.323 +
   1.324 +    let result = deferred.promise.then(
   1.325 +      function onResolve(value) {
   1.326 +        do_check_eq(value, 1, "Resolution chose the first value");
   1.327 +      },
   1.328 +      function onReject(reason) {
   1.329 +        do_throw("Obtained a rejection while the promise was already resolved");
   1.330 +      }
   1.331 +    );
   1.332 +
   1.333 +    return result;
   1.334 +  }));
   1.335 +
   1.336 +// Test that, once a promise is rejected, further resolve/reject
   1.337 +// are ignored.
   1.338 +tests.push(
   1.339 +  make_promise_test(function subsequent_rejects_are_ignored(test) {
   1.340 +    let deferred = Promise.defer();
   1.341 +    deferred.reject(1);
   1.342 +    deferred.reject(2);
   1.343 +    deferred.resolve(3);
   1.344 +
   1.345 +    let result = deferred.promise.then(
   1.346 +      function onResolve() {
   1.347 +        do_throw("Obtained a resolution while the promise was already rejected");
   1.348 +      },
   1.349 +      function onReject(reason) {
   1.350 +        do_check_eq(reason, 1, "Rejection chose the first value");
   1.351 +      }
   1.352 +    );
   1.353 +
   1.354 +    return result;
   1.355 +  }));
   1.356 +
   1.357 +// Test that returning normally from a rejection recovers from the error
   1.358 +// and that listeners are informed of a success.
   1.359 +tests.push(
   1.360 +  make_promise_test(function recovery(test) {
   1.361 +    let boom = new Error("Boom!");
   1.362 +    let deferred = Promise.defer();
   1.363 +    const RESULT = "An arbitrary value";
   1.364 +
   1.365 +    let promise = deferred.promise.then(
   1.366 +      function onResolve() {
   1.367 +        do_throw("A rejected promise should not resolve");
   1.368 +      },
   1.369 +      function onReject(reason) {
   1.370 +        do_check_true(reason == boom, "Promise was rejected with the correct error");
   1.371 +        return RESULT;
   1.372 +      }
   1.373 +    );
   1.374 +
   1.375 +    promise = promise.then(
   1.376 +      function onResolve(value) {
   1.377 +        do_check_eq(value, RESULT, "Promise was recovered with the correct value");
   1.378 +      }
   1.379 +    );
   1.380 +
   1.381 +    deferred.reject(boom);
   1.382 +    return promise;
   1.383 +  }));
   1.384 +
   1.385 +// Test that returning a resolved promise from a onReject causes a resolution
   1.386 +// (recovering from the error) and that returning a rejected promise
   1.387 +// from a onResolve listener causes a rejection (raising an error).
   1.388 +tests.push(
   1.389 +  make_promise_test(function recovery_with_promise(test) {
   1.390 +    let boom = new Error("Arbitrary error");
   1.391 +    let deferred = Promise.defer();
   1.392 +    const RESULT = "An arbitrary value";
   1.393 +    const boom2 = new Error("Another arbitrary error");
   1.394 +
   1.395 +    // return a resolved promise from a onReject listener
   1.396 +    let promise = deferred.promise.then(
   1.397 +      function onResolve() {
   1.398 +        do_throw("A rejected promise should not resolve");
   1.399 +      },
   1.400 +      function onReject(reason) {
   1.401 +        do_check_true(reason == boom, "Promise was rejected with the correct error");
   1.402 +        return Promise.resolve(RESULT);
   1.403 +      }
   1.404 +    );
   1.405 +
   1.406 +    // return a rejected promise from a onResolve listener
   1.407 +    promise = promise.then(
   1.408 +      function onResolve(value) {
   1.409 +        do_check_eq(value, RESULT, "Promise was recovered with the correct value");
   1.410 +        return Promise.reject(boom2);
   1.411 +      }
   1.412 +    );
   1.413 +
   1.414 +    promise = promise.catch(
   1.415 +      function onReject(reason) {
   1.416 +        do_check_eq(reason, boom2, "Rejection was propagated with the correct " +
   1.417 +                "reason, through a promise");
   1.418 +      }
   1.419 +    );
   1.420 +
   1.421 +    deferred.reject(boom);
   1.422 +    return promise;
   1.423 +  }));
   1.424 +
   1.425 +// Test that we can resolve with promises of promises
   1.426 +tests.push(
   1.427 +  make_promise_test(function test_propagation(test) {
   1.428 +    const RESULT = "Yet another arbitrary value";
   1.429 +    let d1 = Promise.defer();
   1.430 +    let d2 = Promise.defer();
   1.431 +    let d3 = Promise.defer();
   1.432 +
   1.433 +    d3.resolve(d2.promise);
   1.434 +    d2.resolve(d1.promise);
   1.435 +    d1.resolve(RESULT);
   1.436 +
   1.437 +    return d3.promise.then(
   1.438 +      function onSuccess(value) {
   1.439 +        do_check_eq(value, RESULT, "Resolution with a promise eventually yielded "
   1.440 +                + " the correct result");
   1.441 +      }
   1.442 +    );
   1.443 +  }));
   1.444 +
   1.445 +// Test sequences of |then| and |catch|
   1.446 +tests.push(
   1.447 +  make_promise_test(function test_chaining(test) {
   1.448 +    let error_1 = new Error("Error 1");
   1.449 +    let error_2 = new Error("Error 2");
   1.450 +    let result_1 = "First result";
   1.451 +    let result_2 = "Second result";
   1.452 +    let result_3 = "Third result";
   1.453 +
   1.454 +    let source = Promise.defer();
   1.455 +
   1.456 +    let promise = source.promise.then().then();
   1.457 +
   1.458 +    source.resolve(result_1);
   1.459 +
   1.460 +    // Check that result_1 is correctly propagated
   1.461 +    promise = promise.then(
   1.462 +      function onSuccess(result) {
   1.463 +        do_check_eq(result, result_1, "Result was propagated correctly through " +
   1.464 +                " several applications of |then|");
   1.465 +        return result_2;
   1.466 +      }
   1.467 +    );
   1.468 +
   1.469 +    // Check that returning from the promise produces a resolution
   1.470 +    promise = promise.catch(
   1.471 +      function onReject() {
   1.472 +        do_throw("Incorrect rejection");
   1.473 +      }
   1.474 +    );
   1.475 +
   1.476 +    // ... and that the check did not alter the value
   1.477 +    promise = promise.then(
   1.478 +      function onResolve(value) {
   1.479 +        do_check_eq(value, result_2, "Result was propagated correctly once again");
   1.480 +      }
   1.481 +    );
   1.482 +
   1.483 +    // Now the same kind of tests for rejections
   1.484 +    promise = promise.then(
   1.485 +      function onResolve() {
   1.486 +        throw error_1;
   1.487 +      }
   1.488 +    );
   1.489 +
   1.490 +    promise = promise.then(
   1.491 +      function onResolve() {
   1.492 +        do_throw("Incorrect resolution: the exception should have caused a rejection");
   1.493 +      }
   1.494 +    );
   1.495 +
   1.496 +    promise = promise.catch(
   1.497 +      function onReject(reason) {
   1.498 +        do_check_true(reason == error_1, "Reason was propagated correctly");
   1.499 +        throw error_2;
   1.500 +      }
   1.501 +    );
   1.502 +
   1.503 +    promise = promise.catch(
   1.504 +      function onReject(reason) {
   1.505 +        do_check_true(reason == error_2, "Throwing an error altered the reason " +
   1.506 +            "as expected");
   1.507 +        return result_3;
   1.508 +      }
   1.509 +    );
   1.510 +
   1.511 +    promise = promise.then(
   1.512 +      function onResolve(result) {
   1.513 +        do_check_eq(result, result_3, "Error was correctly recovered");
   1.514 +      }
   1.515 +    );
   1.516 +
   1.517 +    return promise;
   1.518 +  }));
   1.519 +
   1.520 +// Test that resolving with a rejected promise actually rejects
   1.521 +tests.push(
   1.522 +  make_promise_test(function resolve_to_rejected(test) {
   1.523 +    let source = Promise.defer();
   1.524 +    let error = new Error("Boom");
   1.525 +
   1.526 +    let promise = source.promise.then(
   1.527 +      function onResolve() {
   1.528 +        do_throw("Incorrect call to onResolve listener");
   1.529 +      },
   1.530 +      function onReject(reason) {
   1.531 +        do_check_eq(reason, error, "Rejection lead to the expected reason");
   1.532 +      }
   1.533 +    );
   1.534 +
   1.535 +    source.resolve(Promise.reject(error));
   1.536 +
   1.537 +    return promise;
   1.538 +  }));
   1.539 +
   1.540 +// Test that Promise.resolve resolves as expected
   1.541 +tests.push(
   1.542 +  make_promise_test(function test_resolve(test) {
   1.543 +    const RESULT = "arbitrary value";
   1.544 +    let p1 = Promise.resolve(RESULT);
   1.545 +    let p2 = Promise.resolve(p1);
   1.546 +    do_check_eq(p1, p2, "Promise.resolve used on a promise just returns the promise");
   1.547 +
   1.548 +    return p1.then(
   1.549 +      function onResolve(result) {
   1.550 +        do_check_eq(result, RESULT, "Promise.resolve propagated the correct result");
   1.551 +      }
   1.552 +    );
   1.553 +  }));
   1.554 +
   1.555 +// Test that Promise.resolve throws when its argument is an async function.
   1.556 +tests.push(
   1.557 +  make_promise_test(function test_promise_resolve_throws_with_async_function(test) {
   1.558 +    Assert.throws(() => Promise.resolve(Task.async(function* () {})),
   1.559 +                  /Cannot resolve a promise with an async function/);
   1.560 +    return Promise.resolve();
   1.561 +  }));
   1.562 +
   1.563 +// Test that the code after "then" is always executed before the callbacks
   1.564 +tests.push(
   1.565 +  make_promise_test(function then_returns_before_callbacks(test) {
   1.566 +    let promise = Promise.resolve();
   1.567 +
   1.568 +    let thenExecuted = false;
   1.569 +
   1.570 +    promise = promise.then(
   1.571 +      function onResolve() {
   1.572 +        thenExecuted = true;
   1.573 +      }
   1.574 +    );
   1.575 +
   1.576 +    do_check_false(thenExecuted);
   1.577 +
   1.578 +    return promise;
   1.579 +  }));
   1.580 +
   1.581 +// Test that chaining promises does not generate long stack traces
   1.582 +tests.push(
   1.583 +  make_promise_test(function chaining_short_stack(test) {
   1.584 +    let source = Promise.defer();
   1.585 +    let promise = source.promise;
   1.586 +
   1.587 +    const NUM_ITERATIONS = 100;
   1.588 +
   1.589 +    for (let i = 0; i < NUM_ITERATIONS; i++) {
   1.590 +      promise = promise.then(
   1.591 +        function onResolve(result) {
   1.592 +          return result + ".";
   1.593 +        }
   1.594 +      );
   1.595 +    }
   1.596 +
   1.597 +    promise = promise.then(
   1.598 +      function onResolve(result) {
   1.599 +        // Check that the execution went as expected.
   1.600 +        let expectedString = new Array(1 + NUM_ITERATIONS).join(".");
   1.601 +        do_check_true(result == expectedString);
   1.602 +
   1.603 +        // Check that we didn't generate one or more stack frames per iteration.
   1.604 +        let stackFrameCount = 0;
   1.605 +        let stackFrame = Components.stack;
   1.606 +        while (stackFrame) {
   1.607 +          stackFrameCount++;
   1.608 +          stackFrame = stackFrame.caller;
   1.609 +        }
   1.610 +
   1.611 +        do_check_true(stackFrameCount < NUM_ITERATIONS);
   1.612 +      }
   1.613 +    );
   1.614 +
   1.615 +    source.resolve("");
   1.616 +
   1.617 +    return promise;
   1.618 +  }));
   1.619 +
   1.620 +// Test that the values of the promise return by Promise.all() are kept in the
   1.621 +// given order even if the given promises are resolved in arbitrary order
   1.622 +tests.push(
   1.623 +  make_promise_test(function all_resolve(test) {
   1.624 +    let d1 = Promise.defer();
   1.625 +    let d2 = Promise.defer();
   1.626 +    let d3 = Promise.defer();
   1.627 +
   1.628 +    d3.resolve(4);
   1.629 +    d2.resolve(2);
   1.630 +    do_execute_soon(() => d1.resolve(1));
   1.631 +
   1.632 +    let promises = [d1.promise, d2.promise, 3, d3.promise];
   1.633 +
   1.634 +    return Promise.all(promises).then(
   1.635 +      function onResolve([val1, val2, val3, val4]) {
   1.636 +        do_check_eq(val1, 1);
   1.637 +        do_check_eq(val2, 2);
   1.638 +        do_check_eq(val3, 3);
   1.639 +        do_check_eq(val4, 4);
   1.640 +      }
   1.641 +    );
   1.642 +  }));
   1.643 +
   1.644 +// Test that rejecting one of the promises passed to Promise.all()
   1.645 +// rejects the promise return by Promise.all()
   1.646 +tests.push(
   1.647 +  make_promise_test(function all_reject(test) {
   1.648 +    let error = new Error("Boom");
   1.649 +
   1.650 +    let d1 = Promise.defer();
   1.651 +    let d2 = Promise.defer();
   1.652 +    let d3 = Promise.defer();
   1.653 +
   1.654 +    d3.resolve(3);
   1.655 +    d2.resolve(2);
   1.656 +    do_execute_soon(() => d1.reject(error));
   1.657 +
   1.658 +    let promises = [d1.promise, d2.promise, d3.promise];
   1.659 +
   1.660 +    return Promise.all(promises).then(
   1.661 +      function onResolve() {
   1.662 +        do_throw("Incorrect call to onResolve listener");
   1.663 +      },
   1.664 +      function onReject(reason) {
   1.665 +        do_check_eq(reason, error, "Rejection lead to the expected reason");
   1.666 +      }
   1.667 +    );
   1.668 +  }));
   1.669 +
   1.670 +// Test that passing only values (not promises) to Promise.all()
   1.671 +// forwards them all as resolution values.
   1.672 +tests.push(
   1.673 +  make_promise_test(function all_resolve_no_promises(test) {
   1.674 +    try {
   1.675 +      Promise.all(null);
   1.676 +      do_check_true(false, "all() should only accept iterables");
   1.677 +    } catch (e) {
   1.678 +      do_check_true(true, "all() fails when first the arg is not an iterable");
   1.679 +    }
   1.680 +
   1.681 +    let p1 = Promise.all([]).then(
   1.682 +      function onResolve(val) {
   1.683 +        do_check_true(Array.isArray(val) && val.length == 0);
   1.684 +      }
   1.685 +    );
   1.686 +
   1.687 +    let p2 = Promise.all([1, 2, 3]).then(
   1.688 +      function onResolve([val1, val2, val3]) {
   1.689 +        do_check_eq(val1, 1);
   1.690 +        do_check_eq(val2, 2);
   1.691 +        do_check_eq(val3, 3);
   1.692 +      }
   1.693 +    );
   1.694 +
   1.695 +    return Promise.all([p1, p2]);
   1.696 +  }));
   1.697 +
   1.698 +// Test that Promise.all() handles non-array iterables
   1.699 +tests.push(
   1.700 +  make_promise_test(function all_iterable(test) {
   1.701 +    function* iterable() {
   1.702 +      yield 1;
   1.703 +      yield 2;
   1.704 +      yield 3;
   1.705 +    }
   1.706 +
   1.707 +    return Promise.all(iterable()).then(
   1.708 +      function onResolve([val1, val2, val3]) {
   1.709 +        do_check_eq(val1, 1);
   1.710 +        do_check_eq(val2, 2);
   1.711 +        do_check_eq(val3, 3);
   1.712 +      },
   1.713 +      function onReject() {
   1.714 +        do_throw("all() unexpectedly rejected");
   1.715 +      }
   1.716 +    );
   1.717 +  }));
   1.718 +
   1.719 +// Test that throwing from the iterable passed to Promise.all() rejects the
   1.720 +// promise returned by Promise.all()
   1.721 +tests.push(
   1.722 +  make_promise_test(function all_iterable_throws(test) {
   1.723 +    function* iterable() {
   1.724 +      throw 1;
   1.725 +    }
   1.726 +
   1.727 +    return Promise.all(iterable()).then(
   1.728 +      function onResolve() {
   1.729 +        do_throw("all() unexpectedly resolved");
   1.730 +      },
   1.731 +      function onReject(reason) {
   1.732 +        do_check_eq(reason, 1, "all() rejects when the iterator throws");
   1.733 +      }
   1.734 +    );
   1.735 +  }));
   1.736 +
   1.737 +// Test that Promise.race() resolves with the first available resolution value
   1.738 +tests.push(
   1.739 +  make_promise_test(function race_resolve(test) {
   1.740 +    let p1 = Promise.resolve(1);
   1.741 +    let p2 = Promise.resolve().then(() => 2);
   1.742 +
   1.743 +    return Promise.race([p1, p2]).then(
   1.744 +      function onResolve(value) {
   1.745 +        do_check_eq(value, 1);
   1.746 +      }
   1.747 +    );
   1.748 +  }));
   1.749 +
   1.750 +// Test that passing only values (not promises) to Promise.race() works
   1.751 +tests.push(
   1.752 +  make_promise_test(function race_resolve_no_promises(test) {
   1.753 +    try {
   1.754 +      Promise.race(null);
   1.755 +      do_check_true(false, "race() should only accept iterables");
   1.756 +    } catch (e) {
   1.757 +      do_check_true(true, "race() fails when first the arg is not an iterable");
   1.758 +    }
   1.759 +
   1.760 +    return Promise.race([1, 2, 3]).then(
   1.761 +      function onResolve(value) {
   1.762 +        do_check_eq(value, 1);
   1.763 +      }
   1.764 +    );
   1.765 +  }));
   1.766 +
   1.767 +// Test that Promise.race() never resolves when passed an empty iterable
   1.768 +tests.push(
   1.769 +  make_promise_test(function race_resolve_never(test) {
   1.770 +    return new Promise(resolve => {
   1.771 +      Promise.race([]).then(
   1.772 +        function onResolve() {
   1.773 +          do_throw("race() unexpectedly resolved");
   1.774 +        },
   1.775 +        function onReject() {
   1.776 +          do_throw("race() unexpectedly rejected");
   1.777 +        }
   1.778 +      );
   1.779 +
   1.780 +      // Approximate "never" so we don't have to solve the halting problem.
   1.781 +      do_timeout(200, resolve);
   1.782 +    });
   1.783 +  }));
   1.784 +
   1.785 +// Test that Promise.race() handles non-array iterables.
   1.786 +tests.push(
   1.787 +  make_promise_test(function race_iterable(test) {
   1.788 +    function* iterable() {
   1.789 +      yield 1;
   1.790 +      yield 2;
   1.791 +      yield 3;
   1.792 +    }
   1.793 +
   1.794 +    return Promise.race(iterable()).then(
   1.795 +      function onResolve(value) {
   1.796 +        do_check_eq(value, 1);
   1.797 +      },
   1.798 +      function onReject() {
   1.799 +        do_throw("race() unexpectedly rejected");
   1.800 +      }
   1.801 +    );
   1.802 +  }));
   1.803 +
   1.804 +// Test that throwing from the iterable passed to Promise.race() rejects the
   1.805 +// promise returned by Promise.race()
   1.806 +tests.push(
   1.807 +  make_promise_test(function race_iterable_throws(test) {
   1.808 +    function* iterable() {
   1.809 +      throw 1;
   1.810 +    }
   1.811 +
   1.812 +    return Promise.race(iterable()).then(
   1.813 +      function onResolve() {
   1.814 +        do_throw("race() unexpectedly resolved");
   1.815 +      },
   1.816 +      function onReject(reason) {
   1.817 +        do_check_eq(reason, 1, "race() rejects when the iterator throws");
   1.818 +      }
   1.819 +    );
   1.820 +  }));
   1.821 +
   1.822 +// Test that rejecting one of the promises passed to Promise.race() rejects the
   1.823 +// promise returned by Promise.race()
   1.824 +tests.push(
   1.825 +  make_promise_test(function race_reject(test) {
   1.826 +    let p1 = Promise.reject(1);
   1.827 +    let p2 = Promise.resolve(2);
   1.828 +    let p3 = Promise.resolve(3);
   1.829 +
   1.830 +    return Promise.race([p1, p2, p3]).then(
   1.831 +      function onResolve() {
   1.832 +        do_throw("race() unexpectedly resolved");
   1.833 +      },
   1.834 +      function onReject(reason) {
   1.835 +        do_check_eq(reason, 1, "race() rejects when given a rejected promise");
   1.836 +      }
   1.837 +    );
   1.838 +  }));
   1.839 +
   1.840 +// Test behavior of the Promise constructor.
   1.841 +tests.push(
   1.842 +  make_promise_test(function test_constructor(test) {
   1.843 +    try {
   1.844 +      new Promise(null);
   1.845 +      do_check_true(false, "Constructor should fail when not passed a function");
   1.846 +    } catch (e) {
   1.847 +      do_check_true(true, "Constructor fails when not passed a function");
   1.848 +    }
   1.849 +
   1.850 +    let executorRan = false;
   1.851 +    let promise = new Promise(
   1.852 +      function executor(resolve, reject) {
   1.853 +        executorRan = true;
   1.854 +        do_check_eq(this, undefined);
   1.855 +        do_check_eq(typeof resolve, "function",
   1.856 +                    "resolve function should be passed to the executor");
   1.857 +        do_check_eq(typeof reject, "function",
   1.858 +                    "reject function should be passed to the executor");
   1.859 +      }
   1.860 +    );
   1.861 +    do_check_instanceof(promise, Promise);
   1.862 +    do_check_true(executorRan, "Executor should execute synchronously");
   1.863 +
   1.864 +    // resolve a promise from the executor
   1.865 +    let resolvePromise = new Promise(
   1.866 +      function executor(resolve) {
   1.867 +        resolve(1);
   1.868 +      }
   1.869 +    ).then(
   1.870 +      function onResolve(value) {
   1.871 +        do_check_eq(value, 1, "Executor resolved with correct value");
   1.872 +      },
   1.873 +      function onReject() {
   1.874 +        do_throw("Executor unexpectedly rejected");
   1.875 +      }
   1.876 +    );
   1.877 +
   1.878 +    // reject a promise from the executor
   1.879 +    let rejectPromise = new Promise(
   1.880 +      function executor(_, reject) {
   1.881 +        reject(1);
   1.882 +      }
   1.883 +    ).then(
   1.884 +      function onResolve() {
   1.885 +        do_throw("Executor unexpectedly resolved");
   1.886 +      },
   1.887 +      function onReject(reason) {
   1.888 +        do_check_eq(reason, 1, "Executor rejected with correct value");
   1.889 +      }
   1.890 +    );
   1.891 +
   1.892 +    // throw from the executor, causing a rejection
   1.893 +    let throwPromise = new Promise(
   1.894 +      function executor() {
   1.895 +        throw 1;
   1.896 +      }
   1.897 +    ).then(
   1.898 +      function onResolve() {
   1.899 +        do_throw("Throwing inside an executor should not resolve the promise");
   1.900 +      },
   1.901 +      function onReject(reason) {
   1.902 +        do_check_eq(reason, 1, "Executor rejected with correct value");
   1.903 +      }
   1.904 +    );
   1.905 +
   1.906 +    return Promise.all([resolvePromise, rejectPromise, throwPromise]);
   1.907 +  }));
   1.908 +
   1.909 +// Test deadlock in Promise.jsm with nested event loops
   1.910 +// The scenario being tested is:
   1.911 +// promise_1.then({
   1.912 +//   do some work that will asynchronously signal done
   1.913 +//   start an event loop waiting for the done signal
   1.914 +// }
   1.915 +// where the async work uses resolution of a second promise to 
   1.916 +// trigger the "done" signal. While this would likely work in a
   1.917 +// naive implementation, our constant-stack implementation needs
   1.918 +// a special case to avoid deadlock. Note that this test is
   1.919 +// sensitive to the implementation-dependent order in which then()
   1.920 +// clauses for two different promises are executed, so it is
   1.921 +// possible for other implementations to pass this test and still
   1.922 +// have similar deadlocks.
   1.923 +tests.push(
   1.924 +  make_promise_test(function promise_nested_eventloop_deadlock(test) {
   1.925 +    // Set up a (long enough to be noticeable) timeout to
   1.926 +    // exit the nested event loop and throw if the test run is hung
   1.927 +    let shouldExitNestedEventLoop = false;
   1.928 +
   1.929 +    function event_loop() {
   1.930 +      let thr = Services.tm.mainThread;
   1.931 +      while(!shouldExitNestedEventLoop) {
   1.932 +        thr.processNextEvent(true);
   1.933 +      }
   1.934 +    }
   1.935 +
   1.936 +    // I wish there was a way to cancel xpcshell do_timeout()s
   1.937 +    do_timeout(2000, () => {
   1.938 +      if (!shouldExitNestedEventLoop) {
   1.939 +        shouldExitNestedEventLoop = true;
   1.940 +        do_throw("Test timed out");
   1.941 +      }
   1.942 +    });
   1.943 +
   1.944 +    let promise1 = Promise.resolve(1);
   1.945 +    let promise2 = Promise.resolve(2);
   1.946 +
   1.947 +    do_print("Setting wait for first promise");
   1.948 +    promise1.then(value => {
   1.949 +      do_print("Starting event loop");
   1.950 +      event_loop();
   1.951 +    }, null);
   1.952 +
   1.953 +    do_print("Setting wait for second promise");
   1.954 +    return promise2.catch(error => {return 3;})
   1.955 +    .then(
   1.956 +      count => {
   1.957 +        shouldExitNestedEventLoop = true;
   1.958 +      });
   1.959 +  }));
   1.960 +
   1.961 +function wait_for_uncaught(aMustAppear, aTimeout = undefined) {
   1.962 +  let remaining = new Set();
   1.963 +  for (let k of aMustAppear) {
   1.964 +    remaining.add(k);
   1.965 +  }
   1.966 +  let deferred = Promise.defer();
   1.967 +  let print = do_print;
   1.968 +  let execute_soon = do_execute_soon;
   1.969 +  let observer = function({message, stack}) {
   1.970 +    let data = message + stack;
   1.971 +    print("Observing " + message + ", looking for " + aMustAppear.join(", "));
   1.972 +    for (let expected of remaining) {
   1.973 +      if (data.indexOf(expected) != -1) {
   1.974 +        print("I found " + expected);
   1.975 +        remaining.delete(expected);
   1.976 +      }
   1.977 +      if (remaining.size == 0 && observer) {
   1.978 +        Promise.Debugging.removeUncaughtErrorObserver(observer);
   1.979 +        observer = null;
   1.980 +        deferred.resolve();
   1.981 +      }
   1.982 +    }
   1.983 +  };
   1.984 +  Promise.Debugging.addUncaughtErrorObserver(observer);
   1.985 +  if (aTimeout) {
   1.986 +    do_timeout(aTimeout, function timeout() {
   1.987 +      if (observer) {
   1.988 +        Promise.Debugging.removeUncaughtErrorObserver(observer);
   1.989 +        observer = null;
   1.990 +      }
   1.991 +      deferred.reject(new Error("Timeout"));
   1.992 +    });
   1.993 +  }
   1.994 +  return deferred.promise;
   1.995 +}
   1.996 +
   1.997 +// Test that uncaught errors are reported as uncaught
   1.998 +(function() {
   1.999 +  let make_string_rejection = function make_string_rejection() {
  1.1000 +    let salt = (Math.random() * ( Math.pow(2, 24) - 1 ));
  1.1001 +    let string = "This is an uncaught rejection " + salt;
  1.1002 +    // Our error is not Error-like nor an nsIException, so the stack will
  1.1003 +    // include the closure doing the actual rejection.
  1.1004 +    return {mustFind: ["test_rejection_closure", string], error: string};
  1.1005 +  };
  1.1006 +  let make_num_rejection = function make_num_rejection() {
  1.1007 +    let salt = (Math.random() * ( Math.pow(2, 24) - 1 ));
  1.1008 +    // Our error is not Error-like nor an nsIException, so the stack will
  1.1009 +    // include the closure doing the actual rejection.
  1.1010 +    return {mustFind: ["test_rejection_closure", salt], error: salt};
  1.1011 +  };
  1.1012 +  let make_undefined_rejection = function make_undefined_rejection() {
  1.1013 +    // Our error is not Error-like nor an nsIException, so the stack will
  1.1014 +    // include the closure doing the actual rejection.
  1.1015 +    return {mustFind: ["test_rejection_closure"], error: undefined};
  1.1016 +  };
  1.1017 +  let make_error_rejection = function make_error_rejection() {
  1.1018 +    let salt = (Math.random() * ( Math.pow(2, 24) - 1 ));
  1.1019 +    let error = new Error("This is an uncaught error " + salt);
  1.1020 +    return {
  1.1021 +      mustFind: [error.message, error.fileName, error.lineNumber, error.stack],
  1.1022 +      error: error
  1.1023 +    };
  1.1024 +  };
  1.1025 +  let make_exception_rejection = function make_exception_rejection() {
  1.1026 +    let salt = (Math.random() * ( Math.pow(2, 24) - 1 ));
  1.1027 +    let exn = new Components.Exception("This is an uncaught exception " + salt,
  1.1028 +                                       Components.results.NS_ERROR_NOT_AVAILABLE);
  1.1029 +    return {
  1.1030 +      mustFind: [exn.message, exn.filename, exn.lineNumber, exn.location.toString()],
  1.1031 +      error: exn
  1.1032 +    };
  1.1033 +  };
  1.1034 +  for (let make_rejection of [make_string_rejection,
  1.1035 +    make_num_rejection,
  1.1036 +    make_undefined_rejection,
  1.1037 +    make_error_rejection,
  1.1038 +    make_exception_rejection]) {
  1.1039 +      let {mustFind, error} = make_rejection();
  1.1040 +      let name = make_rejection.name;
  1.1041 +      tests.push(make_promise_test(function test_uncaught_is_reported() {
  1.1042 +        do_print("Testing with rejection " + name);
  1.1043 +        let promise = wait_for_uncaught(mustFind);
  1.1044 +        (function test_rejection_closure() {
  1.1045 +          // For the moment, we cannot be absolutely certain that a value is
  1.1046 +          // garbage-collected, even if it is not referenced anymore, due to
  1.1047 +          // the conservative stack-scanning algorithm.
  1.1048 +          //
  1.1049 +          // To be _almost_ certain that a value will be garbage-collected, we
  1.1050 +          // 1. isolate that value in an anonymous closure;
  1.1051 +          // 2. allocate 100 values instead of 1 (gc-ing a single value from
  1.1052 +          //    these is sufficient for the test);
  1.1053 +          // 3. place everything in a loop, as the JIT typically reuses memory;
  1.1054 +          // 4. call all the GC methods we can.
  1.1055 +          //
  1.1056 +          // Unfortunately, we might still have intermittent failures,
  1.1057 +          // materialized as timeouts.
  1.1058 +          //
  1.1059 +          for (let i = 0; i < 100; ++i) {
  1.1060 +            Promise.reject(error);
  1.1061 +          }
  1.1062 +        })();
  1.1063 +        do_print("Posted all rejections");
  1.1064 +        Components.utils.forceGC();
  1.1065 +        Components.utils.forceCC();
  1.1066 +        Components.utils.forceShrinkingGC();
  1.1067 +        return promise;
  1.1068 +      }));
  1.1069 +  }
  1.1070 +})();
  1.1071 +
  1.1072 +
  1.1073 +// Test that caught errors are not reported as uncaught
  1.1074 +tests.push(
  1.1075 +make_promise_test(function test_caught_is_not_reported() {
  1.1076 +  let salt = (Math.random() * ( Math.pow(2, 24) - 1 ));
  1.1077 +  let promise = wait_for_uncaught([salt], 500);
  1.1078 +  (function() {
  1.1079 +    let uncaught = Promise.reject("This error, on the other hand, is caught " + salt);
  1.1080 +    uncaught.catch(function() { /* ignore rejection */});
  1.1081 +    uncaught = null;
  1.1082 +  })();
  1.1083 +  // Isolate this in a function to increase likelihood that the gc will
  1.1084 +  // realise that |uncaught| has remained uncaught.
  1.1085 +  Components.utils.forceGC();
  1.1086 +
  1.1087 +  return promise.then(function onSuccess() {
  1.1088 +    throw new Error("This error was caught and should not have been reported");
  1.1089 +  }, function onError() {
  1.1090 +    do_print("The caught error was not reported, all is fine");
  1.1091 +  }
  1.1092 +  );
  1.1093 +}));
  1.1094 +
  1.1095 +function run_test()
  1.1096 +{
  1.1097 +  do_test_pending();
  1.1098 +  run_promise_tests(tests, do_test_finished);
  1.1099 +}

mercurial