www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

concurrentCallerTest.js (10909B)


      1 "use strict";
      2 
      3 describe("ConcurrentCaller", function () {
      4 	Components.utils.import("resource://zotero/concurrentCaller.js");
      5 	var logger = null;
      6 	// Uncomment to get debug output
      7 	//logger = Zotero.debug;
      8 	
      9 	describe("#start()", function () {
     10 		it("should run functions as slots open and wait for them to complete", function* () {
     11 			var numConcurrent = 2;
     12 			var running = 0;
     13 			var finished = 0;
     14 			var failed = false;
     15 			
     16 			var ids = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
     17 			var funcs = ids.map(function (id) {
     18 				return Zotero.Promise.coroutine(function* () {
     19 					if (logger) {
     20 						Zotero.debug("Running " + id);
     21 					}
     22 					running++;
     23 					if (running > numConcurrent) {
     24 						failed = true;
     25 						throw new Error("Too many concurrent tasks");
     26 					}
     27 					var min = 10;
     28 					var max = 25;
     29 					yield Zotero.Promise.delay(
     30 						Math.floor(Math.random() * (max - min + 1)) + min
     31 					);
     32 					if (running > numConcurrent) {
     33 						failed = true;
     34 						throw new Error("Too many concurrent tasks");
     35 					}
     36 					running--;
     37 					finished++;
     38 					if (logger) {
     39 						Zotero.debug("Finished " + id);
     40 					}
     41 					return id;
     42 				});
     43 			})
     44 			
     45 			var caller = new ConcurrentCaller({
     46 				numConcurrent,
     47 				logger
     48 			});
     49 			var results = yield caller.start(funcs);
     50 			
     51 			assert.equal(results.length, ids.length);
     52 			assert.equal(running, 0);
     53 			assert.equal(finished, ids.length);
     54 			assert.isFalse(failed);
     55 		})
     56 		
     57 		it("should add functions to existing queue and resolve when all are complete (waiting for earlier set)", function* () {
     58 			var numConcurrent = 2;
     59 			var running = 0;
     60 			var finished = 0;
     61 			var failed = false;
     62 			
     63 			var ids1 = {"1": 5, "2": 10, "3": 7};
     64 			var ids2 = {"4": 50, "5": 50};
     65 			var makeFunc = function (id, delay) {
     66 				return Zotero.Promise.coroutine(function* () {
     67 					if (logger) {
     68 						Zotero.debug("Running " + id);
     69 					}
     70 					running++;
     71 					if (running > numConcurrent) {
     72 						failed = true;
     73 						throw new Error("Too many concurrent tasks");
     74 					}
     75 					yield Zotero.Promise.delay(delay);
     76 					if (running > numConcurrent) {
     77 						failed = true;
     78 						throw new Error("Too many concurrent tasks");
     79 					}
     80 					running--;
     81 					finished++;
     82 					if (logger) {
     83 						Zotero.debug("Finished " + id);
     84 					}
     85 					return id;
     86 				});
     87 			};
     88 			var keys1 = Object.keys(ids1);
     89 			var keys2 = Object.keys(ids2);
     90 			var funcs1 = Object.keys(ids1).map(id => makeFunc(id, ids1[id]));
     91 			var funcs2 = Object.keys(ids2).map(id => makeFunc(id, ids2[id]));
     92 			
     93 			var caller = new ConcurrentCaller({
     94 				numConcurrent,
     95 				logger
     96 			});
     97 			var promise1 = caller.start(funcs1);
     98 			yield Zotero.Promise.delay(1);
     99 			var promise2 = caller.start(funcs2);
    100 			
    101 			// Wait for first set
    102 			var results1 = yield promise1;
    103 			
    104 			// Second set shouldn't be done yet
    105 			assert.isFalse(promise2.isFulfilled());
    106 			assert.equal(finished, keys1.length);
    107 			assert.equal(results1.length, keys1.length);
    108 			assert.sameMembers(results1.map(p => p.value()), keys1);
    109 			assert.isFalse(failed);
    110 		})
    111 		
    112 		it("should add functions to existing queue and resolve when all are complete (waiting for later set)", function* () {
    113 			var numConcurrent = 2;
    114 			var running = 0;
    115 			var finished = 0;
    116 			var failed = false;
    117 			
    118 			var ids1 = {"1": 100, "2": 45, "3": 80};
    119 			var ids2 = {"4": 1, "5": 1};
    120 			var makeFunc = function (id, delay) {
    121 				return Zotero.Promise.coroutine(function* () {
    122 					if (logger) {
    123 						Zotero.debug("Running " + id);
    124 					}
    125 					running++;
    126 					if (running > numConcurrent) {
    127 						failed = true;
    128 						throw new Error("Too many concurrent tasks");
    129 					}
    130 					yield Zotero.Promise.delay(delay);
    131 					if (running > numConcurrent) {
    132 						failed = true;
    133 						throw new Error("Too many concurrent tasks");
    134 					}
    135 					running--;
    136 					finished++;
    137 					if (logger) {
    138 						Zotero.debug("Finished " + id);
    139 					}
    140 					return id;
    141 				});
    142 			};
    143 			var keys1 = Object.keys(ids1);
    144 			var keys2 = Object.keys(ids2);
    145 			var funcs1 = Object.keys(ids1).map(id => makeFunc(id, ids1[id]));
    146 			var funcs2 = Object.keys(ids2).map(id => makeFunc(id, ids2[id]));
    147 			
    148 			var caller = new ConcurrentCaller({
    149 				numConcurrent,
    150 				logger
    151 			});
    152 			var promise1 = caller.start(funcs1);
    153 			yield Zotero.Promise.delay(10);
    154 			var promise2 = caller.start(funcs2);
    155 			
    156 			// Wait for second set
    157 			var results2 = yield promise2;
    158 			
    159 			// The second set should finish before the first
    160 			assert.isFalse(promise1.isFulfilled());
    161 			assert.equal(running, 1); // 3 should still be running
    162 			assert.equal(finished, 4); // 1, 2, 4, 5
    163 			assert.equal(results2.length, keys2.length);
    164 			assert.equal(results2[0].value(), keys2[0]);
    165 			assert.equal(results2[1].value(), keys2[1]);
    166 			assert.isFalse(failed);
    167 		})
    168 		
    169 		it("should return a rejected promise if a single passed function fails", function* () {
    170 			var numConcurrent = 2;
    171 			
    172 			var caller = new ConcurrentCaller({
    173 				numConcurrent,
    174 				logger
    175 			});
    176 			var e = yield getPromiseError(caller.start(function () {
    177 				throw new Error("Fail");
    178 			}));
    179 			assert.ok(e);
    180 		})
    181 		
    182 		it("should stop on error if stopOnError is set", function* () {
    183 			var numConcurrent = 2;
    184 			var running = 0;
    185 			var finished = 0;
    186 			var failed = false;
    187 			
    188 			var ids1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'];
    189 			var ids2 = ['n', 'o', 'p', 'q'];
    190 			var makeFunc = function (id) {
    191 				return Zotero.Promise.coroutine(function* () {
    192 					if (logger) {
    193 						Zotero.debug("Running " + id);
    194 					}
    195 					running++
    196 					if (running > numConcurrent) {
    197 						failed = true;
    198 						throw new Error("Too many concurrent tasks");
    199 					}
    200 					var min = 10;
    201 					var max = 25;
    202 					yield Zotero.Promise.delay(
    203 						Math.floor(Math.random() * (max - min + 1)) + min
    204 					);
    205 					if (id == 'g') {
    206 						running--;
    207 						finished++;
    208 						Zotero.debug("Throwing " + id);
    209 						// This causes an erroneous "possibly unhandled rejection" message in
    210 						// Bluebird 2.10.2 that I can't seem to get rid of (and the rejection
    211 						// is later handled), so tell Bluebird to ignore it
    212 						let e = new Error("Fail");
    213 						e.handledRejection = true;
    214 						throw e;
    215 					}
    216 					if (running > numConcurrent) {
    217 						failed = true;
    218 						throw new Error("Too many concurrent tasks");
    219 					}
    220 					running--;
    221 					finished++;
    222 					if (logger) {
    223 						Zotero.debug("Finished " + id);
    224 					}
    225 					return id;
    226 				});
    227 			};
    228 			var funcs1 = ids1.map(makeFunc)
    229 			var funcs2 = ids2.map(makeFunc)
    230 			
    231 			var caller = new ConcurrentCaller({
    232 				numConcurrent,
    233 				stopOnError: true,
    234 				logger
    235 			});
    236 			var promise1 = caller.start(funcs1);
    237 			var promise2 = caller.start(funcs2);
    238 			
    239 			var results1 = yield promise1;
    240 			
    241 			assert.isTrue(promise2.isFulfilled());
    242 			assert.equal(running, 0);
    243 			assert.isBelow(finished, ids1.length);
    244 			assert.equal(results1.length, ids1.length);
    245 			assert.equal(promise2.value().length, ids2.length);
    246 			// 'a' should be fulfilled
    247 			assert.isTrue(results1[0].isFulfilled());
    248 			// 'g' should be rejected
    249 			assert.isTrue(results1[6].isRejected());
    250 			// 'm' should be rejected
    251 			assert.isTrue(results1[12].isRejected());
    252 			// All promises in second batch should be rejected
    253 			assert.isTrue(promise2.value().every(p => p.isRejected()));
    254 		})
    255 		
    256 		
    257 		it("should not stop on error if stopOnError isn't set", function* () {
    258 			var numConcurrent = 2;
    259 			var running = 0;
    260 			var finished = 0;
    261 			var failed = false;
    262 			
    263 			var ids1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'];
    264 			var ids2 = ['n', 'o', 'p', 'q'];
    265 			var makeFunc = function (id) {
    266 				return Zotero.Promise.coroutine(function* () {
    267 					if (logger) {
    268 						Zotero.debug("Running " + id);
    269 					}
    270 					running++
    271 					if (running > numConcurrent) {
    272 						failed = true;
    273 						throw new Error("Too many concurrent tasks");
    274 					}
    275 					var min = 10;
    276 					var max = 25;
    277 					yield Zotero.Promise.delay(
    278 						Math.floor(Math.random() * (max - min + 1)) + min
    279 					);
    280 					if (id == 'g') {
    281 						running--;
    282 						finished++;
    283 						Zotero.debug("Throwing " + id);
    284 						// This causes an erroneous "possibly unhandled rejection" message in
    285 						// Bluebird 2.10.2 that I can't seem to get rid of (and the rejection
    286 						// is later handled), so tell Bluebird to ignore it
    287 						let e = new Error("Fail");
    288 						e.handledRejection = true;
    289 						throw e;
    290 					}
    291 					if (running > numConcurrent) {
    292 						failed = true;
    293 						throw new Error("Too many concurrent tasks");
    294 					}
    295 					running--;
    296 					finished++;
    297 					if (logger) {
    298 						Zotero.debug("Finished " + id);
    299 					}
    300 					return id;
    301 				});
    302 			};
    303 			var funcs1 = ids1.map(makeFunc)
    304 			var funcs2 = ids2.map(makeFunc)
    305 			
    306 			var caller = new ConcurrentCaller({
    307 				numConcurrent,
    308 				logger
    309 			});
    310 			var promise1 = caller.start(funcs1);
    311 			var promise2 = caller.start(funcs2);
    312 			
    313 			var results2 = yield promise2;
    314 			
    315 			assert.isTrue(promise1.isFulfilled());
    316 			assert.isTrue(promise2.isFulfilled());
    317 			assert.equal(running, 0);
    318 			assert.equal(finished, ids1.length + ids2.length);
    319 			assert.equal(promise1.value().length, ids1.length);
    320 			assert.equal(results2.length, ids2.length);
    321 			// 'a' should be fulfilled
    322 			assert.isTrue(promise1.value()[0].isFulfilled());
    323 			// 'g' should be rejected
    324 			assert.isTrue(promise1.value()[6].isRejected());
    325 			// 'm' should be fulfilled
    326 			assert.isTrue(promise1.value()[12].isFulfilled());
    327 			// All promises in second batch should be fulfilled
    328 			assert.isTrue(results2.every(p => p.isFulfilled()));
    329 		})
    330 	})
    331 	
    332 	describe("#wait()", function () {
    333 		it("should return when all tasks are done", function* () {
    334 			var numConcurrent = 2;
    335 			var running = 0;
    336 			var finished = 0;
    337 			var failed = false;
    338 			
    339 			var ids1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'];
    340 			var ids2 = ['n', 'o', 'p', 'q'];
    341 			var makeFunc = function (id) {
    342 				return Zotero.Promise.coroutine(function* () {
    343 					if (logger) {
    344 						Zotero.debug("Running " + id);
    345 					}
    346 					running++;
    347 					if (running > numConcurrent) {
    348 						failed = true;
    349 						throw new Error("Too many concurrent tasks");
    350 					}
    351 					var min = 10;
    352 					var max = 25;
    353 					yield Zotero.Promise.delay(
    354 						Math.floor(Math.random() * (max - min + 1)) + min
    355 					);
    356 					if (running > numConcurrent) {
    357 						failed = true;
    358 						throw new Error("Too many concurrent tasks");
    359 					}
    360 					running--;
    361 					finished++;
    362 					if (logger) {
    363 						Zotero.debug("Finished " + id);
    364 					}
    365 					return id;
    366 				});
    367 			};
    368 			var funcs1 = ids1.map(makeFunc)
    369 			var funcs2 = ids2.map(makeFunc)
    370 			
    371 			var caller = new ConcurrentCaller({
    372 				numConcurrent,
    373 				logger
    374 			});
    375 			var promise1 = caller.start(funcs1);
    376 			yield Zotero.Promise.delay(10);
    377 			var promise2 = caller.start(funcs2);
    378 			
    379 			yield caller.wait();
    380 			
    381 			assert.isTrue(promise1.isFulfilled());
    382 			assert.isTrue(promise2.isFulfilled());
    383 			assert.equal(running, 0);
    384 			assert.equal(finished, ids1.length + ids2.length);
    385 		})
    386 	})
    387 })