diff --git a/lib/emitter_wrapper.js b/lib/emitter_wrapper.js new file mode 100644 index 000000000..1e7126b07 --- /dev/null +++ b/lib/emitter_wrapper.js @@ -0,0 +1,31 @@ +function EmitterWrapper(emitter) { + this.listeners = {}; + this.emitter = emitter; +} + +EmitterWrapper.prototype.addListener = EmitterWrapper.prototype.on = function (event, listener) { + this.emitter.addListener(event, listener); + + if (!this.listeners.hasOwnProperty(event)) { + this.listeners[event] = []; + } + + this.listeners[event].push(listener); + + return this; +}; + +EmitterWrapper.prototype.removeAllListeners = function (event) { + var events = event ? [event] : Object.keys(this.listeners); + var self = this; + events.forEach(function (event) { + self.listeners[event].forEach(function (listener) { + self.emitter.removeListener(event, listener); + }); + delete self.listeners[event]; + }); + + return this; +}; + +module.exports = EmitterWrapper; diff --git a/lib/server.js b/lib/server.js index 3efbebeb8..51ec91cb6 100644 --- a/lib/server.js +++ b/lib/server.js @@ -18,6 +18,8 @@ var EventEmitter = events.EventEmitter; var Executor = require('./executor'); var Browser = require('./browser'); var BrowserCollection = require('./browser_collection'); +var EmitterWrapper = require('./emitter_wrapper'); +var processWrapper = new EmitterWrapper(process); var log = logger.create(); @@ -198,8 +200,9 @@ var start = function(injector, config, launcher, globalEmitter, preprocess, file }); globalEmitter.emitAsync('exit').then(function() { - // All systems down, stop the webserver - webServer.close(function () { + webServer.close(function() { + webServer.removeAllListeners(); + processWrapper.removeAllListeners(); done(code || 0); }); }); @@ -207,8 +210,8 @@ var start = function(injector, config, launcher, globalEmitter, preprocess, file try { - process.on('SIGINT', disconnectBrowsers); - process.on('SIGTERM', disconnectBrowsers); + processWrapper.on('SIGINT', disconnectBrowsers); + processWrapper.on('SIGTERM', disconnectBrowsers); } catch (e) { // Windows doesn't support signals yet, so they simply don't get this handling. // https://github.com/joyent/node/issues/1553 @@ -216,7 +219,7 @@ var start = function(injector, config, launcher, globalEmitter, preprocess, file // Handle all unhandled exceptions, so we don't just exit but // disconnect the browsers before exiting. - process.on('uncaughtException', function(error) { + processWrapper.on('uncaughtException', function (error) { log.error(error); disconnectBrowsers(1); }); diff --git a/test/unit/emitter_wrapper.spec.coffee b/test/unit/emitter_wrapper.spec.coffee new file mode 100644 index 000000000..fa9ae9579 --- /dev/null +++ b/test/unit/emitter_wrapper.spec.coffee @@ -0,0 +1,57 @@ +#============================================================================== +# lib/emitter_wrapper.js module +#============================================================================== +describe 'emitter_wrapper', -> + EmitterWrapper = require '../../lib/emitter_wrapper' + events = require 'events' + EventEmitter = events.EventEmitter + + emitter = null + wrapped = null + called = false + + beforeEach -> + emitter = new EventEmitter() + emitter.aMethod = (e) -> called = true + emitter.on 'anEvent', emitter.aMethod + wrapped = new EmitterWrapper(emitter) + + #=========================================================================== + # wrapper.addListener + #=========================================================================== + describe 'addListener', -> + aListener = (e) -> true + + it 'should add a listener to the wrapped emitter', -> + wrapped.addListener 'anEvent', aListener + expect(emitter.listeners('anEvent')).to.contain aListener + + it 'returns the wrapped emitter', -> + expect(wrapped.addListener 'anEvent', aListener).to.equal wrapped + + #=========================================================================== + # wrapper.removeAllListeners + #=========================================================================== + describe 'removeAllListeners', -> + aListener = (e) -> true + + beforeEach -> + wrapped.addListener 'anEvent', aListener + + it 'should remove listeners that were attached via the wrapper', -> + wrapped.removeAllListeners() + expect(emitter.listeners('anEvent')).not.to.contain aListener + + it 'should not remove listeners that were attached to the original emitter', -> + wrapped.removeAllListeners() + expect(emitter.listeners('anEvent')).to.contain emitter.aMethod + + it 'should remove only matching listeners when called with an event name', -> + anotherListener = (e) -> true + wrapped.addListener 'anotherEvent', anotherListener + wrapped.removeAllListeners('anEvent') + expect(emitter.listeners('anEvent')).not.to.contain aListener + expect(emitter.listeners('anotherEvent')).to.contain anotherListener + + it 'returns the wrapped emitter', -> + expect(wrapped.addListener 'anEvent', aListener).to.equal wrapped