1 var path = require('path')
2 var log = require('../logger').create('launcher')
5 var ProcessLauncher = function (spawn, tempDir, timer) {
10 this._tempDir = tempDir.getPath('/karma-' + this.id.toString())
12 this.on('start', function (url) {
13 tempDir.create(self._tempDir)
17 this.on('kill', function (done) {
19 return process.nextTick(done)
24 self._killTimer = timer.setTimeout(self._onKillTimeout, killTimeout)
27 this._start = function (url) {
28 self._execCommand(self._getCommand(), self._getOptions(url))
31 this._getCommand = function () {
32 return env[self.ENV_CMD] || self.DEFAULT_CMD[process.platform]
35 this._getOptions = function (url) {
39 // Normalize the command, remove quotes (spawn does not like them).
40 this._normalizeCommand = function (cmd) {
41 if (cmd.charAt(0) === cmd.charAt(cmd.length - 1) && '\'`"'.indexOf(cmd.charAt(0)) !== -1) {
42 cmd = cmd.substring(1, cmd.length - 1)
43 log.warn('The path should not be quoted.\n Normalized the path to %s', cmd)
46 return path.normalize(cmd)
49 this._execCommand = function (cmd, args) {
51 log.error('No binary for %s browser on your platform.\n ' +
52 'Please, set "%s" env variable.', self.name, self.ENV_CMD)
57 return self._clearTempDirAndReportDone('no binary')
60 cmd = this._normalizeCommand(cmd)
62 log.debug(cmd + ' ' + args.join(' '))
63 self._process = spawn(cmd, args)
67 self._process.on('close', function (code) {
68 self._onProcessExit(code, errorOutput)
71 self._process.on('error', function (err) {
72 if (err.code === 'ENOENT') {
74 errorOutput = 'Can not find the binary ' + cmd + '\n\t' +
75 'Please set env variable ' + self.ENV_CMD
77 errorOutput += err.toString()
81 // Node 0.8 does not emit the error
82 if (process.versions.node.indexOf('0.8') === 0) {
83 self._process.stderr.on('data', function (data) {
84 var msg = data.toString()
86 if (msg.indexOf('No such file or directory') !== -1) {
88 errorOutput = 'Can not find the binary ' + cmd + '\n\t' +
89 'Please set env variable ' + self.ENV_CMD
97 this._onProcessExit = function (code, errorOutput) {
98 log.debug('Process %s exited with code %d', self.name, code)
102 if (self.state === self.STATE_BEING_CAPTURED) {
103 log.error('Cannot start %s\n\t%s', self.name, errorOutput)
104 error = 'cannot start'
107 if (self.state === self.STATE_CAPTURED) {
108 log.error('%s crashed.\n\t%s', self.name, errorOutput)
113 if (self._killTimer) {
114 timer.clearTimeout(self._killTimer)
115 self._killTimer = null
117 self._clearTempDirAndReportDone(error)
120 this._clearTempDirAndReportDone = function (error) {
121 tempDir.remove(self._tempDir, function () {
123 if (onExitCallback) {
125 onExitCallback = null
130 this._onKillTimeout = function () {
131 if (self.state !== self.STATE_BEING_KILLED) {
135 log.warn('%s was not killed in %d ms, sending SIGKILL.', self.name, killTimeout)
136 self._process.kill('SIGKILL')
138 // NOTE: https://github.com/karma-runner/karma/pull/1184
139 // NOTE: SIGKILL is just a signal. Processes should never ignore it, but they can.
140 // If a process gets into a state where it doesn't respond in a reasonable amout of time
141 // Karma should warn, and continue as though the kill succeeded.
142 // This a certainly suboptimal, but it is better than having the test harness hang waiting
143 // for a zombie child process to exit.
144 self._killTimer = timer.setTimeout(function () {
145 log.warn('%s was not killed by SIGKILL in %d ms, continuing.', self.name, killTimeout)
146 self._onProcessExit(-1, '')
151 ProcessLauncher.decoratorFactory = function (timer) {
152 return function (launcher) {
153 ProcessLauncher.call(launcher, require('child_process').spawn, require('../temp_dir'), timer)
157 module.exports = ProcessLauncher