UNPKG

ember-app-scheduler

Version:

Ember addon to schedule work at different phases of app life cycle.

593 lines (448 loc) 15.4 kB
var assert = require('assert'), Promise = require('../lib/Promise'), Pool = require('../lib/Pool'); function add(a, b) { return a + b; } describe('Pool', function () { it('should offload a function to a worker', function (done) { var pool = new Pool({maxWorkers: 10}); function add(a, b) { return a + b; } assert.equal(pool.workers.length, 0); pool.exec(add, [3, 4]) .then(function (result) { assert.equal(result, 7); assert.equal(pool.workers.length, 1); pool.clear(); assert.equal(pool.workers.length, 0); done(); }); assert.equal(pool.workers.length, 1); }); it('should offload functions to multiple workers', function (done) { var pool = new Pool({maxWorkers: 10}); function add(a, b) { return a + b; } assert.equal(pool.workers.length, 0); Promise.all([ pool.exec(add, [3, 4]), pool.exec(add, [2, 3]) ]) .then(function (results) { assert.deepEqual(results, [7, 5]); assert.equal(pool.workers.length, 2); pool.clear(); done(); }); assert.equal(pool.workers.length, 2); }); it('should put tasks in queue when all workers are busy', function (done) { var pool = new Pool({maxWorkers: 2}); function add(a, b) { return a + b; } assert.equal(pool.tasks.length, 0); assert.equal(pool.workers.length, 0); var task1 = pool.exec(add, [3, 4]); var task2 = pool.exec(add, [2, 3]); assert.equal(pool.tasks.length, 0); assert.equal(pool.workers.length, 2); var task3 = pool.exec(add, [5, 7]); var task4 = pool.exec(add, [1, 1]); assert.equal(pool.tasks.length, 2); assert.equal(pool.workers.length, 2); Promise.all([ task1, task2, task3, task4 ]) .then(function (results) { assert.deepEqual(results, [7, 5, 12, 2]); assert.equal(pool.tasks.length, 0); assert.equal(pool.workers.length, 2); pool.clear(); done(); }); }); it('should create a proxy', function (done) { var pool = new Pool(); pool.proxy().then(function (proxy) { assert.deepEqual(Object.keys(proxy).sort(), ['methods', 'run']); proxy.methods() .then(function (methods) { assert.deepEqual(methods.sort(), ['methods', 'run']); done(); }) .catch(function (err) { assert('Should not throw an error'); }); }); }); it('should create a proxy of a custom worker', function (done) { var pool = new Pool(__dirname + '/workers/simple.js'); pool.proxy().then(function (proxy) { assert.deepEqual(Object.keys(proxy).sort(), ['add','methods','multiply','run','timeout']); done(); }); }); it('should invoke a method via a proxy', function (done) { var pool = new Pool(__dirname + '/workers/simple.js'); pool.proxy().then(function (proxy) { proxy.multiply(4, 3) .then(function (result) { assert.equal(result, 12); done(); }) .catch(function (err) { console.log(err); assert('Should not throw an error'); }); }); }); it('should invoke an async method via a proxy', function (done) { var pool = new Pool(__dirname + '/workers/simple.js'); pool.proxy().then(function (proxy) { proxy.timeout(100) .then(function (result) { assert.equal(result, 'done'); done(); }) .catch(function (err) { console.log(err); assert('Should not throw an error'); }); }); }); it('should handle errors thrown by a worker', function (done) { var pool = new Pool({maxWorkers: 10}); function test() { throw new TypeError('Test error'); } pool.exec(test) .catch(function (err) { assert.ok(err instanceof Error); assert.equal(err.message, 'Test error') pool.clear(); done(); }); }); it('should execute a function returning a Promise', function (done) { var pool = new Pool({maxWorkers: 10}); function testAsync() { return Promise.resolve('done'); } pool.exec(testAsync) .then(function (result) { assert.equal(result, 'done'); done(); }) .catch(function (err) { assert('Should not throw an error'); }); }); it('should propagate a rejected Promise', function (done) { var pool = new Pool({maxWorkers: 10}); function testAsync() { return Promise.reject(new Error('I reject!')); } pool.exec(testAsync) .then(function (result) { assert('Should not resolve'); }) .catch(function (err) { //assert.ok(err instanceof Error); // FIXME: returned error should be an instanceof Error assert.equal(err, err.toString('Error: I reject!')); done(); }); }); it('should cancel a task', function (done) { var pool = new Pool({maxWorkers: 10}); function forever() { while (1 > 0) {} // runs forever } var promise = pool.exec(forever) .then(function (result) { assert('promise should never resolve'); }) //.catch(Promise.CancellationError, function (err) { // TODO: not yet supported .catch(function (err) { assert(err instanceof Promise.CancellationError); assert.equal(pool.workers.length, 0); done(); }); // cancel the task setTimeout(function () { promise.cancel(); }, 0); }); it('should cancel a queued task', function (done) { var pool = new Pool({maxWorkers: 1}); var reachedTheEnd = false; function delayed() { var Promise = require('../lib/Promise'); return new Promise(function (resolve, reject) { setTimeout(function () { resolve(1); }, 0); }); } function one() { return 1; } var p1 = pool.exec(delayed) .then(function (result) { assert.equal(result, 1); assert.equal(reachedTheEnd, true); assert.equal(pool.workers.length, 1); assert.equal(pool.tasks.length, 0); done(); }); assert.equal(pool.workers.length, 1); assert.equal(pool.tasks.length, 0); var p2 = pool.exec(one); // will be queued assert.equal(pool.workers.length, 1); assert.equal(pool.tasks.length, 1); p2.cancel(); // cancel immediately assert.equal(pool.workers.length, 1); assert.equal(pool.tasks.length, 1); reachedTheEnd = true; }); // TODO: test whether a task in the queue can be neatly cancelled it('should timeout a task', function (done) { var pool = new Pool({maxWorkers: 10}); function forever() { while (1 > 0) {} // runs forever } var promise = pool.exec(forever) .timeout(50) .then(function (result) { assert('promise should never resolve'); }) //.catch(Promise.CancellationError, function (err) { // TODO: not yet supported .catch(function (err) { assert(err instanceof Promise.TimeoutError); assert.equal(pool.workers.length, 0); done(); }); }); it('should start timeout timer of a task once the task is taken from the queue (1)', function (done) { var pool = new Pool({maxWorkers: 1}); var delay = 50 function sleep() { return new Promise(function (resolve, reject) { setTimeout(function () { resolve ('done :)') }, 100) // 2 * delay }) } function doNothing() { return 'ready' } // add a task pool.exec(sleep) // add a second task, will be queued until the first finishes // the timeout is shorter than the currently executing task and longer than // the queued task, so it should not timeout pool.exec(doNothing) .timeout(delay) .then(function (result) { assert.equal(result, 'ready'); done(); }) .catch(function (err) { assert('promise should not throw'); }); }); it('should start timeout timer of a task once the task is taken from the queue (2)', function (done) { var pool = new Pool({maxWorkers: 1}); var delay = 50 function sleep() { return new Promise(function (resolve, reject) { setTimeout(function () { resolve ('done :)') }, 100) // 2 * delay }) } // add a task pool.exec(sleep) // add a second task, will be queued until the first finishes pool.exec(sleep) .timeout(delay) .then(function (result) { assert('promise should never resolve'); }) .catch(function (err) { assert(err instanceof Promise.TimeoutError); done(); }); }); it('should handle crashed workers (1)', function (done) { var pool = new Pool({maxWorkers: 1}); pool.exec(add) .then(function () { assert('Promise should not be resolved'); }) .catch(function (err) { assert.equal(err.toString(), 'Error: Worker terminated unexpectedly'); assert.equal(pool.workers.length, 0); // validate whether a new worker is spawned pool.exec(add, [2,3]) .then(function (result) { assert.equal(result, 5); assert.equal(pool.workers.length, 1); pool.clear(); done(); }); assert.equal(pool.workers.length, 1); }); assert.equal(pool.workers.length, 1); // kill the worker so it will be terminated pool.workers[0].worker.kill(); assert.equal(pool.workers.length, 1); }); describe('options', function () { it('should throw an error on invalid type or number of maxWorkers', function () { assert.throws(function () { new Pool({maxWorkers: 'a string'}); }, TypeError); assert.throws(function () { new Pool({maxWorkers: 2.5}); }, TypeError); assert.throws(function () { new Pool({maxWorkers: 0}); }, TypeError); assert.throws(function () { new Pool({maxWorkers: -1}); }, TypeError); }); it('should limit to the configured number of max workers', function () { var pool = new Pool({maxWorkers: 2}); pool.exec(add, [1, 2]); pool.exec(add, [3, 4]); pool.exec(add, [5, 6]); pool.exec(add, [7, 8]); pool.exec(add, [9, 0]); assert.equal(pool.maxWorkers, 2); assert.equal(pool.workers.length, 2); assert.equal(pool.tasks.length, 3); pool.clear(); }); it('should take number of cpus minus one as default maxWorkers', function () { var pool = new Pool(); var cpus = require('os').cpus(); assert.equal(pool.maxWorkers, cpus.length - 1); pool.clear(); }); it('should throw an error on invalid type or number of minWorkers', function () { assert.throws(function () { new Pool({minWorkers: 'a string'}); }, TypeError); assert.throws(function () { new Pool({minWorkers: 2.5}); }, TypeError); assert.throws(function () { new Pool({maxWorkers: -1}); }, TypeError); }); it('should create number of cpus minus one when minWorkers set to \'max\'', function () { var pool = new Pool({minWorkers:'max'}); var cpus = require('os').cpus(); assert.equal(pool.workers.length, cpus.length - 1); pool.clear(); }); it('should increase maxWorkers to match minWorkers', function () { var pool = new Pool({minWorkers: 16}); for(var i=0;i<20;i++) pool.exec(add, [i, i*2]); assert.equal(pool.minWorkers, 16); assert.equal(pool.maxWorkers, 16); assert.equal(pool.workers.length, 16); assert.equal(pool.tasks.length, 4); pool.clear(); }); }); it.skip('should handle crashed workers (2)', function (done) { // TODO: create a worker from a script, which really crashes itself }); it('should clear all workers', function (done) { var pool = new Pool({maxWorkers: 10}); assert.equal(pool.workers.length, 0); function test() { return 'ok'; } pool.exec(test) .then(function (result) { assert.equal(result, 'ok'); assert.equal(pool.workers.length, 1); pool.clear(); assert.equal(pool.workers.length, 0); done(); }); assert.equal(pool.workers.length, 1); }); it('should clear all workers after tasks are finished', function (done) { var pool = new Pool({maxWorkers: 10}); assert.equal(pool.workers.length, 0); function test() { return 'ok'; } pool.exec(test) .then(function (result) { assert.equal(result, 'ok'); assert.equal(pool.workers.length, 0); done(); }); assert.equal(pool.workers.length, 1); pool.clear(); assert.equal(pool.workers.length, 0); }); it('should return statistics', function () { var pool = new Pool({maxWorkers: 4}); function test() { return new Promise(function (resolve, reject) { setTimeout(resolve, 100); }); } function testError() { return new Promise(function (resolve, reject) { throw new Error('Test error') }); } assert.deepEqual(pool.stats(), {totalWorkers: 0, busyWorkers: 0, idleWorkers: 0, pendingTasks: 0, activeTasks: 0}); var promise = pool.exec(test) .then(function () { assert.deepEqual(pool.stats(), {totalWorkers: 1, busyWorkers: 0, idleWorkers: 1, pendingTasks: 0, activeTasks: 0 }); // start six tasks (max workers is 4, so we should get pending tasks) var all = Promise.all([ pool.exec(test), pool.exec(test), pool.exec(test), pool.exec(test), pool.exec(test), pool.exec(test) ]); assert.deepEqual(pool.stats(), {totalWorkers: 4, busyWorkers: 4, idleWorkers: 0, pendingTasks: 2, activeTasks: 4}); return all; }) .then(function () { assert.deepEqual(pool.stats(), {totalWorkers: 4, busyWorkers: 0, idleWorkers: 4, pendingTasks: 0, activeTasks: 0 }); return pool.exec(testError) }) .catch(function () { assert.deepEqual(pool.stats(), {totalWorkers: 4, busyWorkers: 0, idleWorkers: 4, pendingTasks: 0, activeTasks: 0}); }); assert.deepEqual(pool.stats(), {totalWorkers: 1, busyWorkers: 1, idleWorkers: 0, pendingTasks: 0, activeTasks: 1}); return promise; }); it('should throw an error in case of wrong type of arguments in function exec', function () { var pool = new Pool(); assert.throws(function () {pool.exec()}, TypeError); assert.throws(function () {pool.exec(23)}, TypeError); assert.throws(function () {pool.exec(add, {})}, TypeError); assert.throws(function () {pool.exec(add, 2, 3)}, TypeError); assert.throws(function () {pool.exec(add, 'a string')}, TypeError); }); });