ObservableProcess

ObservableProcess enhances the slightly too low-level
Node.JS ChildProcess model with
functionality to observe the behavior of processes more conveniently. In
particular:
- easy access to the accumulated content of
stdout
and
stderr
- await content in
stdout and stderr
- combines
stdout and stderr into a new output stream
- await the process end
- easier access to the process exit code
- signals whether the process ended naturally or was manually terminated
ObservableProcess is for short-lived processes, for example when testing the
terminal output of applications. Since ObservableProcess stores all output from
the child process in memory, executing long-running processes that produce lots
of output through ObservableProcess will cause high memory consumption.
Setup
Add this library to your code base:
$ npm install observable-process
Load this library into your JavaScript code:
const observableProcess = require("observable-process")
or
import * as observableProcess from "observable-process"
Starting processes
The best way to provide the command to start is in the form of an argv array:
const observable = observableProcess.start(["node", "server.js"])
You can also provide the full command line to run as a string:
const observable = observableProcess.start("node server.js")
By default, the process runs in the current directory. To set a different
working directory for the subprocess:
const observable = observableProcess.start("node server.js", { cwd: "~/tmp" })
You can provide custom environment variables for the process:
const observable = observableProcess.start("node server.js", {
env: {
foo: "bar",
PATH: process.env.PATH,
},
})
Without an env parameter, ObservableProcess uses the environment variables
from the parent process.
Reading output
The stdout and stderr variables of an ObservableProcess behave like normal
readable streams:
observable.stdout.on("data", function () {
})
They also provide extra functionality to access and search their aggregated
content. To get all content from STDOUT as a string:
const text = observable.stdout.fullText()
To wait for text to appear in STDOUT:
const match = await observable.stdout.waitForText("server is online")
To wait for a regular expression on STDOUT:
const match = await observable.stdout.waitForRegex(/running at port \d+/)
Comparable functionality is available for stderr. ObservableProcess also
provides a new output stream with the combined content of STDOUT and STDERR:
observable.output.on("data", function (data) {
})
const text = observable.output.fullText()
await observable.output.waitForText("server is online")
const port = await observable.output.waitForRegex(/running at port \d+./)
You also get a copy of the process output after it ended (see below).
Sending input to the process
ObservableProcess exposes the
stdin
stream of its underlying
ChildProcess:
observable.stdin.write("my input\n")
observable.stdin.end()
Get the process id
observable.pid()
Stop the process
Wait until the process ends naturally:
const result = await observable.waitForEnd()
assert.equal(result, {
status: "finished",
exitCode: 0,
stdText: "... content from STDOUT ...",
errText: "... content from STDERR ...",
combinedText: "... content from both STDOUT and STDERR ...",
})
Manually stop the process:
const result = await observable.kill()
assert.equal(result, {
status: "killed",
stdText: "... content from STDOUT ...",
errText: "... content from STDERR ...",
combinedText: "... content from both STDOUT and STDERR ...",
})
Related libraries
- nexpect: Allows to define expectations
on command output, and send it input, but doesn't allow to add more listeners
to existing long-running processes, which makes declarative testing hard.
Development
If you want to hack on ObservableProcess:
- run all tests:
make test
- run automated code repair:
make fix
- see all make commands:
make help
To deploy a new version:
- update the version in
package.json and commit to master
- run
npm publish