|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 * http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 "use strict"; |
|
4 |
|
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"); |
|
8 |
|
9 // Deactivate the standard xpcshell observer, as it turns uncaught |
|
10 // rejections into failures, which we don't want here. |
|
11 Promise.Debugging.clearUncaughtErrorObservers(); |
|
12 |
|
13 //////////////////////////////////////////////////////////////////////////////// |
|
14 //// Test runner |
|
15 |
|
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 }; |
|
38 |
|
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 }; |
|
81 |
|
82 //////////////////////////////////////////////////////////////////////////////// |
|
83 //// Tests |
|
84 |
|
85 let tests = []; |
|
86 |
|
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 }; |
|
95 |
|
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"; |
|
102 |
|
103 // Number of observers that yet need to be notified |
|
104 let expected = SIZE; |
|
105 |
|
106 // |true| once an observer has been notified |
|
107 let notified = []; |
|
108 |
|
109 // The promise observed |
|
110 let source = Promise.defer(); |
|
111 let result = Promise.defer(); |
|
112 |
|
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; |
|
118 |
|
119 do_check_eq(value, RESULT, "Ensuring that the observed value is correct"); |
|
120 if (--expected == 0) { |
|
121 result.resolve(); |
|
122 } |
|
123 })); |
|
124 }; |
|
125 |
|
126 // Install a number of observers before resolving |
|
127 let i; |
|
128 for (i = 0; i < SIZE/2; ++i) { |
|
129 install_observer(i); |
|
130 } |
|
131 |
|
132 source.resolve(RESULT); |
|
133 |
|
134 // Install remaining observers |
|
135 for(;i < SIZE; ++i) { |
|
136 install_observer(i); |
|
137 } |
|
138 |
|
139 return result; |
|
140 })); |
|
141 |
|
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 })); |
|
159 |
|
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; |
|
165 |
|
166 let order = 0; |
|
167 |
|
168 promise.then( |
|
169 function onResolve() { |
|
170 do_check_eq(order, 0); |
|
171 order++; |
|
172 } |
|
173 ); |
|
174 |
|
175 promise.then( |
|
176 function onResolve() { |
|
177 do_check_eq(order, 1); |
|
178 order++; |
|
179 } |
|
180 ); |
|
181 |
|
182 let newPromise = promise.then( |
|
183 function onResolve() { |
|
184 do_check_eq(order, 2); |
|
185 } |
|
186 ); |
|
187 |
|
188 deferred.resolve(); |
|
189 |
|
190 // This test finishes after the last handler succeeds. |
|
191 return newPromise; |
|
192 })); |
|
193 |
|
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(); |
|
198 |
|
199 let order = 0; |
|
200 |
|
201 promise.then( |
|
202 function onResolve() { |
|
203 do_check_eq(order, 0); |
|
204 order++; |
|
205 } |
|
206 ); |
|
207 |
|
208 promise.then( |
|
209 function onResolve() { |
|
210 do_check_eq(order, 1); |
|
211 order++; |
|
212 } |
|
213 ); |
|
214 |
|
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 })); |
|
222 |
|
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"; |
|
230 |
|
231 // Number of observers that yet need to be notified |
|
232 let expected = SIZE; |
|
233 |
|
234 // |true| once an observer has been notified |
|
235 let notified = []; |
|
236 |
|
237 // The promise observed |
|
238 let observed = Promise.defer(); |
|
239 let result = Promise.defer(); |
|
240 |
|
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; |
|
246 |
|
247 do_check_eq(value, RESULT, "Ensuring that the observed value is correct"); |
|
248 if (--expected == 0) { |
|
249 result.resolve(); |
|
250 } |
|
251 })); |
|
252 }; |
|
253 |
|
254 // Install a number of observers before resolving |
|
255 let i; |
|
256 for (i = 0; i < SIZE/2; ++i) { |
|
257 install_observer(i); |
|
258 } |
|
259 |
|
260 observed.resolve(RESULT); |
|
261 |
|
262 // Install remaining observers |
|
263 for(;i < SIZE; ++i) { |
|
264 install_observer(i); |
|
265 } |
|
266 |
|
267 // Resolve some more |
|
268 for (i = 0; i < 10; ++i) { |
|
269 observed.resolve(RESULT); |
|
270 observed.reject(); |
|
271 } |
|
272 |
|
273 return result; |
|
274 })); |
|
275 |
|
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(); |
|
282 |
|
283 let exception_thrown = false; |
|
284 let exception_content = new Error("Boom!"); |
|
285 |
|
286 let observer_1 = source.promise.then( |
|
287 function onResolve() { |
|
288 exception_thrown = true; |
|
289 throw exception_content; |
|
290 }); |
|
291 |
|
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 ); |
|
297 |
|
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 ); |
|
306 |
|
307 source.resolve(); |
|
308 return result; |
|
309 } |
|
310 )); |
|
311 |
|
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); |
|
320 |
|
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 ); |
|
329 |
|
330 return result; |
|
331 })); |
|
332 |
|
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); |
|
341 |
|
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 ); |
|
350 |
|
351 return result; |
|
352 })); |
|
353 |
|
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"; |
|
361 |
|
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 ); |
|
371 |
|
372 promise = promise.then( |
|
373 function onResolve(value) { |
|
374 do_check_eq(value, RESULT, "Promise was recovered with the correct value"); |
|
375 } |
|
376 ); |
|
377 |
|
378 deferred.reject(boom); |
|
379 return promise; |
|
380 })); |
|
381 |
|
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"); |
|
391 |
|
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 ); |
|
402 |
|
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 ); |
|
410 |
|
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 ); |
|
417 |
|
418 deferred.reject(boom); |
|
419 return promise; |
|
420 })); |
|
421 |
|
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(); |
|
429 |
|
430 d3.resolve(d2.promise); |
|
431 d2.resolve(d1.promise); |
|
432 d1.resolve(RESULT); |
|
433 |
|
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 })); |
|
441 |
|
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"; |
|
450 |
|
451 let source = Promise.defer(); |
|
452 |
|
453 let promise = source.promise.then().then(); |
|
454 |
|
455 source.resolve(result_1); |
|
456 |
|
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 ); |
|
465 |
|
466 // Check that returning from the promise produces a resolution |
|
467 promise = promise.catch( |
|
468 function onReject() { |
|
469 do_throw("Incorrect rejection"); |
|
470 } |
|
471 ); |
|
472 |
|
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 ); |
|
479 |
|
480 // Now the same kind of tests for rejections |
|
481 promise = promise.then( |
|
482 function onResolve() { |
|
483 throw error_1; |
|
484 } |
|
485 ); |
|
486 |
|
487 promise = promise.then( |
|
488 function onResolve() { |
|
489 do_throw("Incorrect resolution: the exception should have caused a rejection"); |
|
490 } |
|
491 ); |
|
492 |
|
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 ); |
|
499 |
|
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 ); |
|
507 |
|
508 promise = promise.then( |
|
509 function onResolve(result) { |
|
510 do_check_eq(result, result_3, "Error was correctly recovered"); |
|
511 } |
|
512 ); |
|
513 |
|
514 return promise; |
|
515 })); |
|
516 |
|
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"); |
|
522 |
|
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 ); |
|
531 |
|
532 source.resolve(Promise.reject(error)); |
|
533 |
|
534 return promise; |
|
535 })); |
|
536 |
|
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"); |
|
544 |
|
545 return p1.then( |
|
546 function onResolve(result) { |
|
547 do_check_eq(result, RESULT, "Promise.resolve propagated the correct result"); |
|
548 } |
|
549 ); |
|
550 })); |
|
551 |
|
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 })); |
|
559 |
|
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(); |
|
564 |
|
565 let thenExecuted = false; |
|
566 |
|
567 promise = promise.then( |
|
568 function onResolve() { |
|
569 thenExecuted = true; |
|
570 } |
|
571 ); |
|
572 |
|
573 do_check_false(thenExecuted); |
|
574 |
|
575 return promise; |
|
576 })); |
|
577 |
|
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; |
|
583 |
|
584 const NUM_ITERATIONS = 100; |
|
585 |
|
586 for (let i = 0; i < NUM_ITERATIONS; i++) { |
|
587 promise = promise.then( |
|
588 function onResolve(result) { |
|
589 return result + "."; |
|
590 } |
|
591 ); |
|
592 } |
|
593 |
|
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); |
|
599 |
|
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 } |
|
607 |
|
608 do_check_true(stackFrameCount < NUM_ITERATIONS); |
|
609 } |
|
610 ); |
|
611 |
|
612 source.resolve(""); |
|
613 |
|
614 return promise; |
|
615 })); |
|
616 |
|
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(); |
|
624 |
|
625 d3.resolve(4); |
|
626 d2.resolve(2); |
|
627 do_execute_soon(() => d1.resolve(1)); |
|
628 |
|
629 let promises = [d1.promise, d2.promise, 3, d3.promise]; |
|
630 |
|
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 })); |
|
640 |
|
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"); |
|
646 |
|
647 let d1 = Promise.defer(); |
|
648 let d2 = Promise.defer(); |
|
649 let d3 = Promise.defer(); |
|
650 |
|
651 d3.resolve(3); |
|
652 d2.resolve(2); |
|
653 do_execute_soon(() => d1.reject(error)); |
|
654 |
|
655 let promises = [d1.promise, d2.promise, d3.promise]; |
|
656 |
|
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 })); |
|
666 |
|
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 } |
|
677 |
|
678 let p1 = Promise.all([]).then( |
|
679 function onResolve(val) { |
|
680 do_check_true(Array.isArray(val) && val.length == 0); |
|
681 } |
|
682 ); |
|
683 |
|
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 ); |
|
691 |
|
692 return Promise.all([p1, p2]); |
|
693 })); |
|
694 |
|
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 } |
|
703 |
|
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 })); |
|
715 |
|
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 } |
|
723 |
|
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 })); |
|
733 |
|
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); |
|
739 |
|
740 return Promise.race([p1, p2]).then( |
|
741 function onResolve(value) { |
|
742 do_check_eq(value, 1); |
|
743 } |
|
744 ); |
|
745 })); |
|
746 |
|
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 } |
|
756 |
|
757 return Promise.race([1, 2, 3]).then( |
|
758 function onResolve(value) { |
|
759 do_check_eq(value, 1); |
|
760 } |
|
761 ); |
|
762 })); |
|
763 |
|
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 ); |
|
776 |
|
777 // Approximate "never" so we don't have to solve the halting problem. |
|
778 do_timeout(200, resolve); |
|
779 }); |
|
780 })); |
|
781 |
|
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 } |
|
790 |
|
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 })); |
|
800 |
|
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 } |
|
808 |
|
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 })); |
|
818 |
|
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); |
|
826 |
|
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 })); |
|
836 |
|
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 } |
|
846 |
|
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"); |
|
860 |
|
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 ); |
|
874 |
|
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 ); |
|
888 |
|
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 ); |
|
902 |
|
903 return Promise.all([resolvePromise, rejectPromise, throwPromise]); |
|
904 })); |
|
905 |
|
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; |
|
925 |
|
926 function event_loop() { |
|
927 let thr = Services.tm.mainThread; |
|
928 while(!shouldExitNestedEventLoop) { |
|
929 thr.processNextEvent(true); |
|
930 } |
|
931 } |
|
932 |
|
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 }); |
|
940 |
|
941 let promise1 = Promise.resolve(1); |
|
942 let promise2 = Promise.resolve(2); |
|
943 |
|
944 do_print("Setting wait for first promise"); |
|
945 promise1.then(value => { |
|
946 do_print("Starting event loop"); |
|
947 event_loop(); |
|
948 }, null); |
|
949 |
|
950 do_print("Setting wait for second promise"); |
|
951 return promise2.catch(error => {return 3;}) |
|
952 .then( |
|
953 count => { |
|
954 shouldExitNestedEventLoop = true; |
|
955 }); |
|
956 })); |
|
957 |
|
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 } |
|
993 |
|
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); |
|
1058 } |
|
1059 })(); |
|
1060 do_print("Posted all rejections"); |
|
1061 Components.utils.forceGC(); |
|
1062 Components.utils.forceCC(); |
|
1063 Components.utils.forceShrinkingGC(); |
|
1064 return promise; |
|
1065 })); |
|
1066 } |
|
1067 })(); |
|
1068 |
|
1069 |
|
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(); |
|
1083 |
|
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"); |
|
1088 } |
|
1089 ); |
|
1090 })); |
|
1091 |
|
1092 function run_test() |
|
1093 { |
|
1094 do_test_pending(); |
|
1095 run_promise_tests(tests, do_test_finished); |
|
1096 } |