UNPKG

guvnor

Version:

A node process manager that isn't spanners all the way down

1,150 lines (920 loc) 33.4 kB
var expect = require('chai').expect, sinon = require('sinon'), inherits = require('util').inherits, EventEmitter = require('events').EventEmitter, ProcessInfo = require('../../../../lib/daemon/domain/ProcessInfo'), ProcessService = require('../../../../lib/daemon/service/ProcessService') describe('ProcessService', function () { var processService, clock beforeEach(function () { clock = sinon.useFakeTimers() processService = new ProcessService() processService._logger = { info: sinon.stub(), warn: sinon.stub(), error: sinon.stub(), debug: sinon.stub(), log: sinon.stub() } processService._portService = { freePort: sinon.stub() } processService._processInfoStore = { create: sinon.stub(), find: sinon.stub(), remove: sinon.stub(), all: sinon.stub().returns([]) } processService._child_process = { fork: sinon.stub() } processService._config = { guvnor: { resettimeout: 5000 } } processService._managedProcessFactory = { create: sinon.stub() } processService._appService = { findByName: sinon.stub() } }) afterEach(function () { clock.restore() }) it('should automatically restart a process on exit with non-zero code', function () { function MockProcess() { this.pid = Math.floor(Math.random() * 1000) } inherits(MockProcess, EventEmitter) MockProcess.prototype.kill = sinon.stub() var mockProcess0 = new MockProcess() var mockProcess1 = new MockProcess() processService._child_process.fork .onFirstCall().returns(mockProcess0) .onSecondCall().returns(mockProcess1) processService._portService.freePort.callsArgWith(0, undefined, 5) var processInfo = { id: 'foo', restartOnError: true, restartRetries: 5, restarts: 0, totalRestarts: 0, validate: sinon.stub().callsArg(0), getProcessArgs: sinon.stub(), getProcessOptions: sinon.stub(), logger: { error: sinon.stub() } } // processInfo.validate processService._processInfoStore.create.callsArgWith(1, undefined, processInfo) processService._processInfoStore.find.withArgs('id', processInfo.id).returns(processInfo) // Start a process processService.startProcess(__filename, {}, sinon.stub()) expect(processService._child_process.fork.calledOnce).to.be.true mockProcess0.emit('message', {event: 'process:ready'}) // Exit the mock process mockProcess0.emit('exit', 7) // A new challenger appears expect(processService._child_process.fork.calledTwice).to.be.true mockProcess1.emit('message', {event: 'process:ready'}) expect(processService._processInfoStore.create.calledOnce).to.be.true }) it('should find a process by pid', function () { processService._processInfoStore.find.withArgs('process.pid', 1).returns({ prop: 'baz', process: { pid: 1 } }) expect(processService.findByPid(1).prop).to.equal('baz') }) it('should notify of failure if reserving a debug port fails', function (done) { var processInfo = { validate: sinon.stub() } processInfo.validate.callsArgAsync(0) processService._processInfoStore.create.callsArgWithAsync(1, undefined, processInfo) processService.on('process:failed', function (processInfo) { expect(processInfo.status).to.equal('failed') done() }) processService._portService.freePort.callsArgWith(0, new Error('AAIIIIEEEE!')) processService.startProcess('foo', {}, function () { }) }) it('should start a cluster manager', function (done) { var processInfo = { cluster: true, validate: sinon.stub(), getProcessArgs: sinon.stub(), getProcessOptions: sinon.stub() } processInfo.validate.callsArgAsync(0) var childProcess = { on: sinon.stub() } processService._processInfoStore.create.callsArgWith(1, undefined, processInfo) processService._child_process.fork.returns(childProcess) var emittedStartedClusterEvent = false processService.on('cluster:starting', function () { emittedStartedClusterEvent = true }) processService.on('cluster:forked', function (processInfo) { expect(emittedStartedClusterEvent).to.be.true expect(processInfo.cluster).to.be.true done() }) processService._portService.freePort.callsArgWith(0, undefined, 5) processService.startProcess('foo', { instances: 2 }, function () { }) }) it('should forward events', function (done) { processService.on('foo:bar', function (processInfo, one, two, three) { expect(one).to.equal('one') expect(two).to.equal('two') expect(three).to.equal('three') done() }) var processInfo = { process: new EventEmitter() } processService._forwardEvents(processInfo) // should cause the 'foo:bar' event to be emitted by the processService processInfo.process.emit('foo:bar', 'one', 'two', 'three') }) it('should not restart a failing process when restartOnError is false', function () { var processInfo = { id: 'foo', restartOnError: false, process: { pid: 5 } } processService._restartProcess(processInfo, 'process') // and not restarted it expect(processService._child_process.fork.notCalled).to.be.true }) it('should not restart a failing process when retries exceeded', function (done) { var processInfo = { id: 'foo', restartOnError: true, process: { pid: 5 }, stillCrashing: sinon.stub(), restarts: 5, restartRetries: 5 } processService._processes = { foo: processInfo } processService.on('process:aborted', function (info) { expect(info).to.equal(processInfo) // should have set status expect(processInfo.status).to.equal('aborted') // should have reset restart count expect(processInfo.restarts).to.equal(0) // should have deleted the pid expect(processInfo.pid).to.not.exist done() }) processService._restartProcess(processInfo, 'process') // should not have restarted it expect(processService._child_process.fork.called).to.be.false }) it('should restart a failing process when restarts are fewer than retries', function () { var processInfo = { id: 'foo', process: { pid: 1 }, restartOnError: true, stillCrashing: sinon.stub(), restarts: 2, restartRetries: 5, totalRestarts: 0 } processService._startProcess = sinon.stub() processService._restartProcess(processInfo, 'process') // should have restarted it expect(processService._startProcess.calledOnce).to.be.true }) it('should forward events from child process', function (done) { var childProcess = new EventEmitter() childProcess.pid = 5 var processInfo = { id: 'foo', process: childProcess, validate: sinon.stub().callsArg(0), getProcessArgs: sinon.stub(), getProcessOptions: sinon.stub(), logger: processService._logger } processService._processes = { foo: processInfo } processService._portService.freePort.callsArgWith(0, undefined, 5) processService._child_process.fork.returns(childProcess) processService._processInfoStore.create.callsArgWith(1, undefined, processInfo) processService.startProcess(__filename, {}, sinon.stub()) var events = [ 'process:log:info', 'process:log:error', 'process:log:warn', 'process:log:debug' ] var invocations = 0 events.forEach(function (event) { processService.on(event, function () { invocations++ if (invocations == events.length) { done() } }) }) events.forEach(function (event) { childProcess.emit(event, { message: 'hello' }) }) }) it('should send config to child process when requested', function () { var childProcess = new EventEmitter() childProcess.pid = 5 childProcess.send = sinon.stub() var processInfo = { id: 'foo', process: childProcess, validate: sinon.stub().callsArg(0), getProcessArgs: sinon.stub(), getProcessOptions: sinon.stub(), logger: processService._logger } processService._processes = { foo: processInfo } processService._portService.freePort.callsArgWith(0, undefined, 5) processService._child_process.fork.returns(childProcess) processService._processInfoStore.create.callsArgWith(1, undefined, processInfo) processService.startProcess(__filename, {}, sinon.stub()) childProcess.emit('process:config:request') expect(childProcess.send.callCount).to.equal(1) expect(childProcess.send.getCall(0).args[0].event).to.equal('daemon:config:response') expect(childProcess.send.getCall(0).args[0].args[0]).to.equal(processService._config) }) it('should connect to process rpc socket after startup', function (done) { var remote = { connect: sinon.stub() } var childProcess = new EventEmitter() childProcess.pid = 5 childProcess.send = sinon.stub() var processInfo = { id: 'foo', process: childProcess, validate: sinon.stub().callsArg(0), getProcessArgs: sinon.stub(), getProcessOptions: sinon.stub(), logger: processService._logger, restarts: 1 } processService._processes = { foo: processInfo } processService._portService.freePort.callsArgWith(0, undefined, 5) processService._child_process.fork.returns(childProcess) processService._processInfoStore.create.callsArgWith(1, undefined, processInfo) processService.on('process:ready', function (info) { expect(info).to.equal(processInfo) expect(processInfo.restarts).to.equal(1) clock.tick(processService._config.guvnor.resettimeout + 1) expect(processInfo.restarts).to.equal(0) done() }) processService.startProcess(__filename, {}, sinon.stub()) processService._managedProcessFactory.create.callsArgWith(1, undefined, remote) remote.connect.callsArgWith(0, undefined, remote) childProcess.emit('process:started', 'socket') expect(processInfo.status).to.equal('running') expect(processInfo.socket).to.equal('socket') expect(processInfo.remote).to.equal(remote) }) it('should connect to cluster rpc socket after startup', function (done) { var remote = { connect: sinon.stub() } var processInfo = { id: 'foo', process: new EventEmitter() } processService.on('cluster:ready', function (info) { expect(info).to.equal(processInfo) expect(processInfo.status).to.equal('running') expect(processInfo.socket).to.equal('socket') expect(processInfo.remote).to.equal(remote) done() }) processService.startProcess(__filename, {}, sinon.stub()) processService._managedProcessFactory.create.callsArgWithAsync(1, undefined, remote) remote.connect.callsArgWithAsync(0, undefined, remote) processService._setupProcessCallbacks(processInfo, 'cluster') processInfo.process.emit('cluster:started', 'socket') }) it('should mark processInfo as failed if connect to process rpc socket after startup fails', function (done) { var remote = { connect: sinon.stub() } var childProcess = new EventEmitter() childProcess.pid = 5 var processInfo = { id: 'foo', process: childProcess, validate: sinon.stub().callsArg(0), getProcessArgs: sinon.stub(), getProcessOptions: sinon.stub(), logger: processService._logger } processService._processes = { foo: processInfo } processService._portService.freePort.callsArgWith(0, undefined, 5) processService._child_process.fork.returns(childProcess) processService._processInfoStore.create.callsArgWith(1, undefined, processInfo) processService.on('process:failed', function (info) { expect(info).to.equal(processInfo) expect(info.status).to.equal('failed') done() }) processService.startProcess(__filename, {}, sinon.stub()) processService._managedProcessFactory.create.callsArgWith(1, undefined, remote) remote.connect.callsArgWith(0, new Error('nope!')) childProcess.emit('process:started', 'socket') expect(processInfo.status).to.equal('failed') expect(processInfo.socket).to.equal('socket') expect(processInfo.remote).to.not.exist }) it('should mark processInfo as errored if connect to process rpc socket after startup fails', function (done) { var childProcess = new EventEmitter() childProcess.pid = 5 var processInfo = { id: 'foo', process: childProcess, validate: sinon.stub().callsArg(0), getProcessArgs: sinon.stub(), getProcessOptions: sinon.stub(), logger: processService._logger } processService._processes = { foo: processInfo } processService._portService.freePort.callsArgWith(0, undefined, 5) processService._child_process.fork.returns(childProcess) processService._processInfoStore.create.callsArgWith(1, undefined, processInfo) processService.on('process:errored', function (info) { expect(info).to.equal(processInfo) expect(info.status).to.equal('errored') done() }) processService.startProcess(__filename, {}, sinon.stub()) childProcess.emit('process:errored', 'socket') }) it('should mark cluster processInfo as failed if connect to process rpc socket after startup fails', function (done) { var remote = { connect: sinon.stub() } var processInfo = { id: 'foo', process: new EventEmitter() } processService.on('cluster:failed', function (info) { expect(info).to.equal(processInfo) expect(info.status).to.equal('failed') done() }) remote.connect.callsArgWithAsync(0, new Error('nope!')) processService._managedProcessFactory.create.callsArgWithAsync(1, undefined, remote) processService._setupProcessCallbacks(processInfo, 'cluster') processInfo.process.emit('cluster:started', 'socket') }) it('should forward stopping event and set status to stopping', function (done) { var processInfo = { id: 'foo', process: new EventEmitter() } processService.on('cluster:stopping', function (info) { expect(info).to.equal(processInfo) expect(info.status).to.equal('stopping') done() }) processService._setupProcessCallbacks(processInfo, 'cluster') processInfo.process.emit('cluster:stopping', 'socket') }) it('should forward failed event and set status to failed', function (done) { var processInfo = { id: 'foo', process: new EventEmitter() } processService.on('cluster:failed', function (info) { expect(info).to.equal(processInfo) expect(info.status).to.equal('failed') done() }) processService._setupProcessCallbacks(processInfo, 'cluster') processInfo.process.emit('cluster:failed', new Error('urk!')) }) it('should forward restarting event and set status to restarting', function (done) { var childProcess = new EventEmitter() childProcess.pid = 5 var processInfo = { id: 'foo', process: childProcess, validate: sinon.stub().callsArg(0), getProcessArgs: sinon.stub(), getProcessOptions: sinon.stub(), logger: processService._logger } processService._processes = { foo: processInfo } processService._portService.freePort.callsArgWith(0, undefined, 5) processService._child_process.fork.returns(childProcess) processService._processInfoStore.create.callsArgWith(1, undefined, processInfo) processService.startProcess(__filename, {}, sinon.stub()) processService.on('process:restarting', function (info) { expect(info).to.equal(processInfo) expect(info.status).to.equal('restarting') expect(info.restarts).to.equal(0) done() }) childProcess.emit('process:restarting') }) it('should forward uncaughtexception event', function (done) { var childProcess = new EventEmitter() childProcess.pid = 5 var processInfo = { id: 'foo', process: childProcess, validate: sinon.stub().callsArg(0), getProcessArgs: sinon.stub(), getProcessOptions: sinon.stub(), logger: processService._logger } processService._processes = { foo: processInfo } processService._portService.freePort.callsArgWith(0, undefined, 5) processService._child_process.fork.returns(childProcess) processService._processInfoStore.create.callsArgWith(1, undefined, processInfo) processService.startProcess(__filename, {}, sinon.stub()) processService.on('process:uncaughtexception', function (info) { expect(info).to.equal(processInfo) done() }) childProcess.emit('process:uncaughtexception', new Error('urk!')) }) it('should forward process events from child process', function (done) { var childProcess = new EventEmitter() childProcess.pid = 5 var processInfo = { id: 'foo', process: childProcess, validate: sinon.stub().callsArg(0), getProcessArgs: sinon.stub(), getProcessOptions: sinon.stub(), logger: processService._logger } processService._processes = { foo: processInfo } processService._portService.freePort.callsArgWith(0, undefined, 5) processService._child_process.fork.returns(childProcess) processService._processInfoStore.create.callsArgWith(1, undefined, processInfo) processService.startProcess(__filename, {}, sinon.stub()) var events = [ 'process:forked', 'process:starting', 'process:ready', 'process:heapdump:start', 'process:heapdump:error', 'process:heapdump:complete', 'process:gc:start', 'process:gc:error', 'process:gc:complete' ] var invocations = 0 events.forEach(function (event) { processService.on(event, function () { invocations++ if (invocations == events.length) { done() } }) }) events.forEach(function (event) { childProcess.emit(event) }) }) it('should forward cluster failed event and set status to failed', function (done) { var processInfo = { id: 'foo', process: new EventEmitter() } processService.on('cluster:failed', function (info) { expect(info).to.equal(processInfo) expect(info.status).to.equal('failed') done() }) processService._setupProcessCallbacks(processInfo, 'cluster') processInfo.process.emit('cluster:failed', new Error('urk!')) }) it('should forward cluster online event', function (done) { var childProcess = new EventEmitter() childProcess.pid = 5 var processInfo = { id: 'foo', process: childProcess, validate: sinon.stub().callsArg(0), getProcessArgs: sinon.stub(), getProcessOptions: sinon.stub(), logger: processService._logger, cluster: true } processService._processes = { foo: processInfo } processService._portService.freePort.callsArgWith(0, undefined, 5) processService._child_process.fork.returns(childProcess) processService._processInfoStore.create.callsArgWith(1, undefined, processInfo) processService.startProcess(__filename, {}, sinon.stub()) processService.on('cluster:online', function (info) { expect(info).to.equal(processInfo) done() }) childProcess.emit('cluster:online') }) /* it('should forward cluster ready event', function(done) { var socket = 'socket' var remote = { connect: sinon.stub() } var processInfo = { id: 'foo', process: new EventEmitter() } processService._processes = { foo: processInfo } processService._portService.freePort.callsArgWithAsync(0, undefined, 5) processService._child_process.fork.returns(childProcess) processService._processInfoStore.create.callsArgWithAsync(1, undefined, processInfo) processService._managedProcessFactory.create.withArgs(['socket']).callsArgWithAsync(1, undefined, remote) remote.connect.callsArgWithAsync(0, undefined, remote) processService.startProcess(__filename, {}, sinon.stub()) var receivedStartedEvent = false processService.on('process:started', function(info) { expect(info).to.equal(processInfo) expect(info.status).to.equal('started') receivedStartedEvent = true }) processService.on('cluster:ready', function(info) { expect(info).to.equal(processInfo) expect(info.remote).to.equal(remote) expect(info.status).to.equal('running') expect(receivedStartedEvent).to.be.true done() }) childProcess.emit('cluster:started', socket) }) */ it('should emit cluster failed event if connecting to cluster rpc fails', function (done) { var socket = 'socket' var remote = { connect: sinon.stub() } var processInfo = { id: 'foo', process: new EventEmitter() } processService._managedProcessFactory.create.callsArgWithAsync(1, undefined, remote) remote.connect.callsArgWithAsync(0, new Error('nope!')) var receivedStartedEvent = false processService.on('cluster:started', function (info) { expect(info).to.equal(processInfo) expect(info.status).to.equal('started') receivedStartedEvent = true }) processService.on('cluster:failed', function (info) { expect(info).to.equal(processInfo) expect(info.status).to.equal('failed') expect(receivedStartedEvent).to.be.true done() }) processService._setupProcessCallbacks(processInfo, 'cluster') processInfo.process.emit('cluster:started', socket) }) it('should forward cluster worker events from child process', function (done) { var childProcess = new EventEmitter() childProcess.pid = 5 var processInfo = { id: 'foo', process: childProcess, validate: sinon.stub().callsArg(0), getProcessArgs: sinon.stub(), getProcessOptions: sinon.stub(), logger: processService._logger, cluster: true } processService._processes = { foo: processInfo } processService._portService.freePort.callsArgWith(0, undefined, 5) processService._child_process.fork.returns(childProcess) processService._processInfoStore.create.callsArgWith(1, undefined, processInfo) processService.startProcess(__filename, {}, sinon.stub()) var events = [ 'worker:forked', 'worker:starting', 'worker:started', 'worker:ready', 'worker:stopping', 'worker:exit', 'worker:failed', 'worker:restarting', 'worker:aborted', 'worker:uncaughtexception', 'worker:heapdump:start', 'worker:heapdump:error', 'worker:heapdump:complete', 'worker:gc:start', 'worker:gc:error', 'worker:gc:complete' ] var invocations = 0 events.forEach(function (event) { processService.on(event, function () { invocations++ if (invocations == events.length) { done() } }) }) events.forEach(function (event) { childProcess.emit(event) }) }) it('should update number of cluster workers event', function () { var childProcess = new EventEmitter() childProcess.pid = 5 var processInfo = { id: 'foo', process: childProcess, validate: sinon.stub().callsArg(0), getProcessArgs: sinon.stub(), getProcessOptions: sinon.stub(), logger: processService._logger, cluster: true, instances: 2 } processService._processes = { foo: processInfo } processService._portService.freePort.callsArgWith(0, undefined, 5) processService._child_process.fork.returns(childProcess) processService._processInfoStore.create.callsArgWith(1, undefined, processInfo) processService.startProcess(__filename, {}, sinon.stub()) expect(processInfo.instances).to.equal(2) childProcess.emit('cluster:workers', 5) expect(processInfo.instances).to.equal(5) }) it('should forward event when process exits cleanly', function (done) { var childProcess = new EventEmitter() childProcess.pid = 5 var processInfo = { id: 'foo', process: childProcess, validate: sinon.stub().callsArg(0), getProcessArgs: sinon.stub(), getProcessOptions: sinon.stub(), logger: processService._logger } processService._processes = { foo: processInfo } processService._portService.freePort.callsArgWith(0, undefined, 5) processService._child_process.fork.returns(childProcess) processService._processInfoStore.create.callsArgWith(1, undefined, processInfo) processService.startProcess(__filename, {}, sinon.stub()) processService._processInfoStore.find.withArgs('id', processInfo.id).returns(processInfo) processService.on('process:exit', function (info) { expect(info).to.equal(processInfo) done() }) childProcess.emit('exit', 0) expect(processInfo.status).to.equal('stopped') }) it('should only emit process exit once if error and exit are both fired', function () { var childProcess = new EventEmitter() childProcess.pid = 5 var processInfo = { id: 'foo', process: childProcess, validate: sinon.stub().callsArg(0), getProcessArgs: sinon.stub(), getProcessOptions: sinon.stub(), logger: processService._logger, socket: 'foo', remote: {} } processService._processes = { foo: processInfo } processService._portService.freePort.callsArgWith(0, undefined, 5) processService._child_process.fork.returns(childProcess) processService._processInfoStore.create.callsArgWith(1, undefined, processInfo) processService.startProcess(__filename, {}, sinon.stub()) processService._processInfoStore.find.withArgs('id', processInfo.id).onFirstCall().returns(processInfo) var invoked = 0 processService.on('process:exit', function (info) { expect(info).to.equal(processInfo) invoked++ }) expect(processInfo.socket).to.be.ok expect(processInfo.remote).to.be.ok childProcess.emit('error', new Error('panic!')) childProcess.emit('exit', 5) expect(invoked).to.equal(1) expect(processInfo.socket).to.not.exist expect(processInfo.remote).to.not.exist expect(processInfo.status).to.equal('errored') }) it('should remove a process', function (done) { var id = 'foo' var processInfo = { running: false } processService._processInfoStore.find.withArgs('id', id).returns(processInfo) processService.removeProcess(id, function (error) { expect(error).to.not.exist expect(processService._processInfoStore.remove.withArgs('id', id).called).to.be.true done() }) }) it('should object when removing a running process', function (done) { var id = 'foo' var processInfo = { running: true } processService._processInfoStore.find.withArgs('id', id).returns(processInfo) processService.removeProcess(id, function (error) { expect(error).to.be.ok done() }) }) it('should not remove a process when that process does not exist', function (done) { var id = 'foo' processService.removeProcess(id, function (error) { expect(error).to.not.exist expect(processService._processInfoStore.remove.called).to.be.false done() }) }) it('should list all processes', function () { var processes = [] processService._processInfoStore.all.returns(processes) var list = processService.listProcesses() expect(list).to.equal(processes) }) it('should kill all processes', function () { var processes = [{ remote: { kill: sinon.stub() }, process: { kill: sinon.stub() } }, { process: { kill: sinon.stub() } }] processService._processInfoStore.all.returns(processes) processService.killAll() expect(processes[0].remote.kill.called).to.be.true expect(processes[0].process.kill.called).to.be.false expect(processes[1].process.kill.called).to.be.true }) it('should find a process by name', function () { var name = 'foo' var processInfo = {} processService._processInfoStore.find.withArgs('name', name).returns(processInfo) var returned = processService.findByName(name) expect(returned).to.equal(processInfo) }) it('should not reset the reset count of a process that fails shortly after startup', function (done) { var remote = { connect: sinon.stub() } var childProcess = new EventEmitter() childProcess.pid = 5 childProcess.send = sinon.stub() var processInfo = { id: 'foo', process: childProcess, validate: sinon.stub().callsArg(0), getProcessArgs: sinon.stub(), getProcessOptions: sinon.stub(), logger: processService._logger } processService._processes = { foo: processInfo } processService._portService.freePort.callsArgWith(0, undefined, 5) processService._child_process.fork.returns(childProcess) processService._processInfoStore.create.callsArgWith(1, undefined, processInfo) processService.on('process:ready', function (info) { expect(info).to.equal(processInfo) processInfo.status = 'aborted' var previousRestartCount = processInfo.restarts = 3 clock.tick(processService._config.guvnor.resettimeout + 1) expect(processInfo.restarts).to.equal(previousRestartCount) done() }) processService.startProcess(__filename, {}, sinon.stub()) processService._managedProcessFactory.create.callsArgWith(1, undefined, remote) remote.connect.callsArgWith(0, undefined, remote) childProcess.emit('process:started', 'socket') }) it('should emit a failed message if creating the managed process object fails', function (done) { var error = new Error('urk!') error.code = 'FAIL' var processInfo = {} processService.on('process:failed', function (info, er) { expect(info).to.equal(processInfo) expect(processInfo.status).to.equal('failed') expect(er.code).to.equal(error.code) expect(er.stack).to.equal(error.stack) done() }) processService._managedProcessFactory.create.callsArgWith(1, error) processService._handleProcessStarted(processInfo, 'process', 'socket') }) it('should write to stdin for a process', function () { var processInfo = { process: new EventEmitter() } processInfo.process.stdin = { write: sinon.stub() } var prefix = 'process' var string = 'foo' processService._setupProcessCallbacks(processInfo, prefix) processInfo.process.emit(prefix + ':stdin:write', string) expect(processInfo.process.stdin.write.calledWith(string + '\n')).to.be.true }) it('should send a signal to a process', function () { var processInfo = { process: new EventEmitter() } processInfo.process.kill = sinon.stub() var prefix = 'process' var signal = 'foo' processService._setupProcessCallbacks(processInfo, prefix) processInfo.process.emit(prefix + ':signal', signal) expect(processInfo.process.kill.calledWith(signal)).to.be.true }) it('should survive sending an invalid signal to a process', function () { var processInfo = { process: new EventEmitter() } processInfo.process.kill = sinon.stub().throws(new Error('invalid!')) var prefix = 'process' var signal = 'foo' processService._setupProcessCallbacks(processInfo, prefix) processInfo.process.emit(prefix + ':signal', signal) expect(processInfo.process.kill.calledWith(signal)).to.be.true }) it('should start an existing process', function (done) { var processInfo = new ProcessInfo({ script: 'foo' }) var callback = sinon.stub() processService._startProcess = function (proc, cb) { expect(proc).to.equal(processInfo) expect(cb).to.equal(callback) done() } processService.startProcess(processInfo, callback) }) it('should not start a running process', function (done) { var processInfo = new ProcessInfo({ script: 'foo' }) processInfo.status = 'running' processService._processInfoStore.find.withArgs('name', 'foo').returns(processInfo) processService.startProcess('foo', {}, function (error) { expect(error.message).to.contain('already running') done() }) }) it('should start a stopped process', function (done) { var processInfo = new ProcessInfo({ script: 'foo' }) processInfo.status = 'stopped' var callback = sinon.stub() processService._processInfoStore.find.withArgs('name', 'foo').returns(processInfo) processService._startProcess = function (proc, cb) { expect(proc).to.equal(processInfo) expect(cb).to.equal(callback) done() } processService.startProcess('foo', {}, callback) }) })