I was running some tests recently using Mocha and I wanted to re-run the tests when files changed. Typically this kind of functionality is built into test runners, but a) I'm not that familiar with Mocha (meh, it's just a tool) and b) I've got nodemon (which I wrote) 🙌.
The thing is: when I ran a failing test with nodemon and mocha, the way mocha exits (in this particular case) makes nodemon think that the command totally failed with process failed, unhandled exit code (2)
. Cleaning the exit is a good trick to know.
UK EVENTAttend ffconf.org 2024
The conference for people who are passionate about the web. 8 amazing speakers with real human interaction and content you can't just read in a blog post or watch on a tiktok!
£249+VAT - reserve your place today
Exit codes
When a process runs (in a unix-based system…I'm unsure how much applies to Windows), it will exit with a code. There's only a handful of standardised exit codes, but the ones of most interest are:
exit 0
→ successexit 1
→ failure
If you try this in your terminal, the terminal will just close (since you're running exit
). But to test this, you can run the following:
$ sh -c "exit 0"
$ echo $?
0
$ sh -c "exit 1"
$ echo $?
1
Running sh -c "…"
runs the string as a bash command and returns the result. Then the shell value $?
is the numerical status of the exit code from the last executed command.
Changing exit codes
When I ran mocha, it was returning an exit code of 2. This is a weirdness of mocha that (misuses) the exit status reporting the number of failing tests.
The "fix" is only a few characters though. When I run mocha inside of nodemon, I use an bash or statement that reads "if this fails, fail with an exit 1":
$ nodemon --exec "mocha bad.test.js || exit 1"
Now if mocha fails with exit 2
it'll exit nodemon's exec with an exit 1
which nodemon sees as a failure.
Wait, that's not how or works!
If you've used to an or
statement in code then you (like me) might think that exit 0 || exit 1
would result in 1
- since 0
is generally falsy. Except in exit codes, remember, 0
is success (truthy), and 1
is failure (falsy).
So ||
(or) reads as: if it failed then do X. You can see the results here:
$ sh -c 'sh -c "exit 0" || exit 1'; echo $?
0 # the OR was not used
$ sh -c 'sh -c "exit 1" || exit 1'; echo $?
1 # the OR _was_ used
$ sh -c 'sh -c "exit 2" || exit 1'; echo $?
1 # again, the OR _was_ used with exit 2 - failure
Explanation of the lines above: the inner sh -c
is a script that exits with the given code. The outer sh -c
will run the first script (the inner sh -c
) and if that fails, it will run exit 1
as specified with the ||
operator.
Elsewhere
I've used this same trick to fix a postinstall
problem where the postinstall
command in npm was failing which causes the entire npm install
process to blow up.
So I change the package.json
from:
{
"scripts": {
"postinstall": "node -e \"console.log('Hi there')"
}
}
To:
{
"scripts": {
"postinstall": "node -e \"console.log('Hi there') || exit 0"
}
}
The || exit 0
will cleanly exit the postinstall
if there's any error. Of course, if the node
command is successful it'll exit with 0
and the || exit 0
does not run and is ignored.
So it's always good to understand exit codes and how to manipulate them if you need to.