blob: f9598799dc411e67fe5b974a984c1bee60d6a1a1 [file] [log] [blame]
'use strict'
const Base = require('./base.js')
const assert = require('assert')
const util = require('util')
const ownOr = require('own-or')
const path = require('path')
const cleanYamlObject = require('./clean-yaml-object.js')
const cp = require('child_process')
class Spawn extends Base {
constructor (options) {
options = options || {}
super(options)
this.command = options.command
if (!this.command)
throw new TypeError('no command provided')
this.args = options.args
// stdout must be a pipe
if (options.stdio) {
if (typeof options.stdio === 'string')
this.stdio = [ options.stdio, 'pipe', options.stdio ]
else
this.stdio = options.stdio.slice(0)
} else
this.stdio = [ 0, 'pipe', 2 ]
this.stdio[1] = 'pipe'
options.stdio = this.stdio
const env = options.env || process.env
this.env = Object.assign({}, env)
this.env.TAP = '1'
if (this.bail)
this.env.TAP_BAIL = '1'
this.cwd = ownOr(options, 'cwd', process.cwd())
options.cwd = this.cwd
if (!this.name) {
if (this.command === process.execPath) {
this.name = path.basename(process.execPath) + ' ' +
this.args.map(a =>
a.indexOf(this.cwd) === 0 ?
'./' + a.substr(this.cwd.length + 1).replace(/\\/g, '/')
: a).join(' ')
} else {
this.name = this.command + ' ' + this.args.join(' ')
}
}
this.proc = null
}
endAll () {
if (this.proc)
this.proc.kill('SIGKILL')
this.parser.abort('test unfinished')
this.callCb()
}
main (cb) {
this.cb = cb
this.setTimeout(this.options.timeout)
const options = Object.assign({
cwd: this.cwd,
env: this.env,
stdio: this.stdio
}, this.options)
try {
const proc = this.proc = cp.spawn(this.command, this.args, options)
proc.stdout.pipe(this.parser)
proc.on('close', (code, signal) => this.onprocclose(code, signal))
proc.on('error', er => this.threw(er))
this.emit('process', proc)
if (this.parent)
this.parent.emit('spawn', this)
} catch (er) {
this.threw(er)
}
}
callCb () {
if (this.cb)
this.cb()
this.cb = null
}
threw (er, extra, proxy) {
extra = Base.prototype.threw.call(this, er, extra, proxy)
extra = cleanYamlObject(extra)
// unhook entirely
this.parser.abort(er.message, extra)
if (this.proc) {
this.proc.stdout.removeAllListeners('data')
this.proc.stdout.removeAllListeners('end')
this.proc.removeAllListeners('close')
this.proc.kill('SIGKILL')
}
this.callCb()
}
onprocclose (code, signal) {
this.debug('SPAWN close %j %s', code, signal)
this.options.exitCode = code
if (signal)
this.options.signal = signal
// spawn closing with no tests is treated as a skip.
if (this.results.plan && this.results.plan.skipAll && !code && !signal)
this.options.skip = this.results.plan.skipReason || true
if (code || signal) {
this.results.ok = false
this.parser.ok = false
}
return this.callCb()
}
timeout (extra) {
if (this.proc) {
this.proc.kill('SIGTERM')
const t = setTimeout(() => {
if (!this.options.signal && this.options.exitCode === undefined) {
Base.prototype.timeout.call(this, extra)
this.proc.kill('SIGKILL')
}
}, 1000)
/* istanbul ignore else */
if (t.unref)
t.unref()
}
}
}
module.exports = Spawn