Skip to content

Commit a4c1eba

Browse files
authored
Merge 05c8fc7 into f1e85dc
2 parents f1e85dc + 05c8fc7 commit a4c1eba

File tree

3 files changed

+159
-24
lines changed

3 files changed

+159
-24
lines changed

.npmignore

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33

44
tmp
55
test
6-
tasks
6+
#tasks
77
/tools/
88
docs
9-
client
9+
#client
1010
logo
1111
integration-tests
1212

@@ -16,8 +16,8 @@ Gruntfile.coffee
1616
credentials
1717
Karma.sublime-*
1818

19-
static/karma.src.js
20-
static/karma.wrapper
19+
#static/karma.src.js
20+
#static/karma.wrapper
2121
test-results.xml
2222
thesis.pdf
2323
mocha-watch.sh

lib/server.js

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,35 @@ class Server extends KarmaEventEmitter {
147147
return this._fileList ? this._fileList.changeFile(path) : Promise.resolve()
148148
}
149149

150+
emitExitAsync (code) {
151+
const name = 'exit'
152+
let pending = this.listeners(name).length
153+
const deferred = helper.defer()
154+
155+
function resolve () {
156+
deferred.resolve(code)
157+
}
158+
159+
try {
160+
this.emit(name, (newCode) => {
161+
if (newCode && typeof newCode === 'number') {
162+
// Only update code if it is given and not zero
163+
code = newCode
164+
}
165+
if (!--pending) {
166+
resolve()
167+
}
168+
})
169+
170+
if (!pending) {
171+
resolve()
172+
}
173+
} catch (err) {
174+
deferred.reject(err)
175+
}
176+
return deferred.promise
177+
}
178+
150179
async _start (config, launcher, preprocess, fileList, capturedBrowsers, executor, done) {
151180
if (config.detached) {
152181
this._detach(config, done)
@@ -298,7 +327,8 @@ class Server extends KarmaEventEmitter {
298327

299328
this.on('stop', function (done) {
300329
this.log.debug('Received stop event, exiting.')
301-
return disconnectBrowsers().then(done)
330+
disconnectBrowsers()
331+
done()
302332
})
303333

304334
if (config.singleRun) {
@@ -356,28 +386,29 @@ class Server extends KarmaEventEmitter {
356386
}
357387
})
358388

359-
let removeAllListenersDone = false
360-
const removeAllListeners = () => {
361-
if (removeAllListenersDone) {
362-
return
389+
this.emitExitAsync(code).catch((err) => {
390+
this.log.error('Error while calling exit event listeners\n' + err.stack || err)
391+
return 1
392+
}).then((code) => {
393+
socketServer.sockets.removeAllListeners()
394+
socketServer.close()
395+
396+
let removeAllListenersDone = false
397+
const removeAllListeners = () => {
398+
if (removeAllListenersDone) {
399+
return
400+
}
401+
removeAllListenersDone = true
402+
webServer.removeAllListeners()
403+
processWrapper.removeAllListeners()
404+
done(code || 0)
363405
}
364-
removeAllListenersDone = true
365-
webServer.removeAllListeners()
366-
processWrapper.removeAllListeners()
367-
done(code || 0)
368-
}
369406

370-
return this.emitAsync('exit').then(() => {
371-
return new Promise((resolve, reject) => {
372-
socketServer.sockets.removeAllListeners()
373-
socketServer.close()
374-
const closeTimeout = setTimeout(removeAllListeners, webServerCloseTimeout)
407+
const closeTimeout = setTimeout(removeAllListeners, webServerCloseTimeout)
375408

376-
webServer.close(() => {
377-
clearTimeout(closeTimeout)
378-
removeAllListeners()
379-
resolve()
380-
})
409+
webServer.close(() => {
410+
clearTimeout(closeTimeout)
411+
removeAllListeners()
381412
})
382413
})
383414
}

test/unit/server.spec.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,110 @@ describe('server', () => {
312312
expect(await exitCode()).to.have.equal(15)
313313
})
314314

315+
it('given on run_complete with exit event listener (15)', async () => {
316+
mockProcess(process)
317+
318+
await server._start(mockConfig, mockLauncher, null, mockFileList, browserCollection, mockExecutor, (exitCode) => {
319+
resolveExitCode(exitCode)
320+
})
321+
322+
// last non-zero exit code will be taken
323+
server.on('exit', (done) => {
324+
setTimeout(() => done(30))
325+
})
326+
server.on('exit', (done) => {
327+
setTimeout(() => done(15))
328+
})
329+
server.on('exit', (done) => {
330+
setTimeout(() => done(0))
331+
})
332+
333+
// Provided run_complete exitCode will be overridden by exit listeners
334+
server.emit('run_complete', browserCollection, { exitCode: 5 })
335+
336+
function mockProcess (process) {
337+
sinon.stub(process, 'kill').callsFake((pid, ev) => process.emit(ev))
338+
}
339+
expect(await exitCode()).to.have.equal(15)
340+
})
341+
342+
it('given on run_complete with exit event listener (0)', async () => {
343+
mockProcess(process)
344+
345+
await server._start(mockConfig, mockLauncher, null, mockFileList, browserCollection, mockExecutor, (exitCode) => {
346+
resolveExitCode(exitCode)
347+
})
348+
349+
// exit listeners can't set exit code back to 0
350+
server.on('exit', (done) => {
351+
setTimeout(() => done(0))
352+
})
353+
354+
server.emit('run_complete', browserCollection, { exitCode: 15 })
355+
356+
function mockProcess (process) {
357+
sinon.stub(process, 'kill').callsFake((pid, ev) => process.emit(ev))
358+
}
359+
expect(await exitCode()).to.have.equal(15)
360+
})
361+
362+
it('1 on run_complete with exit event listener throws', async () => {
363+
mockProcess(process)
364+
365+
await server._start(mockConfig, mockLauncher, null, mockFileList, browserCollection, mockExecutor, (exitCode) => {
366+
resolveExitCode(exitCode)
367+
})
368+
369+
server.on('exit', (done) => {
370+
throw new Error('async error from exit event listener')
371+
})
372+
373+
server.emit('run_complete', browserCollection, { exitCode: 0 })
374+
375+
function mockProcess (process) {
376+
sinon.stub(process, 'kill').callsFake((pid, ev) => process.emit(ev))
377+
}
378+
expect(await exitCode()).to.have.equal(1)
379+
})
380+
381+
it('1 on run_complete with exit event listener rejects', async () => {
382+
mockProcess(process)
383+
384+
await server._start(mockConfig, mockLauncher, null, mockFileList, browserCollection, mockExecutor, (exitCode) => {
385+
resolveExitCode(exitCode)
386+
})
387+
388+
function onExit (done) {
389+
// Need to remove listener to prevent endless loop via unhandledRejection handler
390+
// which again calls disconnectBrowsers to fire the 'exit' event
391+
server.off('exit', onExit)
392+
return Promise.reject(new Error('async error from exit event listener'))
393+
}
394+
server.on('exit', onExit)
395+
396+
server.emit('run_complete', browserCollection, { exitCode: 0 })
397+
398+
function mockProcess (process) {
399+
sinon.stub(process, 'kill').callsFake((pid, ev) => process.emit(ev))
400+
}
401+
expect(await exitCode()).to.have.equal(1)
402+
})
403+
404+
it('0 on server stop', async () => {
405+
mockProcess(process)
406+
407+
await server._start(mockConfig, mockLauncher, null, mockFileList, browserCollection, mockExecutor, (exitCode) => {
408+
resolveExitCode(exitCode)
409+
})
410+
411+
server.stop()
412+
413+
function mockProcess (process) {
414+
sinon.stub(process, 'kill').callsFake((pid, ev) => process.emit(ev))
415+
}
416+
expect(await exitCode()).to.have.equal(0)
417+
})
418+
315419
it('1 on browser_process_failure (singleRunBrowserNotCaptured)', async () => {
316420
mockProcess(process)
317421

0 commit comments

Comments
 (0)