Skip to content

Commit

Permalink
feat(runProcess): Allow waiting for process to exit
Browse files Browse the repository at this point in the history
  • Loading branch information
vinsonchuong committed Sep 6, 2020
1 parent 1f6dd73 commit ae5e956
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 34 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ Returns an object with the following members:
substring or regular expression to be output, for up to a given timeout.
- `childProcess`: The underlying instance of `ChildProcess`

The returned object is also a `Promise` that when the process exits, resolves
with an object containing `output` and the exit `code`.

```js
import test from 'ava'
import {runProcess} from 'ava-patterns'
Expand Down
77 changes: 43 additions & 34 deletions run-process/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,62 +12,71 @@ export default function (
detached: true
})
t.teardown(() => {
process.kill(-child.pid)
try {
process.kill(-child.pid)
} catch (error) {
if (error.code !== 'ESRCH') {
throw error
}
}
})

const program = new Promise((resolve) => {
child.on('close', (code) => {
resolve({code, output: program.output})
})
})
program.childProcess = child
program.output = ''
program.outputStream = new PassThrough()

let output = ''
const outputStream = new PassThrough()
program.outputStream.setEncoding('utf8')
child.stdout.setEncoding('utf8')
child.stderr.setEncoding('utf8')
Promise.all([
(async () => {
for await (const data of child.stdout) {
output += data
outputStream.write(data)
program.output += data
program.outputStream.write(data)
}
})(),
(async () => {
for await (const data of child.stderr) {
output += data
outputStream.write(data)
program.output += data
program.outputStream.write(data)
}
})()
]).then(
() => {
outputStream.end()
program.outputStream.end()
},
(error) => {
outputStream.emit('error', error)
program.outputStream.emit('error', error)
}
)

return {
childProcess: child,
get output() {
return output
},
outputStream,
async waitForOutput(pattern, timeout = 1000) {
const match =
typeof pattern === 'string'
? (string) => string.includes(pattern)
: (string) => Boolean(string.match(pattern))
program.waitForOutput = async (pattern, timeout = 1000) => {
const match =
typeof pattern === 'string'
? (string) => string.includes(pattern)
: (string) => Boolean(string.match(pattern))

await Promise.race([
(async () => {
await wait(timeout)
throw new Error('Timeout exceeded without seeing expected output.')
})(),
(async () => {
for await (const data of outputStream) {
if (match(data)) {
return
}
await Promise.race([
(async () => {
await wait(timeout)
throw new Error('Timeout exceeded without seeing expected output.')
})(),
(async () => {
for await (const data of program.outputStream) {
if (match(data)) {
return
}
}

throw new Error('Process ended without emitting expected output.')
})()
])
}
throw new Error('Process ended without emitting expected output.')
})()
])
}

return program
}
9 changes: 9 additions & 0 deletions run-process/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,12 @@ test('setting the working directory and environment variables', async (t) => {
await program.waitForOutput('Done!')
t.is(program.output, '/tmp\nFOO bar\nDone!\n')
})

test('running a simple command that terminates', async (t) => {
const {output, code} = await runProcess(t, {
command: ['ls', '/']
})

t.true(output.includes('tmp'))
t.is(code, 0)
})

0 comments on commit ae5e956

Please sign in to comment.