[Cronet] Delay StartNetLog and StopNetLog until native request context is initialized
[chromium-blink-merge.git] / remoting / webapp / unittests / spy_promise.js
blob3429d276585c3d4857a1366c5ed64f76adcdd47e
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.
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.
22 * The normal way to use this class is within a call to
23 * SpyPromise.run, for example:
25 * base.SpyPromise.run(function() {
26 * myCodeThatUsesPromises();
27 * });
28 * base.SpyPromise.settleAll().then(function() {
29 * console.log('All promises have been settled!');
30 * });
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.
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
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
64 var realSetTimeout = window.setTimeout.bind(window);
66 /**
67 * The number of unsettled promises.
68 * @type {number}
70 base.SpyPromise.unsettledCount; // initialized by reset()
72 /**
73 * A collection of all unsettled promises.
74 * @type {!Object<number,!Promise>}
76 var unsettled; // initialized by reset()
78 /**
79 * A counter used to assign ID numbers to new SpyPromise objects.
80 * @type {number}
82 var nextPromiseId; // initialized by reset()
84 /**
85 * A promise returned by SpyPromise.settleAll.
86 * @type {Promise<null>}
88 var settleAllPromise; // initialized by reset()
90 /**
91 * Records an unsettled promise.
93 * @param {!Promise} unsettledPromise
94 * @return {number} The ID number to be passed to forget_.
96 function remember(unsettledPromise) {
97 var id = nextPromiseId++;
98 if (unsettled[id] != null) {
99 throw Error('Duplicate ID: ' + id);
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
112 function forget(id) {
113 base.debug.assert(unsettled[id] != null);
114 base.SpyPromise.unsettledCount--;
115 delete unsettled[id];
119 * Forgets about all unsettled promises.
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.
140 base.SpyPromise.settleAll = function(opt_maxTimeMs) {
141 if (settleAllPromise) {
142 return settleAllPromise;
145 var maxDelay = opt_maxTimeMs == null ? 1000 : opt_maxTimeMs;
148 * @param {number} count
149 * @param {number} totalDelay
150 * @return {!Promise<null>}
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);
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);
182 return settleAllPromise;
186 * Only for testing this class. Do not use.
187 * @returns {boolean} True if settleAll is executing.
189 base.SpyPromise.isSettleAllRunning = function() {
190 return settleAllPromise != null;
194 * Wrapper for Promise.resolve.
196 * @param {*} value
197 * @return {!base.SpyPromise}
199 base.SpyPromise.resolve = function(value) {
200 return new base.SpyPromise(function(resolve, reject) {
201 resolve(value);
206 * Wrapper for Promise.reject.
208 * @param {*} value
209 * @return {!base.SpyPromise}
211 base.SpyPromise.reject = function(value) {
212 return new base.SpyPromise(function(resolve, reject) {
213 reject(value);
218 * Wrapper for Promise.all.
220 * @param {!Array<Promise>} promises
221 * @return {!base.SpyPromise}
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}
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().
241 base.SpyPromise.activate = function() {
242 if (settleAllPromise) {
243 throw Error('called base.SpyPromise.activate while settleAll is running');
245 if (Promise === base.SpyPromise) {
246 throw Error('base.SpyPromise is already active');
248 Promise = base.SpyPromise;
252 * Restores the original value of Promise.
254 base.SpyPromise.restore = function() {
255 if (settleAllPromise) {
256 throw Error('called base.SpyPromise.restore while settleAll is running');
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.');
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.
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;
294 })();