1 | 'use strict'
|
2 | const Base = require('./base.js')
|
3 |
|
4 | const assert = require('assert')
|
5 | const util = require('util')
|
6 | const ownOr = require('own-or')
|
7 | const path = require('path')
|
8 | const cleanYamlObject = require('./clean-yaml-object.js')
|
9 | const cp = require('child_process')
|
10 |
|
11 | class Spawn extends Base {
|
12 | constructor (options) {
|
13 |
|
14 | options = options || {}
|
15 | const cwd = ownOr(options, 'cwd', process.cwd())
|
16 | const command = options.command
|
17 | if (!command)
|
18 | throw new TypeError('no command provided')
|
19 | const args = ownOr(options, 'args', [])
|
20 |
|
21 | options.name = options.name || Spawn.procName(cwd, command, args)
|
22 |
|
23 | super(options)
|
24 |
|
25 | this.command = options.command
|
26 |
|
27 | this.args = options.args
|
28 |
|
29 | if (options.stdio) {
|
30 | if (typeof options.stdio === 'string')
|
31 | this.stdio = [ options.stdio, 'pipe', options.stdio ]
|
32 | else
|
33 | this.stdio = options.stdio.slice(0)
|
34 | } else
|
35 | this.stdio = [ 0, 'pipe', 2 ]
|
36 |
|
37 | this.stdio[1] = 'pipe'
|
38 | options.stdio = this.stdio
|
39 |
|
40 | if (!options.env)
|
41 | options.env = process.env
|
42 | this.env = {
|
43 | ...(options.env),
|
44 | TAP_CHILD_ID: options.childId
|
45 | || options.env.TAP_CHILD_ID
|
46 | || 0,
|
47 | TAP: '1',
|
48 | TAP_BAIL: this.bail ? '1' : '0',
|
49 | }
|
50 |
|
51 | this.options.env = Object.keys(this.options.env).reduce((env, k) => {
|
52 | if (process.env[k] !== this.options.env[k])
|
53 | env[k] = this.options.env[k]
|
54 | return env
|
55 | }, {})
|
56 |
|
57 | this.cwd = cwd
|
58 | options.cwd = this.cwd
|
59 |
|
60 | this.processDB = ownOr(options, 'processDB', null) || {
|
61 | spawn: (name, ...rest) => cp.spawn(...rest)
|
62 | }
|
63 | delete options.processDB
|
64 |
|
65 | this.proc = null
|
66 | }
|
67 |
|
68 | endAll () {
|
69 | if (this.proc)
|
70 | this.proc.kill('SIGKILL')
|
71 | this.parser.abort('test unfinished')
|
72 | this.callCb()
|
73 | }
|
74 |
|
75 | main (cb) {
|
76 | this.cb = cb
|
77 | this.setTimeout(this.options.timeout)
|
78 | this.parser.on('comment', c => {
|
79 | const tomatch = c.match(/# timeout=([0-9]+)\n$/)
|
80 | if (tomatch)
|
81 | this.setTimeout(+tomatch[1])
|
82 | })
|
83 | const options = {
|
84 | ...(this.options),
|
85 | cwd: this.cwd,
|
86 | env: this.env,
|
87 | stdio: this.stdio,
|
88 | }
|
89 | try {
|
90 | this.emit('preprocess', options)
|
91 | const proc = this.proc =
|
92 | this.processDB.spawn(this.name, this.command, this.args, options)
|
93 |
|
94 | proc.stdout.pipe(this.parser)
|
95 | proc.on('close', (code, signal) => this.onprocclose(code, signal))
|
96 | proc.on('error', er => this.threw(er))
|
97 | this.emit('process', proc)
|
98 | if (this.parent)
|
99 | this.parent.emit('spawn', this)
|
100 | } catch (er) {
|
101 | er.tapCaught = 'spawn'
|
102 | this.threw(er)
|
103 | }
|
104 | }
|
105 |
|
106 | callCb () {
|
107 | if (this.cb)
|
108 | this.cb()
|
109 | this.cb = null
|
110 | }
|
111 |
|
112 | threw (er, extra, proxy) {
|
113 | extra = Base.prototype.threw.call(this, er, extra, proxy)
|
114 | extra = cleanYamlObject(extra)
|
115 |
|
116 | this.parser.abort(er.message, extra)
|
117 | if (this.proc) {
|
118 | this.proc.stdout.removeAllListeners('data')
|
119 | this.proc.stdout.removeAllListeners('end')
|
120 | this.proc.removeAllListeners('close')
|
121 | this.proc.kill('SIGKILL')
|
122 | }
|
123 | this.callCb()
|
124 | }
|
125 |
|
126 | onprocclose (code, signal) {
|
127 | this.debug('SPAWN close %j %s', code, signal)
|
128 | this.options.exitCode = code
|
129 | if (signal)
|
130 | this.options.signal = signal
|
131 |
|
132 |
|
133 | if (this.results && this.results.plan && this.results.plan.skipAll && !code && !signal)
|
134 | this.options.skip = this.results.plan.skipReason || true
|
135 |
|
136 | if (code || signal) {
|
137 | this.results.ok = false
|
138 | this.parser.ok = false
|
139 | }
|
140 | return this.callCb()
|
141 | }
|
142 |
|
143 | timeout (extra) {
|
144 | if (this.proc) {
|
145 | this.proc.kill('SIGTERM')
|
146 | const t = setTimeout(() => {
|
147 | if (!this.options.signal && this.options.exitCode === undefined) {
|
148 | Base.prototype.timeout.call(this, extra)
|
149 | this.proc.kill('SIGKILL')
|
150 | }
|
151 | }, 1000)
|
152 |
|
153 | if (t.unref)
|
154 | t.unref()
|
155 | }
|
156 | }
|
157 | }
|
158 |
|
159 | Spawn.procName = (cwd, command, args) => (
|
160 | (command === process.execPath)
|
161 | ? path.basename(process.execPath) + ' ' + args.map(a =>
|
162 | a.indexOf(cwd) === 0 ?
|
163 | './' + a.substr(cwd.length + 1).replace(/\\/g, '/')
|
164 | : a).join(' ').trim()
|
165 | : command + ' ' + args.join(' ')
|
166 | ).replace(/\\/g, '/')
|
167 |
|
168 | module.exports = Spawn
|