Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / remoting / webapp / unittests / spy_promise.js
blobfb0894ac0b6ef0a3c0c3ec9bd9b15fbf6ca17ae4
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 'use strict';
7 /** @suppress {duplicate} */
8 var base = base || {};
10 (function() {
11 /**
12  * A wrapper around a Promise object that keeps track of all
13  * outstanding promises.  This function is written to serve as a
14  * drop-in replacement for the native Promise constructor.  To create
15  * a SpyPromise from an existing native Promise, use
16  * SpyPromise.resolve.
17  *
18  * Note that this is a pseudo-constructor that actually returns a
19  * regular promise with appropriate handlers attached.  This detail
20  * should be transparent when SpyPromise.activate has been called.
21  *
22  * The normal way to use this class is within a call to
23  * SpyPromise.run, for example:
24  *
25  *   base.SpyPromise.run(function() {
26  *     myCodeThatUsesPromises();
27  *   });
28  *   base.SpyPromise.settleAll().then(function() {
29  *     console.log('All promises have been settled!');
30  *   });
31  *
32  * @constructor
33  * @extends {Promise}
34  * @param {function(function(?):?, function(*):?):?} func A function
35  *     of the same type used as an argument to the native Promise
36  *     constructor, in other words, a function which is called
37  *     immediately, and whose arguments are a resolve function and a
38  *     reject function.
39  */
40 base.SpyPromise = function(func) {
41   var unsettled = new RealPromise(func);
42   var unsettledId = remember(unsettled);
43   return unsettled.then(function(/** * */value) {
44     forget(unsettledId);
45     return value;
46   }, function(error) {
47     forget(unsettledId);
48     throw error;
49   });
52 /**
53  * The real promise constructor.  Needed because it is normally hidden
54  * by SpyPromise.activate or SpyPromise.run.
55  * @const
56  */
57 var RealPromise = Promise;
59 /**
60  * The real window.setTimeout method.  Needed because some test
61  * frameworks like to replace this method with a fake implementation.
62  * @const
63  */
64 var realSetTimeout = window.setTimeout.bind(window);
66 /**
67  * The number of unsettled promises.
68  * @type {number}
69  */
70 base.SpyPromise.unsettledCount;  // initialized by reset()
72 /**
73  * A collection of all unsettled promises.
74  * @type {!Object<number,!Promise>}
75  */
76 var unsettled;  // initialized by reset()
78 /**
79  * A counter used to assign ID numbers to new SpyPromise objects.
80  * @type {number}
81  */
82 var nextPromiseId;  // initialized by reset()
84 /**
85  * A promise returned by SpyPromise.settleAll.
86  * @type {Promise<null>}
87  */
88 var settleAllPromise;  // initialized by reset()
90 /**
91  * Records an unsettled promise.
92  *
93  * @param {!Promise} unsettledPromise
94  * @return {number} The ID number to be passed to forget_.
95  */
96 function remember(unsettledPromise) {
97   var id = nextPromiseId++;
98   if (unsettled[id] != null) {
99     throw Error('Duplicate ID: ' + id);
100   }
101   base.SpyPromise.unsettledCount++;
102   unsettled[id] = unsettledPromise;
103   return id;
107  * Forgets a promise.  Called after the promise has been settled.
109  * @param {number} id
110  * @private
111  */
112 function forget(id) {
113   console.assert(unsettled[id] != null, 'No such Promise: ' + id + '.');
114   base.SpyPromise.unsettledCount--;
115   delete unsettled[id];
119  * Forgets about all unsettled promises.
120  */
121 base.SpyPromise.reset = function() {
122   base.SpyPromise.unsettledCount = 0;
123   unsettled = {};
124   nextPromiseId = 0;
125   settleAllPromise = null;
128 // Initialize static variables.
129 base.SpyPromise.reset();
132  * Tries to wait until all promises has been settled.
134  * @param {number=} opt_maxTimeMs The maximum number of milliseconds
135  *     (approximately) to wait (default: 1000).
136  * @return {!Promise<null>} A real promise that is resolved when all
137  *     SpyPromises have been settled, or rejected after opt_maxTimeMs
138  *     milliseconds have elapsed.
139  */
140 base.SpyPromise.settleAll = function(opt_maxTimeMs) {
141   if (settleAllPromise) {
142     return settleAllPromise;
143   }
145   var maxDelay = opt_maxTimeMs == null ? 1000 : opt_maxTimeMs;
147   /**
148    * @param {number} count
149    * @param {number} totalDelay
150    * @return {!Promise<null>}
151    */
152   function loop(count, totalDelay) {
153     return new RealPromise(function(resolve, reject) {
154       if (base.SpyPromise.unsettledCount == 0) {
155         settleAllPromise = null;
156         resolve(null);
157       } else if (totalDelay > maxDelay) {
158         settleAllPromise = null;
159         base.SpyPromise.reset();
160         reject(new Error('base.SpyPromise.settleAll timed out'));
161       } else {
162         // This implements quadratic backoff according to Euler's
163         // triangular number formula.
164         var delay = count;
166         // Must jump through crazy hoops to get a real timer in a unit test.
167         realSetTimeout(function() {
168           resolve(loop(
169               count + 1,
170               delay + totalDelay));
171         }, delay);
172       }
173     });
174   };
176   // An extra promise needed here to prevent the loop function from
177   // finishing before settleAllPromise is set.  If that happens,
178   // settleAllPromise will never be reset to null.
179   settleAllPromise = RealPromise.resolve().then(function() {
180     return loop(0, 0);
181   });
182   return settleAllPromise;
186  * Only for testing this class.  Do not use.
187  * @returns {boolean} True if settleAll is executing.
188  */
189 base.SpyPromise.isSettleAllRunning = function() {
190   return settleAllPromise != null;
194  * Wrapper for Promise.resolve.
196  * @param {*} value
197  * @return {!base.SpyPromise}
198  */
199 base.SpyPromise.resolve = function(value) {
200   return new base.SpyPromise(function(resolve, reject) {
201     resolve(value);
202   });
206  * Wrapper for Promise.reject.
208  * @param {*} value
209  * @return {!base.SpyPromise}
210  */
211 base.SpyPromise.reject = function(value) {
212   return new base.SpyPromise(function(resolve, reject) {
213     reject(value);
214   });
218  * Wrapper for Promise.all.
220  * @param  {!Array<Promise>} promises
221  * @return {!base.SpyPromise}
222  */
223 base.SpyPromise.all = function(promises) {
224   return base.SpyPromise.resolve(RealPromise.all(promises));
228  * Wrapper for Promise.race.
230  * @param {!Array<Promise>} promises
231  * @return {!base.SpyPromise}
232  */
233 base.SpyPromise.race = function(promises) {
234   return base.SpyPromise.resolve(RealPromise.race(promises));
238  * Sets Promise = base.SpyPromise.  Must not be called more than once
239  * without an intervening call to restore().
240  */
241 base.SpyPromise.activate = function() {
242   if (settleAllPromise) {
243     throw Error('called base.SpyPromise.activate while settleAll is running');
244   }
245   if (Promise === base.SpyPromise) {
246     throw Error('base.SpyPromise is already active');
247   }
248   Promise = /** @type {function(new:Promise)} */(base.SpyPromise);
252  * Restores the original value of Promise.
253  */
254 base.SpyPromise.restore = function() {
255   if (settleAllPromise) {
256     throw Error('called base.SpyPromise.restore while settleAll is running');
257   }
258   if (Promise === base.SpyPromise) {
259     Promise = RealPromise;
260   } else if (Promise === RealPromise) {
261     throw new Error('base.SpyPromise is not active.');
262   } else {
263     throw new Error('Something fishy is going on.');
264   }
268  * Calls func with Promise equal to base.SpyPromise.
270  * @param {function():void} func A function which is expected to
271  *     create one or more promises.
272  * @param {number=} opt_timeoutMs An optional timeout specifying how
273  *     long to wait for promise chains started in func to be settled.
274  *     (default: 1000 ms)
275  * @return {!Promise<null>} A promise that is resolved after every
276  *     promise chain started in func is fully settled, or rejected
277  *     after a opt_timeoutMs.  In any case, the original value of the
278  *     Promise constructor is restored before this promise is settled.
279  */
280 base.SpyPromise.run = function(func, opt_timeoutMs) {
281   base.SpyPromise.activate();
282   try {
283     func();
284   } finally {
285     return base.SpyPromise.settleAll(opt_timeoutMs).then(function() {
286       base.SpyPromise.restore();
287       return null;
288     }, function(error) {
289       base.SpyPromise.restore();
290       throw error;
291     });
292   }
294 })();