source: trip-planner-front/node_modules/karma/lib/launchers/process.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 5.3 KB
Line 
1const path = require('path')
2const log = require('../logger').create('launcher')
3const env = process.env
4
5function ProcessLauncher (spawn, tempDir, timer, processKillTimeout) {
6 const self = this
7 let onExitCallback
8 const killTimeout = processKillTimeout || 2000
9 // Will hold output from the spawned child process
10 const streamedOutputs = {
11 stdout: '',
12 stderr: ''
13 }
14
15 this._tempDir = tempDir.getPath(`/karma-${this.id.toString()}`)
16
17 this.on('start', function (url) {
18 tempDir.create(self._tempDir)
19 self._start(url)
20 })
21
22 this.on('kill', function (done) {
23 if (!self._process) {
24 return process.nextTick(done)
25 }
26
27 onExitCallback = done
28 self._process.kill()
29 self._killTimer = timer.setTimeout(self._onKillTimeout, killTimeout)
30 })
31
32 this._start = function (url) {
33 self._execCommand(self._getCommand(), self._getOptions(url))
34 }
35
36 this._getCommand = function () {
37 return env[self.ENV_CMD] || self.DEFAULT_CMD[process.platform]
38 }
39
40 this._getOptions = function (url) {
41 return [url]
42 }
43
44 // Normalize the command, remove quotes (spawn does not like them).
45 this._normalizeCommand = function (cmd) {
46 if (cmd.charAt(0) === cmd.charAt(cmd.length - 1) && '\'`"'.includes(cmd.charAt(0))) {
47 cmd = cmd.substring(1, cmd.length - 1)
48 log.warn(`The path should not be quoted.\n Normalized the path to ${cmd}`)
49 }
50
51 return path.normalize(cmd)
52 }
53
54 this._onStdout = function (data) {
55 streamedOutputs.stdout += data
56 }
57
58 this._onStderr = function (data) {
59 streamedOutputs.stderr += data
60 }
61
62 this._execCommand = function (cmd, args) {
63 if (!cmd) {
64 log.error(`No binary for ${self.name} browser on your platform.\n Please, set "${self.ENV_CMD}" env variable.`)
65
66 // disable restarting
67 self._retryLimit = -1
68
69 return self._clearTempDirAndReportDone('no binary')
70 }
71
72 cmd = this._normalizeCommand(cmd)
73
74 log.debug(cmd + ' ' + args.join(' '))
75 self._process = spawn(cmd, args)
76 let errorOutput = ''
77
78 self._process.stdout.on('data', self._onStdout)
79
80 self._process.stderr.on('data', self._onStderr)
81
82 self._process.on('exit', function (code, signal) {
83 self._onProcessExit(code, signal, errorOutput)
84 })
85
86 self._process.on('error', function (err) {
87 if (err.code === 'ENOENT') {
88 self._retryLimit = -1
89 errorOutput = `Can not find the binary ${cmd}\n\tPlease set env variable ${self.ENV_CMD}`
90 } else if (err.code === 'EACCES') {
91 self._retryLimit = -1
92 errorOutput = `Permission denied accessing the binary ${cmd}\n\tMaybe it's a directory?`
93 } else {
94 errorOutput += err.toString()
95 }
96 self._onProcessExit(-1, null, errorOutput)
97 })
98
99 self._process.stderr.on('data', function (errBuff) {
100 errorOutput += errBuff.toString()
101 })
102 }
103
104 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 }
109 log.debug(`Process ${self.name} exited with code ${code} and signal ${signal}`)
110
111 let error = null
112
113 if (self.state === self.STATE_BEING_CAPTURED) {
114 log.error(`Cannot start ${self.name}\n\t${errorOutput}`)
115 error = 'cannot start'
116 }
117
118 if (self.state === self.STATE_CAPTURED) {
119 log.error(`${self.name} crashed.\n\t${errorOutput}`)
120 error = 'crashed'
121 }
122
123 if (error) {
124 log.error(`${self.name} stdout: ${streamedOutputs.stdout}`)
125 log.error(`${self.name} stderr: ${streamedOutputs.stderr}`)
126 }
127
128 self._process = null
129 streamedOutputs.stdout = ''
130 streamedOutputs.stderr = ''
131 if (self._killTimer) {
132 timer.clearTimeout(self._killTimer)
133 self._killTimer = null
134 }
135 self._clearTempDirAndReportDone(error)
136 }
137
138 this._clearTempDirAndReportDone = function (error) {
139 tempDir.remove(self._tempDir, function () {
140 self._done(error)
141 if (onExitCallback) {
142 onExitCallback()
143 onExitCallback = null
144 }
145 })
146 }
147
148 this._onKillTimeout = function () {
149 if (self.state !== self.STATE_BEING_KILLED && self.state !== self.STATE_BEING_FORCE_KILLED) {
150 return
151 }
152
153 log.warn(`${self.name} was not killed in ${killTimeout} ms, sending SIGKILL.`)
154 self._process.kill('SIGKILL')
155
156 // NOTE: https://github.com/karma-runner/karma/pull/1184
157 // NOTE: SIGKILL is just a signal. Processes should never ignore it, but they can.
158 // If a process gets into a state where it doesn't respond in a reasonable amount of time
159 // Karma should warn, and continue as though the kill succeeded.
160 // This a certainly suboptimal, but it is better than having the test harness hang waiting
161 // for a zombie child process to exit.
162 self._killTimer = timer.setTimeout(function () {
163 log.warn(`${self.name} was not killed by SIGKILL in ${killTimeout} ms, continuing.`)
164 self._onProcessExit(-1, null, '')
165 }, killTimeout)
166 }
167}
168
169ProcessLauncher.decoratorFactory = function (timer) {
170 return function (launcher, processKillTimeout) {
171 const spawn = require('child_process').spawn
172
173 function spawnWithoutOutput () {
174 const proc = spawn.apply(null, arguments)
175 proc.stdout.resume()
176 proc.stderr.resume()
177
178 return proc
179 }
180
181 ProcessLauncher.call(launcher, spawnWithoutOutput, require('../temp_dir'), timer, processKillTimeout)
182 }
183}
184
185module.exports = ProcessLauncher
Note: See TracBrowser for help on using the repository browser.