Skip to content

Commit 7ab86be

Browse files
authoredFeb 3, 2021
fix: report launcher process error when exit event is not emitted (#3647)
Co-authored-by: Chris Bottin <cbottin@smartcommunications.com>
1 parent 3cb26e3 commit 7ab86be

File tree

2 files changed

+51
-18
lines changed

2 files changed

+51
-18
lines changed
 

‎lib/launchers/process.js

+5
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ function ProcessLauncher (spawn, tempDir, timer, processKillTimeout) {
9393
} else {
9494
errorOutput += err.toString()
9595
}
96+
self._onProcessExit(-1, null, errorOutput)
9697
})
9798

9899
self._process.stderr.on('data', function (errBuff) {
@@ -101,6 +102,10 @@ function ProcessLauncher (spawn, tempDir, timer, processKillTimeout) {
101102
}
102103

103104
this._onProcessExit = function (code, signal, errorOutput) {
105+
if (!self._process) {
106+
// Both exit and error events trigger _onProcessExit(), but we only need one cleanup.
107+
return
108+
}
104109
log.debug(`Process ${self.name} exited with code ${code} and signal ${signal}`)
105110

106111
let error = null

‎test/unit/launchers/process.spec.js

+46-18
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,25 @@ const CaptureTimeoutLauncher = require('../../../lib/launchers/capture_timeout')
77
const ProcessLauncher = require('../../../lib/launchers/process')
88
const EventEmitter = require('../../../lib/events').EventEmitter
99
const createMockTimer = require('../mocks/timer')
10+
const logger = require('../../../lib/logger')
1011

1112
describe('launchers/process.js', () => {
1213
let emitter
1314
let mockSpawn
1415
let mockTempDir
1516
let launcher
17+
let logErrorSpy
18+
let logDebugSpy
1619

1720
const BROWSER_PATH = path.normalize('/usr/bin/browser')
1821

1922
beforeEach(() => {
2023
emitter = new EventEmitter()
2124
launcher = new BaseLauncher('fake-id', emitter)
25+
launcher.name = 'fake-name'
26+
launcher.ENV_CMD = 'fake-ENV-CMD'
27+
logErrorSpy = sinon.spy(logger.create('launcher'), 'error')
28+
logDebugSpy = sinon.spy(logger.create('launcher'), 'debug')
2229

2330
mockSpawn = sinon.spy(function (cmd, args) {
2431
const process = new EventEmitter()
@@ -74,7 +81,7 @@ describe('launchers/process.js', () => {
7481
})
7582

7683
describe('with RetryLauncher', () => {
77-
it('should handle spawn ENOENT error and not even retry', (done) => {
84+
function assertSpawnError ({ errorCode, emitExit, expectedError }, done) {
7885
ProcessLauncher.call(launcher, mockSpawn, mockTempDir)
7986
RetryLauncher.call(launcher, 2)
8087
launcher._getCommand = () => BROWSER_PATH
@@ -83,35 +90,56 @@ describe('launchers/process.js', () => {
8390
emitter.on('browser_process_failure', failureSpy)
8491

8592
launcher.start('http://host:9876/')
86-
mockSpawn._processes[0].emit('error', { code: 'ENOENT' })
87-
mockSpawn._processes[0].emit('exit', 1)
93+
mockSpawn._processes[0].emit('error', { code: errorCode })
94+
if (emitExit) {
95+
mockSpawn._processes[0].emit('exit', 1)
96+
}
8897
mockTempDir.remove.callArg(1)
8998

9099
_.defer(() => {
91100
expect(launcher.state).to.equal(launcher.STATE_FINISHED)
92101
expect(failureSpy).to.have.been.called
102+
expect(logDebugSpy).to.have.been.callCount(5)
103+
expect(logDebugSpy.getCall(0)).to.have.been.calledWithExactly('null -> BEING_CAPTURED')
104+
expect(logDebugSpy.getCall(1)).to.have.been.calledWithExactly(`${BROWSER_PATH} http://host:9876/?id=fake-id`)
105+
expect(logDebugSpy.getCall(2)).to.have.been.calledWithExactly('Process fake-name exited with code -1 and signal null')
106+
expect(logDebugSpy.getCall(3)).to.have.been.calledWithExactly('fake-name failed (cannot start). Not restarting.')
107+
expect(logDebugSpy.getCall(4)).to.have.been.calledWithExactly('BEING_CAPTURED -> FINISHED')
108+
expect(logErrorSpy).to.have.been.calledWith(expectedError)
93109
done()
94110
})
111+
}
112+
113+
it('should handle spawn ENOENT error and not even retry', (done) => {
114+
assertSpawnError({
115+
errorCode: 'ENOENT',
116+
emitExit: true,
117+
expectedError: `Cannot start fake-name\n\tCan not find the binary ${BROWSER_PATH}\n\tPlease set env variable fake-ENV-CMD`
118+
}, done)
95119
})
96120

97121
it('should handle spawn EACCES error and not even retry', (done) => {
98-
ProcessLauncher.call(launcher, mockSpawn, mockTempDir)
99-
RetryLauncher.call(launcher, 2)
100-
launcher._getCommand = () => BROWSER_PATH
101-
102-
const failureSpy = sinon.spy()
103-
emitter.on('browser_process_failure', failureSpy)
122+
assertSpawnError({
123+
errorCode: 'EACCES',
124+
emitExit: true,
125+
expectedError: `Cannot start fake-name\n\tPermission denied accessing the binary ${BROWSER_PATH}\n\tMaybe it's a directory?`
126+
}, done)
127+
})
104128

105-
launcher.start('http://host:9876/')
106-
mockSpawn._processes[0].emit('error', { code: 'EACCES' })
107-
mockSpawn._processes[0].emit('exit', 1)
108-
mockTempDir.remove.callArg(1)
129+
it('should handle spawn ENOENT error and report the error when exit event is not emitted', (done) => {
130+
assertSpawnError({
131+
errorCode: 'ENOENT',
132+
emitExit: false,
133+
expectedError: `Cannot start fake-name\n\tCan not find the binary ${BROWSER_PATH}\n\tPlease set env variable fake-ENV-CMD`
134+
}, done)
135+
})
109136

110-
_.defer(() => {
111-
expect(launcher.state).to.equal(launcher.STATE_FINISHED)
112-
expect(failureSpy).to.have.been.called
113-
done()
114-
})
137+
it('should handle spawn EACCES error and report the error when exit event is not emitted', (done) => {
138+
assertSpawnError({
139+
errorCode: 'EACCES',
140+
emitExit: false,
141+
expectedError: `Cannot start fake-name\n\tPermission denied accessing the binary ${BROWSER_PATH}\n\tMaybe it's a directory?`
142+
}, done)
115143
})
116144
})
117145

0 commit comments

Comments
 (0)
Please sign in to comment.