Using entr as a test watcher

Published Feb 3, 2021

When I’m programming, I like to get feedback about my code as fast as possible. I like to learn about all of my bugs every time I’m “done thinking” (every time I save a file). Nowadays, LSP integrations have made it possible to get syntax and type checking on every keystroke, which is even faster than that! But everything else has to run on save.

When I discovered entr, I knew it would immediately change my workflow. I use it all the time for iterating on little one-off scripts. I edit in one terminal window and run this in another:

1
ls shenanigans.py | entr -c python /_

If it ever depends on multiple files (for larger projects or separate test data), I’ll combine it with fd like this:

1
fd . | entr -c make test

But I ran into a problem using this when adding or removing files because entr (intentionally) doesn’t update the list of files by itself. Its manual has an example for solving this (using its -d option and a bash loop):

1
while true; do ls src/*.rb | entr -d make; done

But I would sometimes get double-runs of my tests, which was weird and slow. So far, I’ve narrowed it down to an interaction between Neovim, ALE, Rubocop, and the write-tempfile-and-rename-into-place pattern for atomic rewrites. In other words, I have no idea.

But I do have a fix! The answer up front:

1
2
3
until fd . | entr -cdp make test ; do
  echo 'Edit a file or press Space to run tests.'
done

The trick turned out to be two parts:

  1. The -p option to delay execution until something changes.
  2. Accepting the limitation of needing to wait for a file save for the first test run.

Here’s how it works:

  1. fd . finds all the files and directories starting from the current working directory.
  2. entr -cdp does nothing yet because -p makes it wait for a change.
  3. I save a file or press Space.
  4. entr notices that, clears the screen (-c), and runs make test.
  5. If no directories changed (no files added or removed), goto 3.
  6. If a directory changed, entr exits with status code 2 (which is falsy in bash), so until’s condition has not been met.
  7. The body of the loop runs, so the echo message is printed.
  8. until loops, so goto 1.

I thought this was pretty clever, because the instructions are there when I run the command and when it “stops” on a directory change. If you don’t like telling yourself what to do, replace the echo command with : (no-op) or true