Skip to content

Commit b15483a

Browse files
jamestalmagesindresorhus
authored andcommitted
add tests (#48)
1 parent ac7a103 commit b15483a

6 files changed

Lines changed: 153 additions & 16 deletions

File tree

.travis.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ node_js:
44
- '5'
55
- '4'
66
- '0.12'
7-
after_success: npm run coveralls
7+
after_script:
8+
- 'cat coverage/lcov.info | ./node_modules/.bin/coveralls'

fixtures/fail

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env node
2+
'use strict';
3+
4+
process.exit(2);

fixtures/forever

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env node
2+
'use strict';
3+
4+
setTimeout(function () {}, 20000);

index.js

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ function processDone(spawned) {
135135
}
136136

137137
module.exports = function (cmd, args, opts) {
138+
var joinedCmd = cmd;
139+
140+
if (Array.isArray(args) && args.length) {
141+
joinedCmd += ' ' + args.join(' ');
142+
}
143+
138144
var parsed = handleArgs(cmd, args, opts);
139145
var encoding = parsed.opts.encoding;
140146
var maxBuffer = parsed.opts.maxBuffer;
@@ -154,26 +160,22 @@ module.exports = function (cmd, args, opts) {
154160
var signal = result.signal;
155161

156162
if (err || code !== 0 || signal !== null) {
157-
var joinedCmd = cmd;
158-
159-
if (Array.isArray(args) && args.length) {
160-
joinedCmd += ' ' + args.join(' ');
161-
}
162-
163163
if (!err) {
164164
err = new Error('Command failed: ' + joinedCmd + '\n' + stderr + stdout);
165165

166-
// TODO: missing some timeout logic for killed
167-
// https://github.com/nodejs/node/blob/master/lib/child_process.js#L203
168-
// err.killed = spawned.killed || killed;
169-
err.killed = spawned.killed;
170-
171166
err.code = code < 0 ? errname(code) : code;
172167
}
173168

169+
// TODO: missing some timeout logic for killed
170+
// https://github.com/nodejs/node/blob/master/lib/child_process.js#L203
171+
// err.killed = spawned.killed || killed;
172+
err.killed = err.killed || spawned.killed;
173+
174174
err.stdout = stdout;
175175
err.stderr = stderr;
176176
err.failed = true;
177+
err.signal = signal || null;
178+
err.cmd = joinedCmd;
177179

178180
if (!parsed.opts.reject) {
179181
return err;
@@ -186,7 +188,10 @@ module.exports = function (cmd, args, opts) {
186188
stdout: handleOutput(parsed.opts, stdout),
187189
stderr: handleOutput(parsed.opts, stderr),
188190
code: 0,
189-
failed: false
191+
failed: false,
192+
killed: false,
193+
signal: null,
194+
cmd: joinedCmd
190195
};
191196
});
192197

package.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
"node": ">=0.12"
2121
},
2222
"scripts": {
23-
"test": "xo && nyc ava",
24-
"coveralls": "nyc report --reporter=text-lcov | coveralls"
23+
"test": "xo && nyc ava"
2524
},
2625
"files": [
2726
"index.js",
@@ -59,5 +58,16 @@
5958
"coveralls": "^2.11.9",
6059
"nyc": "^6.4.0",
6160
"xo": "*"
61+
},
62+
"nyc": {
63+
"reporter": [
64+
"text",
65+
"lcov"
66+
],
67+
"exclude": [
68+
"**/fixtures/**",
69+
"**/test.js",
70+
"**/test/**"
71+
]
6272
}
6373
}

test.js

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import path from 'path';
22
import stream from 'stream';
3+
import childProcess from 'child_process';
34
import test from 'ava';
45
import getStream from 'get-stream';
56
import m from './';
@@ -33,9 +34,10 @@ test('stdout/stderr available on errors', async t => {
3334
t.is(typeof err.stderr, 'string');
3435
});
3536

36-
test('include stdout in errors for improved debugging', async t => {
37+
test('include stdout and stderr in errors for improved debugging', async t => {
3738
const err = await t.throws(m('fixtures/error-message.js'));
3839
t.regex(err.message, /stdout/);
40+
t.regex(err.message, /stderr/);
3941
});
4042

4143
test('execa.shell()', async t => {
@@ -163,3 +165,114 @@ test(`use relative path with '..' chars`, async t => {
163165
const {stdout} = await m(pathViaParentDir, ['foo']);
164166
t.is(stdout, 'foo');
165167
});
168+
169+
test('err.killed is true if process was killed directly', async t => {
170+
const cp = m('forever');
171+
172+
setTimeout(function () {
173+
cp.kill();
174+
}, 100);
175+
176+
const err = await t.throws(cp);
177+
178+
t.true(err.killed);
179+
});
180+
181+
// TODO: Should this really be the case, or should we improve on child_process?
182+
test('err.killed is false if process was killed indirectly', async t => {
183+
const cp = m('forever');
184+
185+
setTimeout(function () {
186+
process.kill(cp.pid, 'SIGINT');
187+
}, 100);
188+
189+
const err = await t.throws(cp);
190+
191+
t.false(err.killed);
192+
});
193+
194+
if (process.platform === 'darwin') {
195+
test.cb('sanity check: child_process.exec also has killed.false if killed indirectly', t => {
196+
const cp = childProcess.exec('forever', err => {
197+
t.truthy(err);
198+
t.false(err.killed);
199+
t.end();
200+
});
201+
202+
setTimeout(function () {
203+
process.kill(cp.pid, 'SIGINT');
204+
}, 100);
205+
});
206+
}
207+
208+
if (process.platform !== 'win32') {
209+
test('err.signal is SIGINT', async t => {
210+
const cp = m('forever');
211+
212+
setTimeout(function () {
213+
process.kill(cp.pid, 'SIGINT');
214+
}, 100);
215+
216+
const err = await t.throws(cp);
217+
218+
t.is(err.signal, 'SIGINT');
219+
});
220+
221+
test('err.signal is SIGTERM', async t => {
222+
const cp = m('forever');
223+
224+
setTimeout(function () {
225+
process.kill(cp.pid, 'SIGTERM');
226+
}, 100);
227+
228+
const err = await t.throws(cp);
229+
230+
t.is(err.signal, 'SIGTERM');
231+
});
232+
}
233+
234+
test('result.signal is null for successful execution', async t => {
235+
t.is((await m('noop')).signal, null);
236+
});
237+
238+
test('result.signal is null if process failed, but was not killed', async t => {
239+
const err = await t.throws(m('exit', [2]));
240+
t.is(err.signal, null);
241+
});
242+
243+
async function code(t, num) {
244+
const err = await t.throws(m('exit', [`${num}`]));
245+
246+
t.is(err.code, num);
247+
}
248+
249+
test('err.code is 2', code, 2);
250+
test('err.code is 3', code, 3);
251+
test('err.code is 4', code, 4);
252+
253+
async function errorMessage(t, expected, ...args) {
254+
const err = await t.throws(m('exit', args));
255+
256+
t.regex(err.message, expected);
257+
}
258+
259+
errorMessage.title = (message, expected) => `err.message matches: ${expected}`;
260+
261+
test(errorMessage, /Command failed: exit 2 foo bar/, 2, 'foo', 'bar');
262+
test(errorMessage, /Command failed: exit 3 baz quz/, 3, 'baz', 'quz');
263+
264+
async function cmd(t, expected, ...args) {
265+
const err = await t.throws(m('fail', args));
266+
267+
t.is(err.cmd, `fail${expected}`);
268+
269+
const result = await m('noop', args);
270+
271+
t.is(result.cmd, `noop${expected}`);
272+
}
273+
274+
cmd.title = (message, expected) => `cmd is: ${JSON.stringify(expected)}`;
275+
276+
test(cmd, ' foo bar', 'foo', 'bar');
277+
test(cmd, ' baz quz', 'baz', 'quz');
278+
test(cmd, '');

0 commit comments

Comments
 (0)