UNPKG

guvnor

Version:

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

417 lines (308 loc) 11.9 kB
var expect = require('chai').expect, sinon = require('sinon'), path = require('path'), ProcessRPC = require('../../../../lib/daemon/process/ProcessRPC'), EventEmitter = require('events').EventEmitter describe('ProcessRPC', function () { var processRpc, server beforeEach(function () { processRpc = new ProcessRPC() processRpc._userInfo = { getUid: sinon.stub(), getGid: sinon.stub() } processRpc._fileSystem = { findOrCreateProcessDirectory: sinon.stub() } processRpc._parentProcess = { send: sinon.stub() } processRpc._dnode = sinon.stub() processRpc._fs = { chown: sinon.stub(), chmod: sinon.stub(), unlink: sinon.stub(), stat: sinon.stub(), createReadStream: sinon.stub() } processRpc._heapdump = { writeSnapshot: sinon.stub() } processRpc._config = { guvnor: {} } processRpc._usage = { lookup: sinon.stub() } processRpc._process = { exit: sinon.stub(), emit: sinon.stub() } server = { listen: sinon.stub() } processRpc._dnode.returns(server) }) it('should start a kill switch', function (done) { var processDirectory = 'foo bar baz' processRpc._config.guvnor.rundir = processDirectory processRpc._fs.chown.callsArg(3) processRpc._fs.chmod.callsArg(2) processRpc._dnode.returns(server) server.listen.callsArg(1) processRpc.startDnodeServer(function () { // should have started a server expect(server.listen.callCount).to.equal(1) expect(server.listen.getCall(0).args[0]).to.equal(processDirectory + '/processes/' + process.pid) done() }) }) it('should provide a method to kill the process', function (done) { processRpc._fileSystem.findOrCreateProcessDirectory.callsArgWith(0, null, 'foo') processRpc._fs.chown.callsArg(3) processRpc._fs.chmod.callsArg(2) processRpc._dnode.returns(server) server.listen.callsArg(1) processRpc.startDnodeServer(function () { expect(processRpc._dnode.callCount).to.equal(1) expect(processRpc._dnode.getCall(0).args[0].kill).to.be.ok done() }) }) it('should allow sending an event to the process', function () { processRpc.send('event:one') expect(processRpc._process.emit.withArgs('event:one').called).to.be.true }) it('should allow sending an event to the process with arguments', function () { processRpc.send('event:two', 'foo', 'bar') expect(processRpc._process.emit.withArgs('event:two', 'foo', 'bar').called).to.be.true }) it('should send an event and invoke a callback', function (done) { processRpc.send('foo', 'bar', 'baz', done) }) it('should dump heap', function (done) { var name = 'foo' var size = 42390823 processRpc._heapdump.writeSnapshot.callsArgWith(0, undefined, name) processRpc._fs.stat.callsArgWith(1, undefined, { size: size }) processRpc.dumpHeap(function (error, snapshot) { expect(error).to.not.exist expect(processRpc._parentProcess.send.calledWith('process:heapdump:start')).to.be.true expect(processRpc._parentProcess.send.calledWith('process:heapdump:complete')).to.be.true expect(snapshot.path).to.contain(name) expect(snapshot.size).to.equal(size) done() }) }) it('should dump heap with no callback', function (done) { var name = 'foo' processRpc._heapdump.writeSnapshot.callsArgWith(0, undefined, name) processRpc._fs.stat.callsArgWith(1, undefined, { size: 5 }) processRpc.dumpHeap() setTimeout(function () { expect(processRpc._parentProcess.send.calledWith('process:heapdump:start')).to.be.true expect(processRpc._parentProcess.send.calledWith('process:heapdump:complete')).to.be.true done() }, 100) }) it('should inform of dump heap error', function (done) { processRpc._heapdump.writeSnapshot.callsArgWith(0, new Error('urk!')) processRpc.dumpHeap(function (error, fileName) { expect(fileName).to.not.exist expect(processRpc._parentProcess.send.calledWith('process:heapdump:start')).to.be.true expect(processRpc._parentProcess.send.calledWith('process:heapdump:error')).to.be.true done() }) }) it('should force gc', function (done) { global.gc = sinon.stub() processRpc.forceGc(function (error) { expect(error).to.not.exist expect(global.gc.called).to.be.true expect(processRpc._parentProcess.send.calledWith('process:gc:start')).to.be.true expect(processRpc._parentProcess.send.calledWith('process:gc:complete')).to.be.true done() }) }) it('should survive forcing gc when gc is not exposed', function (done) { global.gc = undefined processRpc.forceGc(function (error) { expect(error).to.not.exist expect(processRpc._parentProcess.send.calledWith('process:gc:start')).to.be.true expect(processRpc._parentProcess.send.calledWith('process:gc:complete')).to.be.true done() }) }) it('should force gc with no callback', function () { global.gc = sinon.stub() processRpc.forceGc() expect(global.gc.called).to.be.true expect(processRpc._parentProcess.send.calledWith('process:gc:start')).to.be.true expect(processRpc._parentProcess.send.calledWith('process:gc:complete')).to.be.true }) it('should delegate to parent to write to stdin', function (done) { var string = 'hello' processRpc.write(string, function (error) { expect(error).to.not.exist expect(processRpc._parentProcess.send.calledWith('process:stdin:write', string)).to.be.true done() }) }) it('should delegate to parent to write to stdin with no callback', function () { var string = 'hello' processRpc.write(string) expect(processRpc._parentProcess.send.calledWith('process:stdin:write', string)).to.be.true }) it('should delegate to parent to signal', function (done) { var signal = 'hello' processRpc.signal(signal, function (error) { expect(error).to.not.exist expect(processRpc._parentProcess.send.calledWith('process:signal', signal)).to.be.true done() }) }) it('should delegate to parent to signal with no callback', function () { var signal = 'hello' processRpc.signal(signal) expect(processRpc._parentProcess.send.calledWith('process:signal', signal)).to.be.true }) it('should propagate usage error when reporting status', function (done) { var error = new Error('Urk!') processRpc._usage.lookup.callsArgWith(2, error) processRpc.reportStatus(function (er) { expect(er).to.equal(error) done() }) }) it('should inform parent of restart', function (done) { processRpc.socket = 'socket' processRpc._fs.unlink.withArgs(processRpc.socket).callsArg(1) processRpc.restart(function (error) { expect(error).to.not.exist expect(processRpc._parentProcess.send.calledWith('process:restarting')).to.be.true process.nextTick(function() { expect(processRpc._process.exit.withArgs(0).called).to.be.true done() }) }) }) it('should exit with non-zero code if removing socket fails', function (done) { var error = new Error('Urk!') processRpc.socket = 'socket' processRpc._fs.unlink.withArgs(processRpc.socket).callsArgWith(1, error) processRpc.restart(function (er) { expect(er).to.equal(error) expect(processRpc._parentProcess.send.calledWith('process:restarting')).to.be.true process.nextTick(function() { expect(processRpc._process.exit.withArgs(1).called).to.be.true done() }) }) }) it('should inform parent of restart with no callback', function (done) { processRpc.socket = 'socket' processRpc._fs.unlink.withArgs(processRpc.socket).callsArgWith(1) processRpc.restart() expect(processRpc._parentProcess.send.calledWith('process:restarting')).to.be.true process.nextTick(function() { expect(processRpc._process.exit.withArgs(0).called).to.be.true done() }) }) it('should inform parent of stopping', function (done) { processRpc.socket = 'socket' processRpc._fs.unlink.withArgs(processRpc.socket).callsArg(1) processRpc.kill(function (error) { expect(error).to.not.exist expect(processRpc._parentProcess.send.calledWith('process:stopping')).to.be.true process.nextTick(function() { expect(processRpc._process.exit.withArgs(0).called).to.be.true done() }) }) }) it('should exit with non-zero code if removing socket fails when stopping', function (done) { var error = new Error('Urk!') processRpc.socket = 'socket' processRpc._fs.unlink.withArgs(processRpc.socket).callsArgWith(1, error) processRpc.kill(function (er) { expect(er).to.equal(error) expect(processRpc._parentProcess.send.calledWith('process:stopping')).to.be.true process.nextTick(function() { expect(processRpc._process.exit.withArgs(1).called).to.be.true done() }) }) }) it('should inform parent of stopping with no callback', function (done) { processRpc.socket = 'socket' processRpc._fs.unlink.withArgs(processRpc.socket).callsArgWith(1) processRpc.kill() expect(processRpc._parentProcess.send.calledWith('process:stopping')).to.be.true process.nextTick(function() { expect(processRpc._process.exit.withArgs(0).called).to.be.true done() }) }) it('should fetch a heap snapshot', function () { var snapshot = { id: 'foo', path: 'bar' } processRpc._heapSnapshots[snapshot.id] = snapshot var onReadable = sinon.stub() var onData = sinon.stub() var onEnd = sinon.stub() var callback = sinon.stub() var stream = new EventEmitter() stream.read = sinon.stub() processRpc._fs.createReadStream.withArgs(snapshot.path).returns(stream) processRpc.fetchHeapSnapshot(snapshot.id, onReadable, onData, onEnd, callback) expect(callback.calledWith(undefined, snapshot, sinon.match.func)).to.be.true expect(onReadable.called).to.be.false stream.emit('readable') expect(onReadable.called).to.be.true // when read returns nothing, onData should not be called expect(onData.called).to.be.false callback.getCall(0).args[2]() expect(onData.called).to.be.false // when read returns something, onData should be called expect(onData.called).to.be.false stream.read.returns('buf') callback.getCall(0).args[2]() expect(onData.calledWith('buf')).to.be.true expect(onEnd.called).to.be.false stream.emit('end') expect(onEnd.called).to.be.true }) it('should pass an error when no snapshot can be found during fetching', function () { var callback = sinon.stub() processRpc.fetchHeapSnapshot('foo', null, null, null, callback) expect(callback.getCall(0).args[0].message).to.contain('No snapshot for') expect(callback.getCall(0).args[0].code).to.equal('ENOENT') }) it('should remove a heap snapshot', function () { var snapshot = { id: 'foo', path: 'bar' } processRpc._heapSnapshots[snapshot.id] = snapshot var callback = sinon.stub() processRpc._fs.unlink.withArgs(snapshot.path).callsArg(1) processRpc.removeHeapSnapshot(snapshot.id, callback) expect(callback.called).to.be.true expect(processRpc._parentProcess.send.calledWith('process:heapdump:removed', snapshot)).to.be.true }) it('should remove a non-existent heap snapshot', function () { var callback = sinon.stub() processRpc.removeHeapSnapshot('foo', callback) expect(callback.called).to.be.true expect(processRpc._parentProcess.send.calledWith('process:heapdump:removed', { id: 'foo' })).to.be.true }) })