diff --git a/lib/launcher.js b/lib/launcher.js index 0d32b1157..aed0a8548 100644 --- a/lib/launcher.js +++ b/lib/launcher.js @@ -1,5 +1,5 @@ var Promise = require('bluebird') -var Batch = require('batch') +var Jobs = require('qjobs') var helper = require('./helper') var log = require('./logger').create('launcher') @@ -25,27 +25,25 @@ var baseBrowserDecoratorFactory = function ( } var Launcher = function (server, emitter, injector) { - var browsers = [] + this._browsers = [] var lastStartTime + var self = this var getBrowserById = function (id) { - for (var i = 0; i < browsers.length; i++) { - if (browsers[i].id === id) { - return browsers[i] + for (var i = 0; i < self._browsers.length; i++) { + if (self._browsers[i].id === id) { + return self._browsers[i] } } return null } - this.launch = function (names, protocol, hostname, port, urlRoot, concurrency) { - var url = protocol + '//' + hostname + ':' + port + urlRoot - var batch = new Batch() - batch.concurrency(concurrency) + this.launchSingle = function (protocol, hostname, port, urlRoot) { + var self = this + return function (name) { + var url = protocol + '//' + hostname + ':' + port + urlRoot - lastStartTime = Date.now() - - names.forEach(function (name) { var locals = { id: ['value', Launcher.generateId()], name: ['value', name], @@ -75,29 +73,26 @@ var Launcher = function (server, emitter, injector) { return } - if (server.loadErrors.length > 0) return [] // TODO(vojta): remove in v1.0 (BC for old launchers) if (!browser.forceKill) { browser.forceKill = function () { - var self = this - + var me = this return new Promise(function (resolve) { - self.kill(resolve) + me.kill(resolve) }) } browser.restart = function () { - var self = this + var me = this this.kill(function () { - self.start(url) + me.start(url) }) } } - batch.push(function (done) { + self.jobs.add(function (args, done) { log.info('Starting browser %s', helper.isDefined(browser.displayName) ? browser.displayName : browser.name) - browser.start(url) browser.on('browser_process_failure', function () { done(browser.error) }) @@ -109,12 +104,34 @@ var Launcher = function (server, emitter, injector) { done(null, browser) }) - }) - browsers.push(browser) - }) + browser.start(url) + }, []) + + self.jobs.run() + self._browsers.push(browser) + } + } + + this.launch = function (names, concurrency) { + log.info('Launching', names, concurrency) + this.jobs = new Jobs({maxConcurrency: concurrency}) - batch.end(function (err) { + var self = this + lastStartTime = Date.now() + + if (server.loadErrors.length === 0) { + names.forEach(function (name) { + injector.invoke(self.launchSingle, self)(name) + }) + } else { + // Empty task to ensure `end` is emitted + this.jobs.add(function (args, done) { + done() + }, []) + } + + this.jobs.on('end', function (err) { log.debug('Finished all browsers') if (err) { @@ -122,16 +139,21 @@ var Launcher = function (server, emitter, injector) { } }) - return browsers + this.jobs.run() + + return self._browsers } this.launch.$inject = [ 'config.browsers', + 'config.concurrency' + ] + + this.launchSingle.$inject = [ 'config.protocol', 'config.hostname', 'config.port', - 'config.urlRoot', - 'config.concurrency' + 'config.urlRoot' ] this.kill = function (id, callback) { @@ -169,24 +191,24 @@ var Launcher = function (server, emitter, injector) { } } - if (!browsers.length) { + if (!self._browsers.length) { return process.nextTick(callback) } - browsers.forEach(function (browser) { + self._browsers.forEach(function (browser) { remaining++ browser.forceKill().then(finish) }) } this.areAllCaptured = function () { - return !browsers.some(function (browser) { + return !self._browsers.some(function (browser) { return !browser.isCaptured() }) } this.markCaptured = function (id) { - browsers.forEach(function (browser) { + self._browsers.forEach(function (browser) { if (browser.id === id) { browser.markCaptured() log.debug('%s (id %s) captured in %d secs', browser.name, browser.id, diff --git a/package.json b/package.json index c4593874b..b0d8e246a 100644 --- a/package.json +++ b/package.json @@ -261,7 +261,6 @@ "Karolis Narkevicius " ], "dependencies": { - "batch": "^0.5.3", "bluebird": "^3.3.0", "body-parser": "^1.12.4", "chokidar": "^1.4.1", @@ -281,6 +280,7 @@ "mime": "^1.3.4", "minimatch": "^3.0.0", "optimist": "^0.6.1", + "qjobs": "^1.1.4", "rimraf": "^2.3.3", "socket.io": "^1.4.5", "source-map": "^0.5.3", diff --git a/test/e2e/load.feature b/test/e2e/load.feature index 865f211a7..cab1c8c30 100644 --- a/test/e2e/load.feature +++ b/test/e2e/load.feature @@ -98,9 +98,5 @@ Feature: Basic Testrunner """ And it fails with like: """ - Cannot load browser "NonExistingBrowser": it is not registered! Perhaps you are missing some plugin\? - """ - And it fails with like: - """ - Found 3 load errors + Found 2 load errors """ diff --git a/test/unit/launcher.spec.js b/test/unit/launcher.spec.js index dfbde5842..366fc16ba 100644 --- a/test/unit/launcher.spec.js +++ b/test/unit/launcher.spec.js @@ -21,11 +21,18 @@ class FakeBrowser { constructor (id, name, baseBrowserDecorator) { this.id = id this.name = name + this.DEFAULT_CMD = { + linux: '/script', + darwin: '/script', + win32: 'script.exe' + } + this.ENV_CMD = 'SCRIPT_BIN' + baseBrowserDecorator(this) FakeBrowser._instances.push(this) sinon.stub(this, 'start', () => { this.state = this.STATE_BEING_CAPTURED - return this.state + this._done() }) stubPromise(this, 'forceKill') sinon.stub(this, 'restart') @@ -36,10 +43,18 @@ class ScriptBrowser { constructor (id, name, baseBrowserDecorator) { this.id = id this.name = name + this.DEFAULT_CMD = { + linux: '/script', + darwin: '/script', + win32: 'script.exe' + } + this.ENV_CMD = 'SCRIPT_BIN' + baseBrowserDecorator(this) ScriptBrowser._instances.push(this) sinon.stub(this, 'start', () => { this.state = this.STATE_BEING_CAPTURED + this._done() }) stubPromise(this, 'forceKill') sinon.stub(this, 'restart') @@ -70,82 +85,82 @@ describe('launcher', () => { describe('Launcher', () => { var emitter var server - var l = emitter = server = null + var config + var l beforeEach(() => { emitter = new events.EventEmitter() server = {'loadErrors': []} + config = { + captureTimeout: 0, + protocol: 'http:', + hostname: 'localhost', + port: 1234, + urlRoot: '/root/' + } var injector = new di.Injector([{ 'launcher:Fake': ['type', FakeBrowser], 'launcher:Script': ['type', ScriptBrowser], 'server': ['value', server], 'emitter': ['value', emitter], - 'config': ['value', {captureTimeout: 0}], + 'config': ['value', config], 'timer': ['factory', createMockTimer] }]) l = new launcher.Launcher(server, emitter, injector) }) describe('launch', () => { - it('should inject and start all browsers', () => { - l.launch(['Fake'], 'http:', 'localhost', 1234, '/root/', 1) + it('should inject and start all browsers', (done) => { + l.launch(['Fake'], 1) var browser = FakeBrowser._instances.pop() - expect(browser.start).to.have.been.calledWith('http://localhost:1234/root/') - expect(browser.id).to.equal(lastGeneratedId) - expect(browser.name).to.equal('Fake') + l.jobs.on('end', () => { + expect(browser.start).to.have.been.calledWith('http://localhost:1234/root/') + expect(browser.id).to.equal(lastGeneratedId) + expect(browser.name).to.equal('Fake') + done() + }) }) - it('should not start when server has load errors', () => { + it('should not start when server has load errors', (done) => { server.loadErrors = ['error'] - l.launch(['Fake'], 'http:', 'localhost', 1234, '/root/', 1) - var browser = FakeBrowser._instances.pop() - expect(browser.start).to.not.have.been.called - expect(browser.id).to.equal(lastGeneratedId) - expect(browser.name).to.equal('Fake') + l.launch(['Fake'], 1) + + l.jobs.on('end', () => { + expect(FakeBrowser._instances).to.be.empty + done() + }) }) - it('should allow launching a script', () => { - l.launch(['/usr/local/bin/special-browser'], 'http:', 'localhost', 1234, '/', 1) + it('should allow launching a script', (done) => { + l.launch(['/usr/local/bin/special-browser'], 1) var script = ScriptBrowser._instances.pop() - expect(script.start).to.have.been.calledWith('http://localhost:1234/') - expect(script.name).to.equal('/usr/local/bin/special-browser') - }) - it('should use the non default host', () => { - l.launch(['Fake'], 'http:', 'whatever', 1234, '/root/', 1) + l.jobs.on('end', () => { + expect(script.start).to.have.been.calledWith('http://localhost:1234/root/') + expect(script.name).to.equal('/usr/local/bin/special-browser') - var browser = FakeBrowser._instances.pop() - expect(browser.start).to.have.been.calledWith('http://whatever:1234/root/') + done() + }) }) - it('should only launch the specified number of browsers at once', () => { - l.launch([ - 'Fake', - 'Fake', - 'Fake' - ], 'http:', 'whatever', 1234, '/root/', 2) - - var b1 = FakeBrowser._instances.pop() - var b2 = FakeBrowser._instances.pop() - var b3 = FakeBrowser._instances.pop() - - expect(b1.start).to.not.have.been.called - expect(b2.start).to.have.been.calledOnce - expect(b3.start).to.have.been.calledOnce + it('should use the non default host', (done) => { + config.hostname = 'whatever' + l.launch(['Fake'], 1) - b1._done() - b2._done() - - expect(b1.start).to.have.been.calledOnce + var browser = FakeBrowser._instances.pop() + l.jobs.on('end', () => { + expect(browser.start).to.have.been.calledWith('http://whatever:1234/root/') + done() + }) }) }) describe('restart', () => { it('should restart the browser', () => { - l.launch(['Fake'], 'http:', 'localhost', 1234, '/root/', 1) + l.launch(['Fake'], 1) var browser = FakeBrowser._instances.pop() var returnedValue = l.restart(lastGeneratedId) @@ -154,14 +169,14 @@ describe('launcher', () => { }) it('should return false if the browser was not launched by launcher (manual)', () => { - l.launch([], 'http:', 'localhost', 1234, '/', 1) + l.launch([], 1) expect(l.restart('manual-id')).to.equal(false) }) }) describe('kill', () => { it('should kill browser with given id', (done) => { - l.launch(['Fake'], 'http:', 'localhost', 1234, '/', 1) + l.launch(['Fake'], 1) var browser = FakeBrowser._instances.pop() l.kill(browser.id, done) @@ -171,7 +186,7 @@ describe('launcher', () => { }) it('should return false if browser does not exist, but still resolve the callback', (done) => { - l.launch(['Fake'], 'http:', 'localhost', 1234, '/', 1) + l.launch(['Fake'], 1) var browser = FakeBrowser._instances.pop() var returnedValue = l.kill('weird-id', done) @@ -180,7 +195,7 @@ describe('launcher', () => { }) it('should not require a callback', (done) => { - l.launch(['Fake'], 'http:', 'localhost', 1234, '/', 1) + l.launch(['Fake'], 1) FakeBrowser._instances.pop() l.kill('weird-id') @@ -190,7 +205,7 @@ describe('launcher', () => { describe('killAll', () => { it('should kill all running processe', () => { - l.launch(['Fake', 'Fake'], 'http:', 'localhost', 1234, '/', 1) + l.launch(['Fake', 'Fake'], 1) l.killAll() var browser = FakeBrowser._instances.pop() @@ -203,7 +218,7 @@ describe('launcher', () => { it('should call callback when all processes killed', () => { var exitSpy = sinon.spy() - l.launch(['Fake', 'Fake'], 'http:', 'localhost', 1234, '/', 1) + l.launch(['Fake', 'Fake'], 1) l.killAll(exitSpy) expect(exitSpy).not.to.have.been.called @@ -234,21 +249,27 @@ describe('launcher', () => { describe('areAllCaptured', () => { it('should return true if only if all browsers captured', () => { - l.launch(['Fake', 'Fake'], 'http:', 'localhost', 1234, '/', 2) + l._browsers = [{ + isCaptured: () => true + }, { + isCaptured: () => false + }] - expect(l.areAllCaptured()).to.equal(false) + expect(l.areAllCaptured()).to.be.equal(false) - l.markCaptured(1) - expect(l.areAllCaptured()).to.equal(false) + l._browsers = [{ + isCaptured: () => true + }, { + isCaptured: () => true + }] - l.markCaptured(2) - expect(l.areAllCaptured()).to.equal(true) + expect(l.areAllCaptured()).to.be.equal(true) }) }) describe('onExit', () => { it('should kill all browsers', (done) => { - l.launch(['Fake', 'Fake'], 'http:', 'localhost', 1234, '/', 1) + l.launch(['Fake', 'Fake'], 1) emitter.emitAsync('exit').then(done)