├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── lib └── runTask.js ├── package.json └── test ├── add.js ├── doneCallback.js ├── emitTaskDone.js ├── gulpTask.js ├── hasTask.js ├── onAll.js ├── readyToRunTask.js ├── resetAllTasks.js ├── resetSpecificTasks.js ├── resetTask.js ├── runTask.js ├── start.js ├── stop.js ├── stopTask.js ├── streamConsume.js ├── task.js ├── taskDependencies.js ├── taskTimings.js └── taskWaiting.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | .tmp 4 | node_modules 5 | build 6 | *.node 7 | components 8 | *.orig 9 | .idea 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | build 5 | *.node 6 | components 7 | *.orig 8 | .idea 9 | test 10 | .travis.yml 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | arch: 2 | - amd64 3 | - ppc64le 4 | language: node_js 5 | node_js: 6 | - "10" 7 | - "12" 8 | - "14" 9 | - "15" 10 | - "stable" 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 [Richardson & Sons, LLC](http://richardsonandsons.com/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://secure.travis-ci.org/robrich/orchestrator.svg?branch=master)](https://travis-ci.org/robrich/orchestrator) 2 | [![Dependency Status](https://david-dm.org/robrich/orchestrator.svg)](https://david-dm.org/robrich/orchestrator) 3 | 4 | Orchestrator 5 | ============ 6 | 7 | A module for sequencing and executing tasks and dependencies in maximum concurrency 8 | 9 | Usage 10 | ----- 11 | 12 | ### 1. Get a reference: 13 | 14 | ```javascript 15 | var Orchestrator = require('orchestrator'); 16 | var orchestrator = new Orchestrator(); 17 | ``` 18 | 19 | ### 2. Load it up with stuff to do: 20 | 21 | ```javascript 22 | orchestrator.add('thing1', function(){ 23 | // do stuff 24 | }); 25 | orchestrator.add('thing2', function(){ 26 | // do stuff 27 | }); 28 | ``` 29 | 30 | ### 3. Run the tasks: 31 | 32 | ```javascript 33 | orchestrator.start('thing1', 'thing2', function (err) { 34 | // all done 35 | }); 36 | ``` 37 | 38 | API 39 | --- 40 | 41 | ### orchestrator.add(name[, deps][, function]); 42 | 43 | Define a task 44 | 45 | ```javascript 46 | orchestrator.add('thing1', function(){ 47 | // do stuff 48 | }); 49 | ``` 50 | 51 | #### name 52 | Type: `String` 53 | 54 | The name of the task. 55 | 56 | #### deps 57 | Type: `Array` 58 | 59 | An array of task names to be executed and completed before your task will run. 60 | 61 | ```javascript 62 | orchestrator.add('mytask', ['array', 'of', 'task', 'names'], function() { 63 | // Do stuff 64 | }); 65 | ``` 66 | 67 | **Note:** Are your tasks running before the dependencies are complete? Make sure your dependency tasks 68 | are correctly using the async run hints: take in a callback or return a promise or event stream. 69 | 70 | #### fn 71 | Type: `function` 72 | 73 | The function that performs the task's operations. For asynchronous tasks, you need to provide a hint when the task is complete: 74 | 75 | - Take in a callback 76 | - Return a stream or a promise 77 | 78 | #### examples: 79 | 80 | **Accept a callback:** 81 | 82 | ```javascript 83 | orchestrator.add('thing2', function(callback){ 84 | // do stuff 85 | callback(err); 86 | }); 87 | ``` 88 | 89 | **Return a promise:** 90 | 91 | ```javascript 92 | var Q = require('q'); 93 | 94 | orchestrator.add('thing3', function(){ 95 | var deferred = Q.defer(); 96 | 97 | // do async stuff 98 | setTimeout(function () { 99 | deferred.resolve(); 100 | }, 1); 101 | 102 | return deferred.promise; 103 | }); 104 | ``` 105 | 106 | **Return a stream:** (task is marked complete when stream ends) 107 | 108 | ```javascript 109 | var map = require('map-stream'); 110 | 111 | orchestrator.add('thing4', function(){ 112 | var stream = map(function (args, cb) { 113 | cb(null, args); 114 | }); 115 | // do stream stuff 116 | return stream; 117 | }); 118 | ``` 119 | 120 | **Note:** By default, tasks run with maximum concurrency -- e.g. it launches all the tasks at once and waits for nothing. 121 | If you want to create a series where tasks run in a particular order, you need to do two things: 122 | 123 | - give it a hint to tell it when the task is done, 124 | - and give it a hint that a task depends on completion of another. 125 | 126 | For these examples, let's presume you have two tasks, "one" and "two" that you specifically want to run in this order: 127 | 128 | 1. In task "one" you add a hint to tell it when the task is done. Either take in a callback and call it when you're 129 | done or return a promise or stream that the engine should wait to resolve or end respectively. 130 | 131 | 2. In task "two" you add a hint telling the engine that it depends on completion of the first task. 132 | 133 | So this example would look like this: 134 | 135 | ```javascript 136 | var Orchestrator = require('orchestrator'); 137 | var orchestrator = new Orchestrator(); 138 | 139 | // takes in a callback so the engine knows when it'll be done 140 | orchestrator.add('one', function (cb) { 141 | // do stuff -- async or otherwise 142 | cb(err); // if err is not null or undefined, the orchestration will stop, and note that it failed 143 | }); 144 | 145 | // identifies a dependent task must be complete before this one begins 146 | orchestrator.add('two', ['one'], function () { 147 | // task 'one' is done now 148 | }); 149 | 150 | orchestrator.start('one', 'two'); 151 | ``` 152 | 153 | ### orchestrator.hasTask(name); 154 | 155 | Have you defined a task with this name? 156 | 157 | #### name 158 | Type: `String` 159 | 160 | The task name to query 161 | 162 | ### orchestrator.start(tasks...[, cb]); 163 | 164 | Start running the tasks 165 | 166 | #### tasks 167 | Type: `String` or `Array` of `String`s 168 | 169 | Tasks to be executed. You may pass any number of tasks as individual arguments. 170 | 171 | #### cb 172 | Type: `function`: `function (err) {` 173 | 174 | Callback to call after run completed. 175 | 176 | Passes single argument: `err`: did the orchestration succeed? 177 | 178 | **Note:** Tasks run concurrently and therefore may not complete in order. 179 | **Note:** Orchestrator uses `sequencify` to resolve dependencies before running, and therefore may not start in order. 180 | Listen to orchestration events to watch task running. 181 | 182 | ```javascript 183 | orchestrator.start('thing1', 'thing2', 'thing3', 'thing4', function (err) { 184 | // all done 185 | }); 186 | ``` 187 | ```javascript 188 | orchestrator.start(['thing1','thing2'], ['thing3','thing4']); 189 | ``` 190 | 191 | **FRAGILE:** Orchestrator catches exceptions on sync runs to pass to your callback 192 | but doesn't hook to process.uncaughtException so it can't pass those exceptions 193 | to your callback 194 | 195 | **FRAGILE:** Orchestrator will ensure each task and each dependency is run once during an orchestration run 196 | even if you specify it to run more than once. (e.g. `orchestrator.start('thing1', 'thing1')` 197 | will only run 'thing1' once.) If you need it to run a task multiple times, wait for 198 | the orchestration to end (start's callback) then call start again. 199 | (e.g. `orchestrator.start('thing1', function () {orchestrator.start('thing1');})`.) 200 | Alternatively create a second orchestrator instance. 201 | 202 | ### orchestrator.stop() 203 | 204 | Stop an orchestration run currently in process 205 | 206 | **Note:** It will call the `start()` callback with an `err` noting the orchestration was aborted 207 | 208 | ### orchestrator.on(event, cb); 209 | 210 | Listen to orchestrator internals 211 | 212 | #### event 213 | Type: `String` 214 | 215 | Event name to listen to: 216 | - start: from start() method, shows you the task sequence 217 | - stop: from stop() method, the queue finished successfully 218 | - err: from stop() method, the queue was aborted due to a task error 219 | - task_start: from _runTask() method, task was started 220 | - task_stop: from _runTask() method, task completed successfully 221 | - task_err: from _runTask() method, task errored 222 | - task_not_found: from start() method, you're trying to start a task that doesn't exist 223 | - task_recursion: from start() method, there are recursive dependencies in your task list 224 | 225 | #### cb 226 | Type: `function`: `function (e) {` 227 | 228 | Passes single argument: `e`: event details 229 | 230 | ```javascript 231 | orchestrator.on('task_start', function (e) { 232 | // e.message is the log message 233 | // e.task is the task name if the message applies to a task else `undefined` 234 | // e.err is the error if event is 'err' else `undefined` 235 | }); 236 | // for task_end and task_err: 237 | orchestrator.on('task_stop', function (e) { 238 | // e is the same object from task_start 239 | // e.message is updated to show how the task ended 240 | // e.duration is the task run duration (in seconds) 241 | }); 242 | ``` 243 | 244 | **Note:** fires either *stop or *err but not both. 245 | 246 | ### orchestrator.onAll(cb); 247 | 248 | Listen to all orchestrator events from one callback 249 | 250 | #### cb 251 | Type: `function`: `function (e) {` 252 | 253 | Passes single argument: `e`: event details 254 | 255 | ```javascript 256 | orchestrator.onAll(function (e) { 257 | // e is the original event args 258 | // e.src is event name 259 | }); 260 | ``` 261 | 262 | LICENSE 263 | ------- 264 | 265 | (MIT License) 266 | 267 | Copyright (c) 2013-2015 [Richardson & Sons, LLC](http://richardsonandsons.com/) 268 | 269 | Permission is hereby granted, free of charge, to any person obtaining 270 | a copy of this software and associated documentation files (the 271 | "Software"), to deal in the Software without restriction, including 272 | without limitation the rights to use, copy, modify, merge, publish, 273 | distribute, sublicense, and/or sell copies of the Software, and to 274 | permit persons to whom the Software is furnished to do so, subject to 275 | the following conditions: 276 | 277 | The above copyright notice and this permission notice shall be 278 | included in all copies or substantial portions of the Software. 279 | 280 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 281 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 282 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 283 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 284 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 285 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 286 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 287 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | 3 | "use strict"; 4 | 5 | var util = require('util'); 6 | var events = require('events'); 7 | var EventEmitter = events.EventEmitter; 8 | var runTask = require('./lib/runTask'); 9 | 10 | var Orchestrator = function () { 11 | EventEmitter.call(this); 12 | this.doneCallback = undefined; // call this when all tasks in the queue are done 13 | this.seq = []; // the order to run the tasks 14 | this.tasks = {}; // task objects: name, dep (list of names of dependencies), fn (the task to run) 15 | this.isRunning = false; // is the orchestrator running tasks? .start() to start, .stop() to stop 16 | }; 17 | util.inherits(Orchestrator, EventEmitter); 18 | 19 | Orchestrator.prototype.reset = function () { 20 | if (this.isRunning) { 21 | this.stop(null); 22 | } 23 | this.tasks = {}; 24 | this.seq = []; 25 | this.isRunning = false; 26 | this.doneCallback = undefined; 27 | return this; 28 | }; 29 | Orchestrator.prototype.add = function (name, dep, fn) { 30 | if (!fn && typeof dep === 'function') { 31 | fn = dep; 32 | dep = undefined; 33 | } 34 | dep = dep || []; 35 | fn = fn || function () {}; // no-op 36 | if (!name) { 37 | throw new Error('Task requires a name'); 38 | } 39 | // validate name is a string, dep is an array of strings, and fn is a function 40 | if (typeof name !== 'string') { 41 | throw new Error('Task requires a name that is a string'); 42 | } 43 | if (typeof fn !== 'function') { 44 | throw new Error('Task '+name+' requires a function that is a function'); 45 | } 46 | if (!Array.isArray(dep)) { 47 | throw new Error('Task '+name+' can\'t support dependencies that is not an array of strings'); 48 | } 49 | dep.forEach(function (item) { 50 | if (typeof item !== 'string') { 51 | throw new Error('Task '+name+' dependency '+item+' is not a string'); 52 | } 53 | }); 54 | this.tasks[name] = { 55 | fn: fn, 56 | dep: dep, 57 | name: name 58 | }; 59 | return this; 60 | }; 61 | Orchestrator.prototype.task = function (name, dep, fn) { 62 | if (dep || fn) { 63 | // alias for add, return nothing rather than this 64 | this.add(name, dep, fn); 65 | } else { 66 | return this.tasks[name]; 67 | } 68 | }; 69 | Orchestrator.prototype.hasTask = function (name) { 70 | return !!this.tasks[name]; 71 | }; 72 | // tasks and optionally a callback 73 | Orchestrator.prototype.start = function() { 74 | var args, arg, names = [], lastTask, i, seq = []; 75 | args = Array.prototype.slice.call(arguments, 0); 76 | if (args.length) { 77 | lastTask = args[args.length-1]; 78 | if (typeof lastTask === 'function') { 79 | this.doneCallback = lastTask; 80 | args.pop(); 81 | } 82 | for (i = 0; i < args.length; i++) { 83 | arg = args[i]; 84 | if (typeof arg === 'string') { 85 | names.push(arg); 86 | } else if (Array.isArray(arg)) { 87 | names = names.concat(arg); // FRAGILE: ASSUME: it's an array of strings 88 | } else { 89 | throw new Error('pass strings or arrays of strings'); 90 | } 91 | } 92 | } 93 | if (this.isRunning) { 94 | // reset specified tasks (and dependencies) as not run 95 | this._resetSpecificTasks(names); 96 | } else { 97 | // reset all tasks as not run 98 | this._resetAllTasks(); 99 | } 100 | if (this.isRunning) { 101 | // if you call start() again while a previous run is still in play 102 | // prepend the new tasks to the existing task queue 103 | names = names.concat(this.seq); 104 | } 105 | if (names.length < 1) { 106 | // run all tasks 107 | for (i in this.tasks) { 108 | if (this.tasks.hasOwnProperty(i)) { 109 | names.push(this.tasks[i].name); 110 | } 111 | } 112 | } 113 | seq = []; 114 | try { 115 | this.sequence(this.tasks, names, seq, []); 116 | } catch (err) { 117 | // Is this a known error? 118 | if (err) { 119 | if (err.missingTask) { 120 | this.emit('task_not_found', {message: err.message, task:err.missingTask, err: err}); 121 | } 122 | if (err.recursiveTasks) { 123 | this.emit('task_recursion', {message: err.message, recursiveTasks:err.recursiveTasks, err: err}); 124 | } 125 | } 126 | this.stop(err); 127 | return this; 128 | } 129 | this.seq = seq; 130 | this.emit('start', {message:'seq: '+this.seq.join(',')}); 131 | if (!this.isRunning) { 132 | this.isRunning = true; 133 | } 134 | this._runStep(); 135 | return this; 136 | }; 137 | Orchestrator.prototype.stop = function (err, successfulFinish) { 138 | this.isRunning = false; 139 | if (err) { 140 | this.emit('err', {message:'orchestration failed', err:err}); 141 | } else if (successfulFinish) { 142 | this.emit('stop', {message:'orchestration succeeded'}); 143 | } else { 144 | // ASSUME 145 | err = 'orchestration aborted'; 146 | this.emit('err', {message:'orchestration aborted', err: err}); 147 | } 148 | if (this.doneCallback) { 149 | // Avoid calling it multiple times 150 | this.doneCallback(err); 151 | } else if (err && !this.listeners('err').length) { 152 | // No one is listening for the error so speak louder 153 | throw err; 154 | } 155 | }; 156 | Orchestrator.prototype.sequence = require('sequencify'); 157 | Orchestrator.prototype.allDone = function () { 158 | var i, task, allDone = true; // nothing disputed it yet 159 | for (i = 0; i < this.seq.length; i++) { 160 | task = this.tasks[this.seq[i]]; 161 | if (!task.done) { 162 | allDone = false; 163 | break; 164 | } 165 | } 166 | return allDone; 167 | }; 168 | Orchestrator.prototype._resetTask = function(task) { 169 | if (task) { 170 | if (task.done) { 171 | task.done = false; 172 | } 173 | delete task.start; 174 | delete task.stop; 175 | delete task.duration; 176 | delete task.hrDuration; 177 | delete task.args; 178 | } 179 | }; 180 | Orchestrator.prototype._resetAllTasks = function() { 181 | var task; 182 | for (task in this.tasks) { 183 | if (this.tasks.hasOwnProperty(task)) { 184 | this._resetTask(this.tasks[task]); 185 | } 186 | } 187 | }; 188 | Orchestrator.prototype._resetSpecificTasks = function (names) { 189 | var i, name, t; 190 | 191 | if (names && names.length) { 192 | for (i = 0; i < names.length; i++) { 193 | name = names[i]; 194 | t = this.tasks[name]; 195 | if (t) { 196 | this._resetTask(t); 197 | if (t.dep && t.dep.length) { 198 | this._resetSpecificTasks(t.dep); // recurse 199 | } 200 | //} else { 201 | // FRAGILE: ignore that the task doesn't exist 202 | } 203 | } 204 | } 205 | }; 206 | Orchestrator.prototype._runStep = function () { 207 | var i, task; 208 | if (!this.isRunning) { 209 | return; // user aborted, ASSUME: stop called previously 210 | } 211 | for (i = 0; i < this.seq.length; i++) { 212 | task = this.tasks[this.seq[i]]; 213 | if (!task.done && !task.running && this._readyToRunTask(task)) { 214 | this._runTask(task); 215 | } 216 | if (!this.isRunning) { 217 | return; // task failed or user aborted, ASSUME: stop called previously 218 | } 219 | } 220 | if (this.allDone()) { 221 | this.stop(null, true); 222 | } 223 | }; 224 | Orchestrator.prototype._readyToRunTask = function (task) { 225 | var ready = true, // no one disproved it yet 226 | i, name, t; 227 | if (task.dep.length) { 228 | for (i = 0; i < task.dep.length; i++) { 229 | name = task.dep[i]; 230 | t = this.tasks[name]; 231 | if (!t) { 232 | // FRAGILE: this should never happen 233 | this.stop("can't run "+task.name+" because it depends on "+name+" which doesn't exist"); 234 | ready = false; 235 | break; 236 | } 237 | if (!t.done) { 238 | ready = false; 239 | break; 240 | } 241 | } 242 | } 243 | return ready; 244 | }; 245 | Orchestrator.prototype._stopTask = function (task, meta) { 246 | task.duration = meta.duration; 247 | task.hrDuration = meta.hrDuration; 248 | task.running = false; 249 | task.done = true; 250 | }; 251 | Orchestrator.prototype._emitTaskDone = function (task, message, err) { 252 | if (!task.args) { 253 | task.args = {task:task.name}; 254 | } 255 | task.args.duration = task.duration; 256 | task.args.hrDuration = task.hrDuration; 257 | task.args.message = task.name+' '+message; 258 | var evt = 'stop'; 259 | if (err) { 260 | task.args.err = err; 261 | evt = 'err'; 262 | } 263 | // 'task_stop' or 'task_err' 264 | this.emit('task_'+evt, task.args); 265 | }; 266 | Orchestrator.prototype._runTask = function (task) { 267 | var that = this; 268 | 269 | task.args = {task:task.name, message:task.name+' started'}; 270 | this.emit('task_start', task.args); 271 | task.running = true; 272 | 273 | runTask(task.fn.bind(this), function (err, meta) { 274 | that._stopTask.call(that, task, meta); 275 | that._emitTaskDone.call(that, task, meta.runMethod, err); 276 | if (err) { 277 | return that.stop.call(that, err); 278 | } 279 | that._runStep.call(that); 280 | }); 281 | }; 282 | 283 | // FRAGILE: ASSUME: this list is an exhaustive list of events emitted 284 | var events = ['start','stop','err','task_start','task_stop','task_err','task_not_found','task_recursion']; 285 | 286 | var listenToEvent = function (target, event, callback) { 287 | target.on(event, function (e) { 288 | e.src = event; 289 | callback(e); 290 | }); 291 | }; 292 | 293 | Orchestrator.prototype.onAll = function (callback) { 294 | var i; 295 | if (typeof callback !== 'function') { 296 | throw new Error('No callback specified'); 297 | } 298 | 299 | for (i = 0; i < events.length; i++) { 300 | listenToEvent(this, events[i], callback); 301 | } 302 | }; 303 | 304 | module.exports = Orchestrator; 305 | -------------------------------------------------------------------------------- /lib/runTask.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | 3 | "use strict"; 4 | 5 | var eos = require('end-of-stream'); 6 | var consume = require('stream-consume'); 7 | 8 | module.exports = function (task, done) { 9 | var that = this, finish, cb, isDone = false, start, r; 10 | 11 | finish = function (err, runMethod) { 12 | var hrDuration = process.hrtime(start); 13 | 14 | if (isDone && !err) { 15 | err = new Error('task completion callback called too many times'); 16 | } 17 | isDone = true; 18 | 19 | var duration = hrDuration[0] + (hrDuration[1] / 1e9); // seconds 20 | 21 | done.call(that, err, { 22 | duration: duration, // seconds 23 | hrDuration: hrDuration, // [seconds,nanoseconds] 24 | runMethod: runMethod 25 | }); 26 | }; 27 | 28 | cb = function (err) { 29 | finish(err, 'callback'); 30 | }; 31 | 32 | try { 33 | start = process.hrtime(); 34 | r = task(cb); 35 | } catch (err) { 36 | return finish(err, 'catch'); 37 | } 38 | 39 | if (r && typeof r.then === 'function') { 40 | // wait for promise to resolve 41 | // FRAGILE: ASSUME: Promises/A+, see http://promises-aplus.github.io/promises-spec/ 42 | r.then(function () { 43 | finish(null, 'promise'); 44 | }, function(err) { 45 | finish(err, 'promise'); 46 | }); 47 | 48 | } else if (r && typeof r.pipe === 'function') { 49 | // wait for stream to end 50 | 51 | eos(r, { error: true, readable: r.readable, writable: r.writable && !r.readable }, function(err){ 52 | finish(err, 'stream'); 53 | }); 54 | 55 | // Ensure that the stream completes 56 | consume(r); 57 | 58 | } else if (task.length === 0) { 59 | // synchronous, function took in args.length parameters, and the callback was extra 60 | finish(null, 'sync'); 61 | 62 | //} else { 63 | // FRAGILE: ASSUME: callback 64 | 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "orchestrator", 3 | "description": "A module for sequencing and executing tasks and dependencies in maximum concurrency", 4 | "version": "0.3.8", 5 | "homepage": "https://github.com/robrich/orchestrator", 6 | "repository": "git://github.com/robrich/orchestrator.git", 7 | "author": "Rob Richardson (http://robrich.org/)", 8 | "main": "./index.js", 9 | "keywords": [ 10 | "async", 11 | "task", 12 | "parallel", 13 | "compose" 14 | ], 15 | "dependencies": { 16 | "end-of-stream": "^1.4.4", 17 | "sequencify": "~0.0.7", 18 | "stream-consume": "~0.1.0" 19 | }, 20 | "devDependencies": { 21 | "event-stream": "^4.0.1", 22 | "gulp-uglify": "^3.0.2", 23 | "jshint": "^2.9.4", 24 | "map-stream": "~0.0.6", 25 | "merge-stream": "^2.0.0", 26 | "mocha": "^8.0.1", 27 | "q": "^1.5.1", 28 | "should": "^13.2.3", 29 | "vinyl-fs": "~2.4.4" 30 | }, 31 | "scripts": { 32 | "test": "mocha" 33 | }, 34 | "license": "MIT" 35 | } 36 | -------------------------------------------------------------------------------- /test/add.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | 4 | "use strict"; 5 | 6 | var Orchestrator = require('../'); 7 | var should = require('should'); 8 | require('mocha'); 9 | 10 | describe('orchestrator', function() { 11 | describe('add()', function() { 12 | 13 | it('should define a task', function(done) { 14 | var orchestrator, fn; 15 | 16 | // Arrange 17 | fn = function() {}; 18 | 19 | // Act 20 | orchestrator = new Orchestrator(); 21 | orchestrator.add('test', fn); 22 | 23 | // Assert 24 | should.exist(orchestrator.tasks.test); 25 | orchestrator.tasks.test.fn.should.equal(fn); 26 | done(); 27 | }); 28 | 29 | var failTest = function (one, two, three) { 30 | var orchestrator, actualErr; 31 | 32 | // Arrange 33 | orchestrator = new Orchestrator(); 34 | 35 | // Act 36 | try { 37 | orchestrator.add(one, two, three); 38 | } catch (err) { 39 | actualErr = err; 40 | } 41 | 42 | // Assert 43 | should.exist(actualErr); 44 | should.ok(actualErr.message.indexOf('Task') > -1); 45 | }; 46 | 47 | it('should error if name is not a string', function (done) { 48 | var name, fn; 49 | 50 | // Arrange 51 | name = 9; // not a string 52 | fn = function () {}; 53 | 54 | // Act & Assert 55 | failTest(name, fn); 56 | done(); 57 | }); 58 | 59 | it('should error if dep is not an array', function (done) { 60 | var name, dep, fn; 61 | 62 | // Arrange 63 | name = "name"; 64 | dep = 9; // not an array 65 | fn = function () {}; 66 | 67 | // Act & Assert 68 | failTest(name, dep, fn); 69 | done(); 70 | }); 71 | 72 | it('should error if dep contains a non-string', function (done) { 73 | var name, dep, fn; 74 | 75 | // Arrange 76 | name = "name"; 77 | dep = 9; // not an array 78 | fn = function () {}; 79 | 80 | // Act & Assert 81 | failTest(name, dep, fn); 82 | done(); 83 | }); 84 | 85 | it('should error if fn is not a function', function (done) { 86 | var name, fn; 87 | 88 | // Arrange 89 | name = "name"; 90 | fn = 9; // not a function 91 | 92 | // Act & Assert 93 | failTest(name, fn); 94 | done(); 95 | }); 96 | 97 | it('should error if fn is not a function and there are dependencies', function (done) { 98 | var name, dep, fn; 99 | 100 | // Arrange 101 | name = "name"; 102 | dep = ['name']; 103 | fn = 9; // not a function 104 | 105 | // Act & Assert 106 | failTest(name, dep, fn); 107 | done(); 108 | }); 109 | 110 | it('should accept dependencies with no function', function (done) { 111 | var orchestrator, name, dep; 112 | 113 | // Arrange 114 | name = "name"; 115 | dep = ['name']; 116 | 117 | // Act 118 | orchestrator = new Orchestrator(); 119 | orchestrator.add(name, dep); 120 | 121 | // Assert 122 | should.exist(orchestrator.tasks.name); 123 | orchestrator.tasks.name.dep.should.equal(dep); 124 | done(); 125 | }); 126 | 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /test/doneCallback.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | "use strict"; 4 | 5 | var Orchestrator = require('../'); 6 | var Q = require('q'); 7 | var fs = require('fs'); 8 | var should = require('should'); 9 | require('mocha'); 10 | 11 | describe('orchestrator', function() { 12 | describe('stop() callback', function() { 13 | 14 | it('should have empty error on succeeding tasks', function(done) { 15 | var orchestrator, a; 16 | 17 | // Arrange 18 | orchestrator = new Orchestrator(); 19 | a = 0; 20 | orchestrator.add('test', function() { 21 | ++a; 22 | }); 23 | 24 | // Act 25 | orchestrator.start('test', function(err) { 26 | // Assert 27 | a.should.equal(1); 28 | should.not.exist(err); 29 | orchestrator.isRunning.should.equal(false); 30 | done(); 31 | }); 32 | }); 33 | 34 | it('should have error on fail by callback tasks', function(done) { 35 | var orchestrator, a, expectedErr = {message:'the error'}; 36 | 37 | // Arrange 38 | orchestrator = new Orchestrator(); 39 | a = 0; 40 | orchestrator.add('test', function (cb) { 41 | ++a; 42 | cb(expectedErr); 43 | }); 44 | 45 | // Act 46 | orchestrator.start('test', function(err) { 47 | // Assert 48 | a.should.equal(1); 49 | should.exist(err); 50 | should.deepEqual(err, expectedErr); 51 | orchestrator.isRunning.should.equal(false); 52 | done(); 53 | }); 54 | }); 55 | 56 | it('should have error on sync throw', function(done) { 57 | var orchestrator, a, errMess = 'the error message'; 58 | 59 | // Arrange 60 | orchestrator = new Orchestrator(); 61 | a = 0; 62 | orchestrator.add('test', function () { 63 | ++a; 64 | throw new Error(errMess); 65 | }); 66 | 67 | // Act 68 | orchestrator.start('test', function(err) { 69 | // Assert 70 | a.should.equal(1); 71 | should.exist(err); 72 | err.message.should.equal(errMess); 73 | orchestrator.isRunning.should.equal(false); 74 | done(); 75 | }); 76 | }); 77 | 78 | it('should have error on promise rejected', function(done) { 79 | var orchestrator, a, expectedErr = 'the error message'; 80 | 81 | // Arrange 82 | orchestrator = new Orchestrator(); 83 | a = 0; 84 | orchestrator.add('test', function () { 85 | var deferred = Q.defer(); 86 | setTimeout(function () { 87 | ++a; 88 | deferred.reject(expectedErr); 89 | },1); 90 | return deferred.promise; 91 | }); 92 | 93 | // Act 94 | orchestrator.start('test', function(actualErr) { 95 | // Assert 96 | a.should.equal(1); 97 | should.exist(actualErr); 98 | actualErr.should.equal(expectedErr); 99 | orchestrator.isRunning.should.equal(false); 100 | done(); 101 | }); 102 | }); 103 | 104 | it('should have error on stream error', function(done) { 105 | var orchestrator; 106 | 107 | // Arrange 108 | orchestrator = new Orchestrator(); 109 | orchestrator.add('test', function () { 110 | return fs.createReadStream('./index.js') 111 | .pipe(fs.createWriteStream('./something/that/does/not/exist')); 112 | }); 113 | 114 | // Act 115 | orchestrator.start('test', function(actualErr) { 116 | // Assert 117 | should.exist(actualErr); 118 | orchestrator.isRunning.should.equal(false); 119 | done(); 120 | }); 121 | }); 122 | 123 | it('should have error on missing task', function(done) { 124 | var orchestrator, name, a, expectedErr; 125 | 126 | // Arrange 127 | name = 'test'; 128 | a = 0; 129 | orchestrator = new Orchestrator(); 130 | // no 'test' task defined 131 | 132 | // Act 133 | orchestrator.on('task_not_found', function (e) { 134 | a++; 135 | e.task.should.equal(name); 136 | e.message.should.match(/not defined/i, e.message+' should include not defined'); 137 | should.exist(e.err); 138 | expectedErr = e.err; 139 | }); 140 | orchestrator.start(name, function(actualErr) { 141 | // Assert 142 | a.should.equal(1); 143 | should.exist(actualErr); 144 | actualErr.should.equal(expectedErr); 145 | orchestrator.isRunning.should.equal(false); 146 | done(); 147 | }); 148 | }); 149 | 150 | it('should have error on recursive tasks', function(done) { 151 | var orchestrator, name, a, expectedErr; 152 | 153 | // Arrange 154 | name = 'test'; 155 | a = 0; 156 | orchestrator = new Orchestrator(); 157 | orchestrator.add(name, [name]); 158 | 159 | // Act 160 | orchestrator.on('task_recursion', function (e) { 161 | a++; 162 | e.recursiveTasks.length.should.equal(2); 163 | e.recursiveTasks[0].should.equal(name); 164 | e.recursiveTasks[1].should.equal(name); 165 | e.message.should.match(/recursive/i, e.message+' should include recursive'); 166 | should.exist(e.err); 167 | expectedErr = e.err; 168 | }); 169 | orchestrator.start(name, function(actualErr) { 170 | // Assert 171 | a.should.equal(1); 172 | should.exist(actualErr); 173 | actualErr.should.equal(expectedErr); 174 | orchestrator.isRunning.should.equal(false); 175 | done(); 176 | }); 177 | }); 178 | 179 | it('should have error when calling callback too many times', function(done) { 180 | var orchestrator, a, timeout = 30; 181 | 182 | // Arrange 183 | orchestrator = new Orchestrator(); 184 | a = 0; 185 | orchestrator.add('test', function (cb) { 186 | cb(null); 187 | cb(null); 188 | }); 189 | 190 | // Act 191 | orchestrator.start('test', function(err) { 192 | // Assert 193 | switch (a) { 194 | case 0: 195 | // first finish 196 | should.not.exist(err); 197 | break; 198 | case 1: 199 | // second finish 200 | should.exist(err); 201 | err.message.indexOf('too many times').should.be.above(-1); 202 | break; 203 | default: 204 | done('finished too many times'); 205 | } 206 | a++; 207 | }); 208 | setTimeout(function () { 209 | a.should.equal(2); 210 | done(); 211 | }, timeout); 212 | }); 213 | 214 | }); 215 | }); 216 | -------------------------------------------------------------------------------- /test/emitTaskDone.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | 4 | "use strict"; 5 | 6 | var Orchestrator = require('../'); 7 | var should = require('should'); 8 | require('mocha'); 9 | 10 | describe('orchestrator', function() { 11 | describe('_emitTaskDone()', function() { 12 | 13 | it('should emit task_stop if no err', function(done) { 14 | var orchestrator, taskName, task, message, err; 15 | 16 | // Arrange 17 | taskName = 'test'; 18 | message = 'test message'; 19 | task = { 20 | name: taskName, 21 | fn: function() {} 22 | }; 23 | //err = undefined; 24 | 25 | // Thing under test 26 | orchestrator = new Orchestrator(); 27 | 28 | orchestrator.on('task_stop', function (/*args*/) { 29 | // Assert 30 | done(); 31 | }); 32 | 33 | // Act 34 | orchestrator._emitTaskDone(task, message, err); 35 | }); 36 | 37 | it('should emit task_err if err', function(done) { 38 | var orchestrator, taskName, task, message, err; 39 | 40 | // Arrange 41 | taskName = 'test'; 42 | message = 'test message'; 43 | task = { 44 | name: taskName, 45 | fn: function() {} 46 | }; 47 | err = 'the error'; 48 | 49 | // Thing under test 50 | orchestrator = new Orchestrator(); 51 | 52 | orchestrator.on('task_err', function (/*args*/) { 53 | // Assert 54 | done(); 55 | }); 56 | 57 | // Act 58 | orchestrator._emitTaskDone(task, message, err); 59 | }); 60 | 61 | it('should pass task, message, duration', function(done) { 62 | var orchestrator, taskName, task, message, duration, err; 63 | 64 | // Arrange 65 | taskName = 'test'; 66 | message = 'test message'; 67 | duration = 1.1; 68 | task = { 69 | name: taskName, 70 | fn: function() {}, 71 | duration: duration 72 | }; 73 | //err = undefined; 74 | 75 | // Thing under test 76 | orchestrator = new Orchestrator(); 77 | 78 | orchestrator.on('task_stop', function (args) { 79 | // Assert 80 | args.task.should.equal(taskName); 81 | args.duration.should.equal(duration); 82 | args.message.should.equal(taskName+' '+message); 83 | done(); 84 | }); 85 | 86 | // Act 87 | orchestrator._emitTaskDone(task, message, err); 88 | }); 89 | 90 | it('should pass err', function(done) { 91 | var orchestrator, taskName, task, message, err; 92 | 93 | // Arrange 94 | taskName = 'test'; 95 | message = 'test message'; 96 | task = { 97 | name: taskName, 98 | fn: function() {} 99 | }; 100 | err = 'the error'; 101 | 102 | // Thing under test 103 | orchestrator = new Orchestrator(); 104 | 105 | orchestrator.on('task_err', function (args) { 106 | // Assert 107 | args.err.should.equal(err); 108 | done(); 109 | }); 110 | 111 | // Act 112 | orchestrator._emitTaskDone(task, message, err); 113 | }); 114 | 115 | it('should die if no task passed', function(done) { 116 | // Arrange 117 | var orchestrator = new Orchestrator(); 118 | var succeed = false; 119 | 120 | // Act 121 | try { 122 | orchestrator._emitTaskDone(); 123 | succeed = true; 124 | } catch (err) { 125 | succeed = false; 126 | } 127 | 128 | // Assert 129 | succeed.should.equal(false); 130 | done(); 131 | }); 132 | 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /test/gulpTask.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | 'use strict'; 4 | 5 | var Orchestrator = require('../'); 6 | var Q = require('q'); 7 | var vfs = require('vinyl-fs'); 8 | var es = require('event-stream'); 9 | var ms = require('merge-stream'); 10 | var uglify = require('gulp-uglify'); 11 | var should = require('should'); 12 | require('mocha'); 13 | 14 | describe('orchestrator tasks integration tests', function() { 15 | var gulp = new Orchestrator(); 16 | describe('task()', function() { 17 | it('should define a task', function(done) { 18 | var fn; 19 | fn = function() {}; 20 | gulp.task('test', fn); 21 | should.exist(gulp.tasks.test); 22 | gulp.tasks.test.fn.should.equal(fn); 23 | gulp.reset(); 24 | done(); 25 | }); 26 | }); 27 | describe('start()', function() { 28 | it('should run multiple tasks', function(done) { 29 | var a, fn, fn2; 30 | a = 0; 31 | fn = function() { 32 | this.should.equal(gulp); 33 | ++a; 34 | }; 35 | fn2 = function() { 36 | this.should.equal(gulp); 37 | ++a; 38 | }; 39 | gulp.task('test', fn); 40 | gulp.task('test2', fn2); 41 | gulp.start('test', 'test2'); 42 | a.should.equal(2); 43 | gulp.reset(); 44 | done(); 45 | }); 46 | it('should run all tasks when call start() multiple times', function(done) { 47 | var a, fn, fn2; 48 | a = 0; 49 | fn = function() { 50 | this.should.equal(gulp); 51 | ++a; 52 | }; 53 | fn2 = function() { 54 | this.should.equal(gulp); 55 | ++a; 56 | }; 57 | gulp.task('test', fn); 58 | gulp.task('test2', fn2); 59 | gulp.start('test'); 60 | gulp.start('test2'); 61 | a.should.equal(2); 62 | gulp.reset(); 63 | done(); 64 | }); 65 | it('should run all async promise tasks', function(done) { 66 | var a, fn, fn2; 67 | a = 0; 68 | fn = function() { 69 | var deferred = Q.defer(); 70 | setTimeout(function () { 71 | ++a; 72 | deferred.resolve(); 73 | },1); 74 | return deferred.promise; 75 | }; 76 | fn2 = function() { 77 | var deferred = Q.defer(); 78 | setTimeout(function () { 79 | ++a; 80 | deferred.resolve(); 81 | },1); 82 | return deferred.promise; 83 | }; 84 | gulp.task('test', fn); 85 | gulp.task('test2', fn2); 86 | gulp.start('test'); 87 | gulp.start('test2', function () { 88 | gulp.isRunning.should.equal(false); 89 | a.should.equal(2); 90 | gulp.reset(); 91 | done(); 92 | }); 93 | gulp.isRunning.should.equal(true); 94 | }); 95 | it('should run all async callback tasks', function(done) { 96 | var a, fn, fn2; 97 | a = 0; 98 | fn = function(cb) { 99 | setTimeout(function () { 100 | ++a; 101 | cb(null); 102 | },1); 103 | }; 104 | fn2 = function(cb) { 105 | setTimeout(function () { 106 | ++a; 107 | cb(null); 108 | },1); 109 | }; 110 | gulp.task('test', fn); 111 | gulp.task('test2', fn2); 112 | gulp.start('test'); 113 | gulp.start('test2', function () { 114 | gulp.isRunning.should.equal(false); 115 | a.should.equal(2); 116 | gulp.reset(); 117 | done(); 118 | }); 119 | gulp.isRunning.should.equal(true); 120 | }); 121 | it('should run all async stream tasks', function(done){ 122 | var a, fn, fn2, fn3, fn4; 123 | a = 0; 124 | fn = function(cb) { 125 | return vfs.src('./index.js') 126 | .pipe(uglify()); 127 | }; 128 | fn2 = function(cb) { 129 | var stream1 = vfs.src('./index.js') 130 | .pipe(vfs.dest('./test/.tmp')); 131 | var stream2 = vfs.src('./index.js') 132 | .pipe(vfs.dest('./test/.tmp')); 133 | return es.concat(stream1, stream2); 134 | }; 135 | fn3 = function(cb) { 136 | var stream1 = vfs.src('./index.js') 137 | .pipe(vfs.dest('./test/.tmp')); 138 | var stream2 = vfs.src('./index.js') 139 | .pipe(vfs.dest('./test/.tmp')); 140 | return ms(stream1, stream2); 141 | }; 142 | fn4 = function(cb) { 143 | return vfs.src('./index.js') 144 | .pipe(vfs.dest('./test/.tmp')); 145 | }; 146 | gulp.task('test', fn); 147 | gulp.task('test2', fn2); 148 | gulp.task('test3', fn3); 149 | gulp.task('test4', fn4); 150 | gulp.on('task_stop', function(){ 151 | ++a; 152 | }); 153 | gulp.start('test'); 154 | gulp.start('test2'); 155 | gulp.start('test3'); 156 | gulp.start('test4', function () { 157 | gulp.isRunning.should.equal(false); 158 | a.should.equal(4); 159 | done(); 160 | }); 161 | }); 162 | it('should emit task_not_found and throw an error when task is not defined', function(done) { 163 | gulp.reset(); 164 | var aErr; 165 | gulp.on('task_not_found', function(err){ 166 | should.exist(err); 167 | should.exist(err.task); 168 | err.task.should.equal('test'); 169 | gulp.reset(); 170 | done(); 171 | }); 172 | try { 173 | gulp.start('test'); 174 | } catch (err) { 175 | aErr = err; 176 | } 177 | should.exist(aErr); 178 | }); 179 | }); 180 | }); 181 | -------------------------------------------------------------------------------- /test/hasTask.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | 4 | "use strict"; 5 | 6 | var Orchestrator = require('../'); 7 | require('should'); 8 | require('mocha'); 9 | 10 | describe('orchestrator', function() { 11 | describe('hasTask()', function() { 12 | 13 | it('should return true if there is a task', function(done) { 14 | var orchestrator, name, task1, expected, actual; 15 | 16 | // Arrange 17 | name = 'task1'; 18 | task1 = { 19 | name: name, 20 | fn: function() {} 21 | }; 22 | 23 | // the thing under test 24 | orchestrator = new Orchestrator(); 25 | orchestrator.tasks[name] = task1; 26 | 27 | // Act 28 | expected = true; 29 | actual = orchestrator.hasTask(name); 30 | 31 | // Assert 32 | actual.should.equal(expected); 33 | done(); 34 | }); 35 | 36 | it('should return false if there is no such task', function(done) { 37 | var orchestrator, name, task1, expected, actual; 38 | 39 | // Arrange 40 | name = 'task1'; 41 | task1 = { 42 | name: name, 43 | fn: function() {} 44 | }; 45 | 46 | // the thing under test 47 | orchestrator = new Orchestrator(); 48 | orchestrator.tasks[name] = task1; 49 | 50 | // Act 51 | expected = false; 52 | actual = orchestrator.hasTask('not'+name); 53 | 54 | // Assert 55 | actual.should.equal(expected); 56 | done(); 57 | }); 58 | 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/onAll.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | 4 | "use strict"; 5 | 6 | var Orchestrator = require('../'); 7 | var should = require('should'); 8 | require('mocha'); 9 | 10 | describe('orchestrator', function() { 11 | describe('onAll()', function() { 12 | 13 | it('should wire up event listener', function(done) { 14 | var orchestrator, cb, a = 0; 15 | 16 | // Arrange 17 | cb = function() { 18 | ++a; 19 | }; 20 | orchestrator = new Orchestrator(); 21 | 22 | // Act 23 | orchestrator.onAll(cb); 24 | orchestrator.emit('start', {}); // fake do action that would fire event 25 | 26 | // Assert 27 | a.should.equal(1); 28 | done(); 29 | }); 30 | 31 | it('should add src to event args', function(done) { 32 | var orchestrator, cb, actualE; 33 | 34 | // Arrange 35 | cb = function(e) { 36 | actualE = e; 37 | }; 38 | orchestrator = new Orchestrator(); 39 | 40 | // Act 41 | orchestrator.onAll(cb); 42 | orchestrator.emit('stop', {}); // fake do action that would fire event 43 | 44 | // Assert 45 | should.exist(actualE); 46 | actualE.src.should.equal('stop'); 47 | done(); 48 | }); 49 | 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/readyToRunTask.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | 4 | "use strict"; 5 | 6 | var Orchestrator = require('../'); 7 | var should = require('should'); 8 | require('mocha'); 9 | 10 | describe('orchestrator', function() { 11 | describe('_readyToRunTask() task is ready when dependencies are resolved', function() { 12 | 13 | it('should be ready if no dependencies', function(done) { 14 | var orchestrator, task, expected, actual; 15 | 16 | // Arrange 17 | expected = true; 18 | task = { 19 | name: 'a', 20 | dep: [] 21 | }; 22 | 23 | // Act 24 | orchestrator = new Orchestrator(); 25 | orchestrator.tasks = { a: task }; 26 | actual = orchestrator._readyToRunTask(task); 27 | 28 | // Assert 29 | actual.should.equal(expected); 30 | done(); 31 | }); 32 | 33 | it('should be ready if dependency is done', function(done) { 34 | var orchestrator, task, dep, expected, actual; 35 | 36 | // Arrange 37 | expected = true; 38 | task = { 39 | name: 'a', 40 | dep: ['b'] 41 | }; 42 | dep = { 43 | name: 'b', 44 | dep: [], 45 | done: true 46 | }; 47 | 48 | // Act 49 | orchestrator = new Orchestrator(); 50 | orchestrator.tasks = { a: task, b: dep }; 51 | actual = orchestrator._readyToRunTask(task); 52 | 53 | // Assert 54 | actual.should.equal(expected); 55 | done(); 56 | }); 57 | 58 | it('should not be ready if dependency is not done', function(done) { 59 | var orchestrator, task, dep, expected, actual; 60 | 61 | // Arrange 62 | expected = false; 63 | task = { 64 | name: 'a', 65 | dep: ['b'] 66 | }; 67 | dep = { 68 | name: 'b', 69 | dep: [] 70 | //done: lack of var is falsey 71 | }; 72 | 73 | // Act 74 | orchestrator = new Orchestrator(); 75 | orchestrator.tasks = { a: task, b: dep }; 76 | actual = orchestrator._readyToRunTask(task); 77 | 78 | // Assert 79 | actual.should.equal(expected); 80 | done(); 81 | }); 82 | 83 | it('should stop() if dependency is missing', function(done) { 84 | var orchestrator, task, cb, expected, actual, expectedErr; 85 | 86 | // Arrange 87 | expected = false; 88 | task = { 89 | name: 'a', 90 | dep: ['b'] // which doesn't exist 91 | }; 92 | cb = function (err) { 93 | expectedErr = err; 94 | }; 95 | 96 | // Act 97 | orchestrator = new Orchestrator(); 98 | orchestrator.tasks = { a: task }; 99 | orchestrator.doneCallback = cb; 100 | actual = orchestrator._readyToRunTask(task); 101 | 102 | // Assert 103 | should.exist(expectedErr); 104 | expectedErr.indexOf('exist').should.be.above(-1); 105 | actual.should.equal(expected); 106 | done(); 107 | }); 108 | 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /test/resetAllTasks.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | "use strict"; 4 | 5 | var Orchestrator = require('../'); 6 | var should = require('should'); 7 | require('mocha'); 8 | 9 | describe('orchestrator', function() { 10 | describe('_resetAllTasks()', function() { 11 | 12 | it('should set done = false on all done tasks', function(done) { 13 | var orchestrator, task1, task2; 14 | 15 | // Arrange 16 | task1 = { 17 | name: 'test1', 18 | fn: function() {}, 19 | done: true 20 | }; 21 | task2 = { 22 | name: 'test2', 23 | fn: function() {}, 24 | done: true 25 | }; 26 | 27 | // the thing under test 28 | orchestrator = new Orchestrator(); 29 | orchestrator.tasks = { 30 | task1: task1, 31 | task2: task2 32 | }; 33 | 34 | // Act 35 | orchestrator._resetAllTasks(); 36 | 37 | // Assert 38 | task1.done.should.equal(false); 39 | task2.done.should.equal(false); 40 | done(); 41 | }); 42 | 43 | it('should not set done = false if done does not exist', function(done) { 44 | var orchestrator, task1, task2; 45 | 46 | // Arrange 47 | task1 = { 48 | name: 'test1', 49 | fn: function() {} 50 | // no done 51 | }; 52 | task2 = { 53 | name: 'test2', 54 | fn: function() {} 55 | // no done 56 | }; 57 | 58 | // the thing under test 59 | orchestrator = new Orchestrator(); 60 | orchestrator.tasks = { 61 | task1: task1, 62 | task2: task2 63 | }; 64 | 65 | // Act 66 | orchestrator._resetAllTasks(); 67 | 68 | // Assert 69 | should.not.exist(task1.done); 70 | should.not.exist(task2.done); 71 | done(); 72 | }); 73 | 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/resetSpecificTasks.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | "use strict"; 4 | 5 | var Orchestrator = require('../'); 6 | var should = require('should'); 7 | require('mocha'); 8 | 9 | describe('orchestrator', function() { 10 | describe('_resetSpecificTasks()', function() { 11 | 12 | it('should set done = false on specified done tasks', function(done) { 13 | var orchestrator, task1, task2; 14 | 15 | // Arrange 16 | task1 = { 17 | name: 'test1', 18 | fn: function() {}, 19 | done: true 20 | }; 21 | task2 = { 22 | name: 'test2', 23 | fn: function() {}, 24 | done: true 25 | }; 26 | 27 | // the thing under test 28 | orchestrator = new Orchestrator(); 29 | orchestrator.tasks = { 30 | test1: task1, 31 | test2: task2 32 | }; 33 | 34 | // Act 35 | orchestrator._resetSpecificTasks(['test1','test2']); 36 | 37 | // Assert 38 | task1.done.should.equal(false); 39 | task2.done.should.equal(false); 40 | done(); 41 | }); 42 | 43 | it('should not set done = false if done does not exist', function(done) { 44 | var orchestrator, task1, task2; 45 | 46 | // Arrange 47 | task1 = { 48 | name: 'test1', 49 | fn: function() {} 50 | // no done 51 | }; 52 | task2 = { 53 | name: 'test2', 54 | fn: function() {} 55 | // no done 56 | }; 57 | 58 | // the thing under test 59 | orchestrator = new Orchestrator(); 60 | orchestrator.tasks = { 61 | test1: task1, 62 | test2: task2 63 | }; 64 | 65 | // Act 66 | orchestrator._resetSpecificTasks(['test1','test2']); 67 | 68 | // Assert 69 | should.not.exist(task1.done); 70 | should.not.exist(task2.done); 71 | done(); 72 | }); 73 | 74 | it('should set done = false on done dependency', function(done) { 75 | var orchestrator, task, dep; 76 | 77 | // Arrange 78 | task = { 79 | name: 'test', 80 | dep: ['dep'], 81 | fn: function() {} 82 | // no done though irrelevant to current test 83 | }; 84 | dep = { 85 | name: 'dep', 86 | fn: function() {}, 87 | done: true 88 | }; 89 | 90 | // the thing under test 91 | orchestrator = new Orchestrator(); 92 | orchestrator.tasks = { 93 | test: task, 94 | dep: dep 95 | }; 96 | 97 | // Act 98 | orchestrator._resetSpecificTasks(['test']); 99 | 100 | // Assert 101 | dep.done.should.equal(false); 102 | done(); 103 | }); 104 | 105 | it('should not set done on irrelevant tasks', function(done) { 106 | var orchestrator, task, irrelevant; 107 | 108 | // Arrange 109 | task = { 110 | name: 'test', 111 | fn: function() {} 112 | // no done though irrelevant to current test 113 | }; 114 | irrelevant = { 115 | name: 'irrelevant', 116 | fn: function() {}, 117 | done: true 118 | }; 119 | 120 | // the thing under test 121 | orchestrator = new Orchestrator(); 122 | orchestrator.tasks = { 123 | test: task, 124 | irrelevant: irrelevant 125 | }; 126 | 127 | // Act 128 | orchestrator._resetSpecificTasks(['test']); 129 | 130 | // Assert 131 | irrelevant.done.should.equal(true); 132 | done(); 133 | }); 134 | 135 | it('should not die if dependency does not exist', function(done) { 136 | var orchestrator, task; 137 | 138 | // Arrange 139 | task = { 140 | name: 'test', 141 | dep: ['dep'], // dep doesn't exist 142 | fn: function() {} 143 | // no done though irrelevant to current test 144 | }; 145 | 146 | // the thing under test 147 | orchestrator = new Orchestrator(); 148 | orchestrator.tasks = { 149 | test: task 150 | }; 151 | 152 | // Act 153 | orchestrator._resetSpecificTasks(['test']); 154 | 155 | // Assert 156 | // we're still here so it worked 157 | done(); 158 | }); 159 | 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /test/resetTask.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | "use strict"; 4 | 5 | var Orchestrator = require('../'); 6 | var should = require('should'); 7 | require('mocha'); 8 | 9 | describe('orchestrator', function() { 10 | describe('_resetTask()', function() { 11 | 12 | it('should set done = false on done task', function(done) { 13 | var orchestrator, task; 14 | 15 | // Arrange 16 | task = { 17 | name: 'test', 18 | fn: function() {}, 19 | done: true 20 | }; 21 | 22 | // the thing under test 23 | orchestrator = new Orchestrator(); 24 | 25 | // Act 26 | orchestrator._resetTask(task); 27 | 28 | // Assert 29 | task.done.should.equal(false); 30 | done(); 31 | }); 32 | 33 | it('should not set done = false if done does not exist', function(done) { 34 | var orchestrator, task; 35 | 36 | // Arrange 37 | task = { 38 | name: 'test', 39 | fn: function() {} 40 | // no done 41 | }; 42 | 43 | // the thing under test 44 | orchestrator = new Orchestrator(); 45 | 46 | // Act 47 | orchestrator._resetTask(task); 48 | 49 | // Assert 50 | should.not.exist(task.done); 51 | done(); 52 | }); 53 | 54 | it('should remove start', function(done) { 55 | var orchestrator, task; 56 | 57 | // Arrange 58 | task = { 59 | name: 'test', 60 | fn: function() {}, 61 | start: new Date() 62 | }; 63 | 64 | // the thing under test 65 | orchestrator = new Orchestrator(); 66 | 67 | // Act 68 | orchestrator._resetTask(task); 69 | 70 | // Assert 71 | should.not.exist(task.start); 72 | done(); 73 | }); 74 | 75 | it('should remove stop', function(done) { 76 | var orchestrator, task; 77 | 78 | // Arrange 79 | task = { 80 | name: 'test', 81 | fn: function() {}, 82 | stop: new Date() 83 | }; 84 | 85 | // the thing under test 86 | orchestrator = new Orchestrator(); 87 | 88 | // Act 89 | orchestrator._resetTask(task); 90 | 91 | // Assert 92 | should.not.exist(task.stop); 93 | done(); 94 | }); 95 | 96 | it('should remove duration', function(done) { 97 | var orchestrator, task; 98 | 99 | // Arrange 100 | task = { 101 | name: 'test', 102 | fn: function() {}, 103 | duration: new Date() 104 | }; 105 | 106 | // the thing under test 107 | orchestrator = new Orchestrator(); 108 | 109 | // Act 110 | orchestrator._resetTask(task); 111 | 112 | // Assert 113 | should.not.exist(task.duration); 114 | done(); 115 | }); 116 | 117 | it('should remove args', function(done) { 118 | var orchestrator, task; 119 | 120 | // Arrange 121 | task = { 122 | name: 'test', 123 | fn: function() {}, 124 | args: {} 125 | }; 126 | 127 | // the thing under test 128 | orchestrator = new Orchestrator(); 129 | 130 | // Act 131 | orchestrator._resetTask(task); 132 | 133 | // Assert 134 | should.not.exist(task.args); 135 | done(); 136 | }); 137 | 138 | it('should not die if not passed a task', function(done) { 139 | // Arrange 140 | var orchestrator = new Orchestrator(); 141 | 142 | // Act 143 | orchestrator._resetTask(); 144 | 145 | // Assert 146 | // we're still here so it worked 147 | done(); 148 | }); 149 | 150 | }); 151 | }); 152 | -------------------------------------------------------------------------------- /test/runTask.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | 4 | "use strict"; 5 | 6 | var Orchestrator = require('../'); 7 | var should = require('should'); 8 | require('mocha'); 9 | 10 | describe('orchestrator', function() { 11 | describe('_runTask() tasks execute as expected', function() { 12 | 13 | it('calls task function', function(done) { 14 | var orchestrator, a, task; 15 | 16 | // Arrange 17 | a = 0; 18 | task = { 19 | name: 'test', 20 | fn: function() { 21 | ++a; 22 | } 23 | }; 24 | 25 | // Act 26 | orchestrator = new Orchestrator(); 27 | orchestrator._runTask(task); 28 | 29 | // Assert 30 | a.should.equal(1); 31 | done(); 32 | }); 33 | 34 | it('sets .running correctly', function(done) { 35 | var orchestrator, task; 36 | 37 | // Arrange 38 | task = { 39 | name: 'test', 40 | fn: function() { 41 | should.exist(task.running); 42 | task.running.should.equal(true); 43 | } 44 | }; 45 | 46 | // Act 47 | orchestrator = new Orchestrator(); 48 | orchestrator._runTask(task); 49 | 50 | // Assert 51 | should.exist(task.running); 52 | task.running.should.equal(false); 53 | done(); 54 | }); 55 | 56 | it('logs start', function(done) { 57 | var orchestrator, task, a = 0; 58 | 59 | // Arrange 60 | task = { 61 | name: 'test', 62 | fn: function() { 63 | } 64 | }; 65 | 66 | // Act 67 | orchestrator = new Orchestrator(); 68 | orchestrator.on('task_start', function (e) { 69 | should.exist(e.task); 70 | e.task.should.equal('test'); 71 | ++a; 72 | }); 73 | orchestrator._runTask(task); 74 | 75 | // Assert 76 | a.should.equal(1); 77 | done(); 78 | }); 79 | 80 | it('passes start args to stop event', function(done) { 81 | var orchestrator, task, passthrough, a = 0; 82 | 83 | // Arrange 84 | task = { 85 | name: 'test', 86 | fn: function() { 87 | } 88 | }; 89 | passthrough = 'passthrough'; 90 | 91 | // Act 92 | orchestrator = new Orchestrator(); 93 | orchestrator.on('task_start', function (e) { 94 | e.passthrough = passthrough; 95 | ++a; 96 | }); 97 | orchestrator.on('task_stop', function (e) { 98 | e.passthrough.should.equal(passthrough); 99 | ++a; 100 | }); 101 | orchestrator._runTask(task); 102 | 103 | // Assert 104 | a.should.equal(2); 105 | done(); 106 | }); 107 | 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /test/start.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | "use strict"; 4 | 5 | var Orchestrator = require('../'); 6 | var Q = require('q'); 7 | require('should'); 8 | require('mocha'); 9 | 10 | describe('orchestrator', function() { 11 | describe('run() tasks', function() { 12 | 13 | it('should run multiple tasks', function(done) { 14 | var orchestrator, a, fn, fn2; 15 | 16 | // Arrange 17 | orchestrator = new Orchestrator(); 18 | a = 0; 19 | fn = function() { 20 | ++a; 21 | }; 22 | fn2 = function() { 23 | ++a; 24 | }; 25 | orchestrator.add('test', fn); 26 | orchestrator.add('test2', fn2); 27 | 28 | // Act 29 | orchestrator.start('test', 'test2'); 30 | 31 | // Assert 32 | a.should.equal(2); 33 | done(); 34 | }); 35 | 36 | it('should run multiple tasks as array', function(done) { 37 | var orchestrator, a, fn, fn2, fn3; 38 | 39 | // Arrange 40 | orchestrator = new Orchestrator(); 41 | a = 0; 42 | fn = function() { 43 | ++a; 44 | }; 45 | fn2 = function() { 46 | ++a; 47 | }; 48 | fn3 = function() { 49 | throw new Error('run wrong task'); 50 | }; 51 | orchestrator.add('test', fn); 52 | orchestrator.add('test2', fn2); 53 | orchestrator.add('test3', fn3); 54 | 55 | // Act 56 | orchestrator.start(['test', 'test2']); 57 | 58 | // Assert 59 | a.should.equal(2); 60 | done(); 61 | }); 62 | 63 | it('should run all tasks when call run() multiple times', function(done) { 64 | var orchestrator, a, fn, fn2; 65 | 66 | // Arrange 67 | orchestrator = new Orchestrator(); 68 | a = 0; 69 | fn = function() { 70 | ++a; 71 | }; 72 | fn2 = function() { 73 | ++a; 74 | }; 75 | orchestrator.add('test', fn); 76 | orchestrator.add('test2', fn2); 77 | 78 | // Act 79 | orchestrator.start('test'); 80 | orchestrator.start('test2'); 81 | 82 | // Assert 83 | a.should.equal(2); 84 | done(); 85 | }); 86 | 87 | it('should add new tasks at the front of the queue', function(done) { 88 | var orchestrator, a, fn, fn2, fn3, aAtFn2, aAtFn3, test2pos, test3pos; 89 | 90 | // Arrange 91 | orchestrator = new Orchestrator(); 92 | a = 0; 93 | fn = function() { 94 | ++a; 95 | orchestrator.start('test3'); 96 | }; 97 | fn2 = function() { 98 | ++a; 99 | aAtFn2 = a; 100 | }; 101 | fn3 = function() { 102 | ++a; 103 | aAtFn3 = a; 104 | }; 105 | orchestrator.add('test', fn); 106 | orchestrator.add('test2', fn2); 107 | orchestrator.add('test3', fn3); 108 | 109 | // Act 110 | orchestrator.start('test', 'test2'); 111 | 112 | // Assert 113 | aAtFn3.should.equal(2); // 1 and 3 ran 114 | aAtFn2.should.equal(3); // 1, 3, and 2 ran 115 | a.should.equal(3); 116 | test2pos = orchestrator.seq.indexOf('test2'); 117 | test3pos = orchestrator.seq.indexOf('test3'); 118 | test2pos.should.be.above(-1); 119 | test3pos.should.be.above(-1); 120 | test2pos.should.be.above(test3pos); 121 | done(); 122 | }); 123 | 124 | it('should run all tasks when call run() multiple times', function(done) { 125 | var orchestrator, a, fn, fn2; 126 | 127 | // Arrange 128 | orchestrator = new Orchestrator(); 129 | a = 0; 130 | fn = function() { 131 | ++a; 132 | }; 133 | fn2 = function() { 134 | ++a; 135 | }; 136 | orchestrator.add('test', fn); 137 | orchestrator.add('test2', fn2); 138 | 139 | // Act 140 | orchestrator.start('test'); 141 | orchestrator.start('test2'); 142 | 143 | // Assert 144 | a.should.equal(2); 145 | done(); 146 | }); 147 | 148 | it('should run all tasks when call run() with no arguments', function(done) { 149 | var orchestrator, a, fn, fn2; 150 | 151 | // Arrange 152 | orchestrator = new Orchestrator(); 153 | a = 0; 154 | fn = function() { 155 | ++a; 156 | }; 157 | fn2 = function() { 158 | ++a; 159 | }; 160 | orchestrator.add('test', fn); 161 | orchestrator.add('test2', fn2); 162 | 163 | // Act 164 | orchestrator.start(); 165 | 166 | // Assert 167 | a.should.equal(2); 168 | done(); 169 | }); 170 | 171 | it('should run all async promise tasks', function(done) { 172 | var orchestrator, a, fn, fn2; 173 | 174 | // Arrange 175 | orchestrator = new Orchestrator(); 176 | a = 0; 177 | fn = function() { 178 | var deferred = Q.defer(); 179 | setTimeout(function () { 180 | ++a; 181 | deferred.resolve(); 182 | },1); 183 | return deferred.promise; 184 | }; 185 | fn2 = function() { 186 | var deferred = Q.defer(); 187 | setTimeout(function () { 188 | ++a; 189 | deferred.resolve(); 190 | },1); 191 | return deferred.promise; 192 | }; 193 | orchestrator.add('test', fn); 194 | orchestrator.add('test2', fn2); 195 | 196 | // Act 197 | orchestrator.start('test'); 198 | orchestrator.start('test2', function () { 199 | // Assert 200 | orchestrator.isRunning.should.equal(false); 201 | a.should.equal(2); 202 | done(); 203 | }); 204 | orchestrator.isRunning.should.equal(true); 205 | }); 206 | 207 | it('should run all async callback tasks', function(done) { 208 | var orchestrator, a, fn, fn2; 209 | 210 | // Arrange 211 | orchestrator = new Orchestrator(); 212 | a = 0; 213 | fn = function(cb) { 214 | setTimeout(function () { 215 | ++a; 216 | cb(null); 217 | },1); 218 | }; 219 | fn2 = function(cb) { 220 | setTimeout(function () { 221 | ++a; 222 | cb(null); 223 | },1); 224 | }; 225 | orchestrator.add('test', fn); 226 | orchestrator.add('test2', fn2); 227 | 228 | // Act 229 | orchestrator.start('test'); 230 | orchestrator.start('test2', function () { 231 | // Assert 232 | orchestrator.isRunning.should.equal(false); 233 | a.should.equal(2); 234 | done(); 235 | }); 236 | orchestrator.isRunning.should.equal(true); 237 | }); 238 | 239 | it('should run task scoped to orchestrator', function(done) { 240 | var orchestrator, a, fn; 241 | 242 | // Arrange 243 | orchestrator = new Orchestrator(); 244 | a = 0; 245 | fn = function() { 246 | this.should.equal(orchestrator); 247 | ++a; 248 | }; 249 | orchestrator.add('test', fn); 250 | 251 | // Act 252 | orchestrator.start('test'); 253 | 254 | // Assert 255 | a.should.equal(1); 256 | orchestrator.isRunning.should.equal(false); 257 | done(); 258 | }); 259 | 260 | // FRAGILE: It resets task.done at `.start()` so if task isn't finished when you call `start()` again, it won't run again 261 | 262 | it('should run task multiple times when call run(task) multiple times', function(done) { 263 | var orchestrator, a, fn; 264 | 265 | // Arrange 266 | orchestrator = new Orchestrator(); 267 | a = 0; 268 | fn = function() { 269 | ++a; 270 | }; 271 | orchestrator.add('test', fn); 272 | 273 | // Act 274 | orchestrator.start('test'); 275 | orchestrator.start('test'); 276 | 277 | // Assert 278 | a.should.equal(2); 279 | done(); 280 | }); 281 | 282 | it('should run task dependencies multiple times when call run(task) multiple times', function(done) { 283 | var orchestrator, a, fn, dep; 284 | 285 | // Arrange 286 | orchestrator = new Orchestrator(); 287 | a = 0; 288 | dep = function() { 289 | ++a; 290 | }; 291 | fn = function() { 292 | }; 293 | orchestrator.add('dep', dep); 294 | orchestrator.add('test', ['dep'], fn); 295 | 296 | // Act 297 | orchestrator.start('test'); 298 | orchestrator.start('test'); 299 | 300 | // Assert 301 | a.should.equal(2); 302 | done(); 303 | }); 304 | 305 | it('should run task multiple times when call run() (default) multiple times', function(done) { 306 | var orchestrator, a, fn; 307 | 308 | // Arrange 309 | orchestrator = new Orchestrator(); 310 | a = 0; 311 | fn = function() { 312 | ++a; 313 | }; 314 | orchestrator.add('test', fn); 315 | orchestrator.add('default', ['test'], function () {}); 316 | 317 | // Act 318 | orchestrator.start(function () { 319 | // Finished first run, now run a second time 320 | orchestrator.start(function () { 321 | 322 | // Assert 323 | a.should.equal(2); 324 | done(); 325 | }); 326 | }); 327 | }); 328 | 329 | it('should run no-op task', function(done) { 330 | var orchestrator; 331 | 332 | // Arrange 333 | orchestrator = new Orchestrator(); 334 | orchestrator.add('test'); 335 | 336 | // Act 337 | orchestrator.start(function () { 338 | // Assert 339 | done(); 340 | }); 341 | }); 342 | 343 | }); 344 | }); 345 | -------------------------------------------------------------------------------- /test/stop.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | "use strict"; 4 | 5 | var Orchestrator = require('../'); 6 | var should = require('should'); 7 | require('mocha'); 8 | 9 | describe('orchestrator', function() { 10 | describe('stop()', function() { 11 | 12 | it('should call doneCallback', function(done) { 13 | var orchestrator, a = 0; 14 | 15 | // Arrange 16 | orchestrator = new Orchestrator(); 17 | orchestrator.doneCallback = function (/*err*/) { 18 | a++; 19 | }; 20 | 21 | // Act 22 | orchestrator.stop(null); 23 | 24 | // Assert 25 | a.should.equal(1); 26 | done(); 27 | }); 28 | 29 | it('should pass error to doneCallback', function(done) { 30 | var orchestrator, actualError, expectedError = 'This is a test error'; 31 | 32 | // Arrange 33 | orchestrator = new Orchestrator(); 34 | orchestrator.doneCallback = function (err) { 35 | actualError = err; 36 | }; 37 | 38 | // Act 39 | orchestrator.stop(expectedError); 40 | 41 | // Assert 42 | should.exist(actualError); 43 | actualError.should.equal(expectedError); 44 | done(); 45 | }); 46 | 47 | it('should set isRunning to false', function(done) { 48 | var orchestrator; 49 | 50 | // Arrange 51 | orchestrator = new Orchestrator(); 52 | orchestrator.isRunning = true; 53 | 54 | // Act 55 | orchestrator.stop(null, true); // true means success 56 | 57 | // Assert 58 | orchestrator.isRunning.should.equal(false); 59 | done(); 60 | }); 61 | 62 | it('should log success', function (done) { 63 | var orchestrator, actualLog; 64 | 65 | // Arrange 66 | orchestrator = new Orchestrator(); 67 | orchestrator.on('stop', function (e) { 68 | actualLog = e; 69 | }); 70 | 71 | // Act 72 | orchestrator.stop(null, true); // true means success 73 | 74 | // Assert 75 | should.exist(actualLog); 76 | should.not.exist(actualLog.task); 77 | actualLog.message.indexOf('succeed').should.be.above(-1); 78 | done(); 79 | }); 80 | 81 | it('should log failure', function (done) { 82 | var orchestrator, actualLog; 83 | 84 | // Arrange 85 | orchestrator = new Orchestrator(); 86 | orchestrator.on('err', function (e) { 87 | actualLog = e; 88 | }); 89 | 90 | // Act 91 | orchestrator.stop(null, false); // false means aborted 92 | 93 | // Assert 94 | should.exist(actualLog); 95 | should.not.exist(actualLog.task); 96 | actualLog.message.indexOf('abort').should.be.above(-1); 97 | done(); 98 | }); 99 | 100 | it('should log exception', function (done) { 101 | var orchestrator, actualErr = 'the error', actualLog; 102 | 103 | // Arrange 104 | orchestrator = new Orchestrator(); 105 | orchestrator.on('err', function (e) { 106 | actualLog = e; 107 | }); 108 | 109 | // Act 110 | orchestrator.stop(actualErr); // false means aborted 111 | 112 | // Assert 113 | should.exist(actualLog); 114 | should.not.exist(actualLog.task); 115 | actualLog.message.indexOf('fail').should.be.above(-1); 116 | actualLog.err.should.equal(actualErr); 117 | done(); 118 | }); 119 | 120 | it('should throw if no callback and no err handler', function (done) { 121 | var orchestrator, expectedErr = 'the error', actualErr; 122 | 123 | // Arrange 124 | orchestrator = new Orchestrator(); 125 | 126 | // Act 127 | try { 128 | orchestrator.stop(expectedErr); 129 | 130 | // Assert 131 | } catch (err) { 132 | actualErr = err; 133 | } 134 | 135 | actualErr.should.equal(expectedErr); 136 | done(); 137 | }); 138 | 139 | }); 140 | }); 141 | -------------------------------------------------------------------------------- /test/stopTask.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | "use strict"; 4 | 5 | var Orchestrator = require('../'); 6 | require('should'); 7 | require('mocha'); 8 | 9 | describe('orchestrator', function() { 10 | describe('_stopTask()', function() { 11 | 12 | it('should set done = true', function(done) { 13 | var orchestrator, task, meta; 14 | 15 | // Arrange 16 | task = { 17 | name: 'test', 18 | fn: function() {} 19 | }; 20 | meta = { 21 | duration: 2, 22 | hrDuration: [2,2] 23 | }; 24 | 25 | // the thing under test 26 | orchestrator = new Orchestrator(); 27 | 28 | // Act 29 | orchestrator._stopTask(task, meta); 30 | 31 | // Assert 32 | task.done.should.equal(true); 33 | done(); 34 | }); 35 | 36 | it('should set running = false', function(done) { 37 | var orchestrator, task, meta; 38 | 39 | // Arrange 40 | task = { 41 | name: 'test', 42 | fn: function() {}, 43 | running: true 44 | }; 45 | meta = { 46 | duration: 2, 47 | hrDuration: [2,2] 48 | }; 49 | 50 | // the thing under test 51 | orchestrator = new Orchestrator(); 52 | 53 | // Act 54 | orchestrator._stopTask(task, meta); 55 | 56 | // Assert 57 | task.running.should.equal(false); 58 | done(); 59 | }); 60 | 61 | it('should set task.duration', function(done) { 62 | var orchestrator, duration, task, meta; 63 | 64 | // Arrange 65 | duration = 2; 66 | task = { 67 | name: 'test', 68 | fn: function() {}, 69 | start: new Date() 70 | }; 71 | meta = { 72 | duration: duration, 73 | hrDuration: [2,2] 74 | }; 75 | 76 | // the thing under test 77 | orchestrator = new Orchestrator(); 78 | 79 | // Act 80 | orchestrator._stopTask(task, meta); 81 | 82 | // Assert 83 | (typeof task.duration).should.equal('number'); 84 | task.duration.should.equal(duration); 85 | done(); 86 | }); 87 | 88 | it('should die if not passed a task', function(done) { 89 | // Arrange 90 | var orchestrator = new Orchestrator(); 91 | var succeed = false; 92 | 93 | // Act 94 | try { 95 | orchestrator._stopTask(); 96 | succeed = true; 97 | } catch (err) { 98 | succeed = false; 99 | } 100 | 101 | // Assert 102 | succeed.should.equal(false); 103 | done(); 104 | }); 105 | 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /test/streamConsume.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | "use strict"; 4 | 5 | var Orchestrator = require('../'); 6 | var Stream = require('stream'); 7 | var Readable = Stream.Readable; 8 | var Writable = Stream.Writable; 9 | var Duplex = Stream.Duplex; 10 | var Q = require('q'); 11 | var fs = require('fs'); 12 | var should = require('should'); 13 | require('mocha'); 14 | 15 | describe('orchestrator', function() { 16 | describe('when given a stream', function() { 17 | 18 | it('should consume a Readable stream to relieve backpressure, in objectMode', function(done) { 19 | var orchestrator, a; 20 | 21 | // Arrange 22 | orchestrator = new Orchestrator(); 23 | a = 0; 24 | orchestrator.add('test', function() { 25 | // Create a Readable stream with a small buffer... 26 | var rs = Readable({objectMode: true, highWaterMark: 2}); 27 | rs._read = function() { 28 | // ...and generate more chunks than fit in that buffer 29 | if (a++ < 100) { 30 | rs.push(a); 31 | } else { 32 | rs.push(null); 33 | } 34 | }; 35 | return rs; 36 | }); 37 | 38 | // Act 39 | orchestrator.start('test', function(err) { 40 | // Assert 41 | // Simple completion of the task is the main criterion here, but check a few things: 42 | a.should.be.above(99); 43 | should.not.exist(err); 44 | orchestrator.isRunning.should.equal(false); 45 | done(); 46 | }); 47 | }); 48 | 49 | it('should consume a Readable stream to relieve backpressure', function(done) { 50 | var orchestrator, a; 51 | 52 | // Arrange 53 | orchestrator = new Orchestrator(); 54 | a = 0; 55 | orchestrator.add('test', function() { 56 | // Create a Readable stream with a small buffer... 57 | var rs = Readable({highWaterMark: 2}); 58 | rs._read = function() { 59 | // ...and generate more chunks than fit in that buffer 60 | if (a++ < 100) { 61 | rs.push("."); 62 | } else { 63 | rs.push(null); 64 | } 65 | }; 66 | return rs; 67 | }); 68 | 69 | // Act 70 | orchestrator.start('test', function(err) { 71 | // Assert 72 | // Simple completion of the task is the main criterion here, but check a few things: 73 | a.should.be.above(99); 74 | should.not.exist(err); 75 | orchestrator.isRunning.should.equal(false); 76 | done(); 77 | }); 78 | }); 79 | 80 | it('should detect completion of a Writable stream', function(done) { 81 | var orchestrator, a, lengthRead; 82 | 83 | // Arrange 84 | orchestrator = new Orchestrator(); 85 | a = 0; 86 | lengthRead = 0; 87 | orchestrator.add('test', function() { 88 | // Create a Readable stream... 89 | var rs = Readable({highWaterMark: 2}); 90 | rs._read = function() { 91 | if (a++ < 100) { 92 | rs.push("."); 93 | } else { 94 | rs.push(null); 95 | } 96 | }; 97 | 98 | // ...and consume it 99 | var ws = Writable(); 100 | ws._write = function(chunk, enc, next) { 101 | lengthRead += chunk.length; 102 | next(); 103 | }; 104 | rs.pipe(ws); 105 | 106 | // Return the Writable 107 | return ws; 108 | }); 109 | 110 | // Act 111 | orchestrator.start('test', function(err) { 112 | // Assert 113 | a.should.be.above(99); 114 | lengthRead.should.equal(100); 115 | should.not.exist(err); 116 | orchestrator.isRunning.should.equal(false); 117 | done(); 118 | }); 119 | }); 120 | 121 | it('should detect completion of a Writable stream, in objectMode', function(done) { 122 | var orchestrator, a, lengthRead; 123 | 124 | // Arrange 125 | orchestrator = new Orchestrator(); 126 | a = 0; 127 | lengthRead = 0; 128 | orchestrator.add('test', function() { 129 | // Create a Readable stream... 130 | var rs = Readable({objectMode: true, highWaterMark: 2}); 131 | rs._read = function() { 132 | if (a++ < 100) { 133 | rs.push(a); 134 | } else { 135 | rs.push(null); 136 | } 137 | }; 138 | 139 | // ...and consume it 140 | var ws = Writable({objectMode: true}); 141 | ws._write = function(chunk, enc, next) { 142 | lengthRead++; 143 | next(); 144 | }; 145 | rs.pipe(ws); 146 | 147 | // Return the Writable 148 | return ws; 149 | }); 150 | 151 | // Act 152 | orchestrator.start('test', function(err) { 153 | // Assert 154 | a.should.be.above(99); 155 | lengthRead.should.equal(100); 156 | should.not.exist(err); 157 | orchestrator.isRunning.should.equal(false); 158 | done(); 159 | }); 160 | }); 161 | 162 | it('should handle an intermediate Readable stream being returned', function(done) { 163 | var orchestrator, a, lengthRead; 164 | 165 | // Arrange 166 | orchestrator = new Orchestrator(); 167 | a = 0; 168 | lengthRead = 0; 169 | orchestrator.add('test', function() { 170 | // Create a Readable stream... 171 | var rs = Readable({highWaterMark: 2}); 172 | rs._read = function() { 173 | if (a++ < 100) { 174 | rs.push("."); 175 | } else { 176 | rs.push(null); 177 | } 178 | }; 179 | 180 | // ...and consume it 181 | var ws = Writable(); 182 | ws._write = function(chunk, enc, next) { 183 | lengthRead += chunk.length; 184 | next(); 185 | }; 186 | rs.pipe(ws); 187 | 188 | // Return the Readable 189 | return rs; 190 | }); 191 | 192 | // Act 193 | orchestrator.start('test', function(err) { 194 | // Assert 195 | a.should.be.above(99); 196 | // Ensure all data was received by the Writable 197 | lengthRead.should.equal(100); 198 | should.not.exist(err); 199 | orchestrator.isRunning.should.equal(false); 200 | done(); 201 | }); 202 | }); 203 | 204 | it('should handle an intermediate Readable stream being returned, in objectMode', function(done) { 205 | var orchestrator, a, lengthRead; 206 | 207 | // Arrange 208 | orchestrator = new Orchestrator(); 209 | a = 0; 210 | lengthRead = 0; 211 | orchestrator.add('test', function() { 212 | // Create a Readable stream... 213 | var rs = Readable({objectMode: true, highWaterMark: 2}); 214 | rs._read = function() { 215 | if (a++ < 100) { 216 | rs.push(a); 217 | } else { 218 | rs.push(null); 219 | } 220 | }; 221 | 222 | // ...and consume it 223 | var ws = Writable({objectMode: true}); 224 | ws._write = function(chunk, enc, next) { 225 | lengthRead++; 226 | next(); 227 | }; 228 | rs.pipe(ws); 229 | 230 | // Return the Readable 231 | return rs; 232 | }); 233 | 234 | // Act 235 | orchestrator.start('test', function(err) { 236 | // Assert 237 | a.should.be.above(99); 238 | // Ensure all data was received by the Writable 239 | lengthRead.should.equal(100); 240 | should.not.exist(err); 241 | orchestrator.isRunning.should.equal(false); 242 | done(); 243 | }); 244 | }); 245 | 246 | it('should require the Readable side of a Duplex stream to be closed to trigger completion', function(done) { 247 | var orchestrator; 248 | var readableClosed = false; 249 | var readCalled = false; 250 | var writableClosed = false; 251 | 252 | // Arrange 253 | orchestrator = new Orchestrator(); 254 | 255 | orchestrator.add('test', function() { 256 | var ds = Duplex(); 257 | ds._write = function(chunk, enc, next) { 258 | next(); 259 | }; 260 | 261 | function closeReadable() { 262 | // Delay closing the Readable side 263 | setTimeout(function() { 264 | readableClosed = true; 265 | ds.push(null); 266 | }, 1); 267 | } 268 | 269 | ds.on("finish", function() { 270 | writableClosed = true; 271 | if (readCalled) 272 | closeReadable(); 273 | }); 274 | 275 | 276 | ds._read = function() { 277 | readCalled = true; 278 | // Only close the Readable if the Writable has already been closed 279 | if (writableClosed) 280 | closeReadable(); 281 | } 282 | 283 | // Close the Writable side - after returning the stream, so that orchestrator sees the close 284 | setTimeout(function() { 285 | ds.end(); 286 | }, 1); 287 | 288 | // Return the Duplex 289 | return ds; 290 | }); 291 | 292 | // Act 293 | orchestrator.start('test', function(err) { 294 | // Assert 295 | readableClosed.should.be.true; 296 | should.not.exist(err); 297 | orchestrator.isRunning.should.equal(false); 298 | done(); 299 | }); 300 | }); 301 | 302 | it('should handle a classic stream that is not piped anywhere', function(done) { 303 | var orchestrator; 304 | var readableClosed = false; 305 | var readCalled = false; 306 | var writableClosed = false; 307 | var i; 308 | 309 | // Arrange 310 | orchestrator = new Orchestrator(); 311 | 312 | orchestrator.add('test', function() { 313 | var rs = new Stream(); 314 | 315 | process.nextTick(function() { 316 | for (i = 1; i <= 100; i++) { 317 | rs.emit("data", i); 318 | } 319 | rs.emit("end"); 320 | }); 321 | 322 | // Return the Readable 323 | return rs; 324 | }); 325 | 326 | // Act 327 | orchestrator.start('test', function(err) { 328 | // Assert 329 | should.not.exist(err); 330 | orchestrator.isRunning.should.equal(false); 331 | done(); 332 | }); 333 | }); 334 | 335 | it('should handle a classic stream that is piped somewhere', function(done) { 336 | var orchestrator; 337 | var readableClosed = false; 338 | var readCalled = false; 339 | var writableClosed = false; 340 | var lengthRead = 0; 341 | var i; 342 | 343 | // Arrange 344 | orchestrator = new Orchestrator(); 345 | 346 | orchestrator.add('test', function() { 347 | var rs = new Stream(); 348 | 349 | process.nextTick(function() { 350 | for (i = 0; i < 100; i++) { 351 | rs.emit("data", i); 352 | } 353 | rs.emit("end"); 354 | }); 355 | 356 | var ws = new Writable({objectMode: true, highWaterMark: 5}); 357 | ws._write = function(chunk, enc, next) { 358 | lengthRead++; 359 | next(); 360 | }; 361 | 362 | rs.pipe(ws); 363 | 364 | // Return the Readable 365 | return rs; 366 | }); 367 | 368 | // Act 369 | orchestrator.start('test', function(err) { 370 | // Assert 371 | should.not.exist(err); 372 | lengthRead.should.equal(100); 373 | orchestrator.isRunning.should.equal(false); 374 | done(); 375 | }); 376 | }); 377 | 378 | }); 379 | }); 380 | -------------------------------------------------------------------------------- /test/task.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | 4 | "use strict"; 5 | 6 | var Orchestrator = require('../'); 7 | var should = require('should'); 8 | require('mocha'); 9 | 10 | describe('orchestrator', function() { 11 | describe('task()', function() { 12 | 13 | it('should return task if there is a task', function(done) { 14 | var orchestrator, name, task1, actual; 15 | 16 | // Arrange 17 | name = 'task1'; 18 | task1 = { 19 | name: name, 20 | fn: function() {} 21 | }; 22 | 23 | // the thing under test 24 | orchestrator = new Orchestrator(); 25 | orchestrator.tasks[name] = task1; 26 | 27 | // Act 28 | actual = orchestrator.task(name); 29 | 30 | // Assert 31 | actual.should.equal(task1); 32 | done(); 33 | }); 34 | 35 | it('should return false if there is no such task', function(done) { 36 | var orchestrator, name, task1, actual; 37 | 38 | // Arrange 39 | name = 'task1'; 40 | task1 = { 41 | name: name, 42 | fn: function() {} 43 | }; 44 | 45 | // the thing under test 46 | orchestrator = new Orchestrator(); 47 | orchestrator.tasks[name] = task1; 48 | 49 | // Act 50 | actual = orchestrator.task('not'+name); 51 | 52 | // Assert 53 | should.not.exist(actual); 54 | done(); 55 | }); 56 | 57 | it('should create a task if passed a second arg', function(done) { 58 | var orchestrator, name, fn, actual; 59 | 60 | // Arrange 61 | name = 'task1'; 62 | fn = function () {}; 63 | 64 | // the thing under test 65 | orchestrator = new Orchestrator(); 66 | 67 | // Act 68 | actual = orchestrator.task(name, fn); 69 | 70 | // Assert 71 | should.not.exist(actual); 72 | should.exist(orchestrator.tasks[name]); 73 | orchestrator.tasks[name].name.should.equal(name); 74 | done(); 75 | }); 76 | 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/taskDependencies.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | 4 | "use strict"; 5 | 6 | var Orchestrator = require('../'); 7 | var Q = require('q'); 8 | var should = require('should'); 9 | require('mocha'); 10 | 11 | describe('orchestrator', function() { 12 | describe('run() task dependencies', function() { 13 | // Technically these are duplicated from require('sequencify'), 14 | // but those are unit tests and these are integration tests 15 | it('should run tasks in specified order if no dependencies', function(done) { 16 | var orchestrator, a, fn, fn2; 17 | 18 | // Arrange 19 | a = 0; 20 | fn = function() { 21 | a.should.equal(1); 22 | ++a; 23 | }; 24 | fn2 = function() { 25 | a.should.equal(2); 26 | ++a; 27 | }; 28 | 29 | // Act 30 | orchestrator = new Orchestrator(); 31 | orchestrator.on('start', function (e) { 32 | should.exist(e); 33 | a.should.equal(0); 34 | ++a; 35 | e.message.should.startWith('seq: '); // Order is not deterministic, but event should still happen 36 | }); 37 | orchestrator.add('test1', fn); 38 | orchestrator.add('test2', fn2); 39 | orchestrator.start('test1', 'test2', function (err) { 40 | // Assert 41 | a.should.equal(3); 42 | should.not.exist(err); 43 | done(); 44 | }); 45 | }); 46 | 47 | it('should run dependency then specified task', function(done) { 48 | var orchestrator, a, fn, fn2; 49 | 50 | // Arrange 51 | a = 0; 52 | fn = function() { 53 | a.should.equal(1); 54 | ++a; 55 | }; 56 | fn2 = function() { 57 | a.should.equal(2); 58 | ++a; 59 | }; 60 | 61 | // Act 62 | orchestrator = new Orchestrator(); 63 | orchestrator.on('start', function (e) { 64 | should.exist(e); 65 | a.should.equal(0); 66 | ++a; 67 | e.message.should.equal('seq: dep,test'); 68 | }); 69 | orchestrator.add('dep', fn); 70 | orchestrator.add('test', ['dep'], fn2); 71 | orchestrator.start('test'); 72 | 73 | // Assert 74 | a.should.equal(3); 75 | done(); 76 | }); 77 | 78 | it('should run asynchronous dependency then specified task', function(done) { 79 | var orchestrator, a, fn, fn2; 80 | 81 | // Arrange 82 | a = 0; 83 | fn = function() { 84 | var deferred = Q.defer(); 85 | setTimeout(function () { 86 | a.should.equal(1); 87 | ++a; 88 | deferred.resolve(); 89 | },1); 90 | return deferred.promise; 91 | }; 92 | fn2 = function() { 93 | var deferred = Q.defer(); 94 | setTimeout(function () { 95 | a.should.equal(2); 96 | ++a; 97 | deferred.resolve(); 98 | },1); 99 | return deferred.promise; 100 | }; 101 | 102 | // Act 103 | orchestrator = new Orchestrator(); 104 | orchestrator.on('start', function (e) { 105 | should.exist(e); 106 | a.should.equal(0); 107 | ++a; 108 | e.message.should.equal('seq: dep,test'); 109 | }); 110 | orchestrator.add('dep', fn); 111 | orchestrator.add('test', ['dep'], fn2); 112 | orchestrator.start('test', function () { 113 | // Assert 114 | orchestrator.isRunning.should.equal(false); 115 | a.should.equal(3); 116 | done(); 117 | }); 118 | orchestrator.isRunning.should.equal(true); 119 | }); 120 | 121 | it('should run all tasks of complex dependency chain', function(done) { 122 | var orchestrator, a, fn1, fn2, fn3, fn4, timeout = 2; 123 | 124 | // Arrange 125 | a = 0; 126 | // fn1 is a long-running task, fn2 and 3 run quickly, fn4 is synchronous 127 | // If shorter tasks mark it done before the longer task finishes that's wrong 128 | fn1 = function() { 129 | var deferred = Q.defer(); 130 | setTimeout(function () { 131 | ++a; 132 | deferred.resolve(); 133 | }, timeout*5); 134 | return deferred.promise; 135 | }; 136 | fn2 = function() { 137 | var deferred = Q.defer(); 138 | setTimeout(function () { 139 | ++a; 140 | deferred.resolve(); 141 | }, timeout); 142 | return deferred.promise; 143 | }; 144 | fn3 = function() { 145 | var deferred = Q.defer(); 146 | setTimeout(function () { 147 | ++a; 148 | deferred.resolve(); 149 | }, timeout); 150 | return deferred.promise; 151 | }; 152 | fn4 = function() { 153 | ++a; 154 | }; 155 | 156 | // Act 157 | orchestrator = new Orchestrator(); 158 | orchestrator.on('start', function (e) { 159 | should.exist(e); 160 | a.should.equal(0); 161 | ++a; 162 | e.message.should.equal('seq: fn1,fn2,fn3,fn4'); 163 | }); 164 | orchestrator.add('fn1', fn1); 165 | orchestrator.add('fn2', fn2); 166 | orchestrator.add('fn3', ['fn1', 'fn2'], fn3); 167 | orchestrator.add('fn4', ['fn3'], fn4); 168 | orchestrator.start('fn4', function () { 169 | // Assert 170 | orchestrator.isRunning.should.equal(false); 171 | a.should.equal(5); 172 | done(); 173 | }); 174 | orchestrator.isRunning.should.equal(true); 175 | }); 176 | 177 | }); 178 | }); 179 | -------------------------------------------------------------------------------- /test/taskTimings.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | 4 | "use strict"; 5 | 6 | var Orchestrator = require('../'); 7 | var should = require('should'); 8 | require('mocha'); 9 | 10 | describe('orchestrator', function() { 11 | describe('_runTask() task timings', function() { 12 | 13 | it('should set duration to 1 when task takes 1 second', function(done) { 14 | var orchestrator, a, fn, timeout; 15 | 16 | // Arrange 17 | timeout = 0.01; // seconds 18 | a = 0; 19 | fn = function(cb) { 20 | setTimeout(function () { 21 | cb(); 22 | }, timeout*1000); // milliseconds 23 | }; 24 | 25 | // The thing under test 26 | orchestrator = new Orchestrator(); 27 | orchestrator.add('test', fn); 28 | 29 | orchestrator.on('task_stop', function (args) { 30 | // Assert 31 | args.duration.should.be.approximately(timeout, 0.02); 32 | args.duration.should.be.above(0); 33 | ++a; 34 | }); 35 | 36 | // Act 37 | orchestrator.start('test', function (err) { 38 | // Assert 39 | a.should.equal(1); 40 | should.not.exist(err); 41 | done(); 42 | }); 43 | }); 44 | 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/taskWaiting.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | /*global describe:false, it:false */ 3 | 4 | "use strict"; 5 | 6 | var Orchestrator = require('../'); 7 | var Q = require('q'); 8 | var map = require('map-stream'); 9 | var es = require('event-stream'); 10 | var should = require('should'); 11 | require('mocha'); 12 | 13 | describe('orchestrator', function() { 14 | describe('_runTask() waits for done correctly', function() { 15 | 16 | it('sync task sets done after calling function', function(done) { 17 | var orchestrator, task, a; 18 | 19 | // Arrange 20 | a = 0; 21 | task = { 22 | name: 'test', 23 | fn: function() { 24 | should.not.exist(task.done); 25 | } 26 | }; 27 | 28 | // Act 29 | orchestrator = new Orchestrator(); 30 | orchestrator._runStep = function () { 31 | a.should.equal(0); 32 | a++; 33 | }; 34 | orchestrator._runTask(task); 35 | 36 | // Assert 37 | should.exist(task.done); 38 | task.done.should.equal(true); 39 | a.should.equal(1); 40 | done(); 41 | }); 42 | 43 | it('sync task logs finish after calling function', function(done) { 44 | var orchestrator, task, a; 45 | 46 | // Arrange 47 | a = 0; 48 | task = { 49 | name: 'test', 50 | fn: function() { 51 | a.should.equal(0); 52 | } 53 | }; 54 | 55 | // the thing under test 56 | orchestrator = new Orchestrator(); 57 | orchestrator.on('task_stop', function (e) { 58 | should.exist(e.task); 59 | e.task.should.equal('test'); 60 | if (e.message.indexOf('finish')) { 61 | ++a; 62 | } 63 | }); 64 | orchestrator._runStep = function () {}; // fake 65 | 66 | // Act 67 | orchestrator._runTask(task); 68 | 69 | // Assert 70 | a.should.equal(1); 71 | done(); 72 | }); 73 | 74 | it('async promise task sets done after task resolves', function(done) { 75 | var orchestrator, task, timeout = 5, a; 76 | 77 | // Arrange 78 | a = 0; 79 | task = { 80 | name: 'test', 81 | fn: function() { 82 | var deferred = Q.defer(); 83 | setTimeout(function () { 84 | should.not.exist(task.done); 85 | deferred.resolve(); 86 | }, timeout); 87 | return deferred.promise; 88 | } 89 | }; 90 | 91 | // Act 92 | orchestrator = new Orchestrator(); 93 | orchestrator._runStep = function () { 94 | a.should.equal(0); 95 | a++; 96 | }; 97 | orchestrator._runTask(task); 98 | 99 | // Assert 100 | should.not.exist(task.done); 101 | setTimeout(function () { 102 | should.exist(task.done); 103 | task.done.should.equal(true); 104 | a.should.equal(1); 105 | done(); 106 | }, timeout*2); 107 | }); 108 | 109 | it('async promise task logs finish after task resolves', function(done) { 110 | var orchestrator, task, timeout = 5, a; 111 | 112 | // Arrange 113 | a = 0; 114 | task = { 115 | name: 'test', 116 | fn: function() { 117 | var deferred = Q.defer(); 118 | setTimeout(function () { 119 | a.should.equal(0); 120 | deferred.resolve(); 121 | }, timeout); 122 | return deferred.promise; 123 | } 124 | }; 125 | 126 | // the thing under test 127 | orchestrator = new Orchestrator(); 128 | orchestrator.on('task_stop', function (e) { 129 | should.exist(e.task); 130 | e.task.should.equal('test'); 131 | if (e.message.indexOf('promise') > -1) { 132 | ++a; 133 | } 134 | }); 135 | orchestrator._runStep = function () {}; 136 | 137 | // Act 138 | orchestrator._runTask(task); 139 | 140 | // Assert 141 | setTimeout(function () { 142 | a.should.equal(1); 143 | done(); 144 | }, timeout*2); 145 | }); 146 | 147 | it('async callback task sets done after task resolves', function(done) { 148 | var orchestrator, task, timeout = 5, a; 149 | 150 | // Arrange 151 | a = 0; 152 | task = { 153 | name: 'test', 154 | fn: function(cb) { 155 | setTimeout(function () { 156 | should.not.exist(task.done); 157 | cb(null); 158 | }, timeout); 159 | } 160 | }; 161 | 162 | // Act 163 | orchestrator = new Orchestrator(); 164 | orchestrator._runStep = function () { 165 | a.should.equal(0); 166 | a++; 167 | }; 168 | orchestrator._runTask(task); 169 | 170 | // Assert 171 | should.not.exist(task.done); 172 | setTimeout(function () { 173 | should.exist(task.done); 174 | task.done.should.equal(true); 175 | a.should.equal(1); 176 | done(); 177 | }, timeout*2); 178 | }); 179 | 180 | it('async callback task logs finish after task resolves', function(done) { 181 | var orchestrator, task, timeout = 5, a; 182 | 183 | // Arrange 184 | a = 0; 185 | task = { 186 | name: 'test', 187 | fn: function(cb) { 188 | setTimeout(function () { 189 | should.not.exist(task.done); 190 | cb(null); 191 | }, timeout); 192 | } 193 | }; 194 | 195 | // the thing under test 196 | orchestrator = new Orchestrator(); 197 | orchestrator.on('task_stop', function (e) { 198 | should.exist(e.task); 199 | e.task.should.equal('test'); 200 | if (e.message.indexOf('callback') > -1) { 201 | ++a; 202 | } 203 | }); 204 | orchestrator._runStep = function () {}; 205 | 206 | // Act 207 | orchestrator._runTask(task); 208 | 209 | // Assert 210 | setTimeout(function () { 211 | a.should.equal(1); 212 | done(); 213 | }, timeout*2); 214 | }); 215 | 216 | it('async stream task sets done after task resolves', function(done) { 217 | var orchestrator, task, timeout = 5, a; 218 | 219 | // Arrange 220 | a = 0; 221 | task = { 222 | name: 'test', 223 | fn: function() { 224 | return es.readable(function(/*count, callback*/) { 225 | this.emit('data', {a:'rgs'}); 226 | this.emit('end'); 227 | }).pipe(map(function (f, cb) { 228 | setTimeout(function () { 229 | cb(null, f); 230 | }, timeout); 231 | })); 232 | } 233 | }; 234 | should.not.exist(task.done); 235 | 236 | // Act 237 | orchestrator = new Orchestrator(); 238 | orchestrator._runStep = function () { 239 | a.should.equal(0); 240 | a++; 241 | }; 242 | orchestrator._runTask(task); 243 | 244 | // Assert 245 | setTimeout(function () { 246 | should.exist(task.done); 247 | task.done.should.equal(true); 248 | a.should.equal(1); 249 | done(); 250 | }, timeout*2); 251 | }); 252 | 253 | it('async stream task logs finish after task resolves', function(done) { 254 | var orchestrator, task, timeout = 5, a; 255 | 256 | // Arrange 257 | a = 0; 258 | task = { 259 | name: 'test', 260 | fn: function() { 261 | return es.readable(function(/*count, callback*/) { 262 | this.emit('data', {a:'rgs'}); 263 | this.emit('end'); 264 | }).pipe(es.map(function (f, cb) { 265 | setTimeout(function () { 266 | cb(null, f); 267 | }, timeout); 268 | })); 269 | } 270 | }; 271 | 272 | // the thing under test 273 | orchestrator = new Orchestrator(); 274 | orchestrator.on('task_stop', function (e) { 275 | should.exist(e.task); 276 | e.task.should.equal('test'); 277 | if (e.message.indexOf('stream') > -1) { 278 | ++a; 279 | } 280 | }); 281 | orchestrator._runStep = function () {}; 282 | 283 | // Act 284 | orchestrator._runTask(task); 285 | 286 | // Assert 287 | setTimeout(function () { 288 | a.should.equal(1); 289 | done(); 290 | }, timeout*2); 291 | }); 292 | 293 | }); 294 | }); 295 | --------------------------------------------------------------------------------