Draggin' the Entr
A brief love letter to 'entr'
entr stands for Event Notify Test Runner.
It’s a command line tool that makes it easy to re-run tests and live reload servers.
1) A list of files
2) A command to run
entr listens to those files and restarts the command when they change. You can
trigger restarts manually by pressing [spacebar].
entr is so simple and Unix-y, it’s hard to believe someone didn’t come up with
it at Bell Labs in the 70s. But it’s actually a pretty recent tool that Eric
Radman first released in
This post is a brief love letter to
entr: what its options are, why they’re
there, and how to build a similar experience on top of the Tilt API once your
logic gets too complex for
We will also have some good
entr the Options
The old rule of thumb of bodegas is that every sign behind the cash register has a great story where someone tried to do something so ridiculous that they had to post a sign to prevent it from happening again.
I feel the same way about
man pages! And the
entr manual is no exception.
entr manual is a history of
all the reasons why a command re-runner might seem simple but is more
complicated than you would expect. Here’s an abridged version of how that manual
reads to me:
-a: What happens if to the command writes to a file that makes the command restart? You don’t really want infinite loops…or do you?
-d: Can new files trigger commands?
-p: OK, I get how restarts are triggered, but what triggers the initial start?
-r: Are servers and test commands the same? Or are they fundamentally different and need different restart behavior? What about subprocesses?
-z: Does it really make sense to restart a server that’s crashing?
These are all good questions! And how you answer them can have a big impact on how you
Example #1: fast iterative testing
For day-to-day running of unit tests, something like
find pkg/model | entr go test ./pkg/model/...
does what I need.
find pkg/model lists all tests in that directory.
reruns the test when any of them change.
Example #2: reload a server
entr in Tilt for restarting servers! You can read the full script in
A simpler version of this script looks like:
echo /.restart-proc | entr -rz run-server
with some custom handling of exit codes when the server is crashing. Then Tilt
can touch the
/.restart-proc file to trigger a restart.
Speak, Friend, and
Let’s reimplement our test re-runner above on the Tilt API.
find pkg/model | entr go test ./pkg/model/...
First, start a Tilt server for your project’s dev environment:
tilt up &
Then run three lines:
tilt create filewatch pkg-model ./pkg/model tilt create cmd --filewatch=pkg-model pkg-model-test go test ./pkg/model/... tilt logs -f
Let’s break down these lines one at a time.
The first line creates a filewatch object.
$ tilt create filewatch pkg-model ./pkg/model filewatch.tilt.dev/pkg-model created
Because a filewatch is a full-fledged Tilt API object, I can inspect it directly to make sure it’s working.
$ tilt get filewatch pkg-model NAME CREATED AT pkg-model 2021-05-12T23:42:20Z $ touch pkg/model/secret.go $ tilt describe filewatch pkg-model Name: pkg-model Namespace: Labels: <none> Annotations: <none> API Version: tilt.dev/v1alpha1 Kind: FileWatch Metadata: Creation Timestamp: 2021-05-12T23:42:20Z Resource Version: 4 UID: 9c3a72df-18e5-4ea7-9ea5-4dcffa305667 Spec: Watched Paths: /home/nick/src/tilt/pkg/model Status: File Events: Seen Files: /home/nick/src/tilt/pkg/model/secret.go Time: 2021-05-12T23:42:27.064831Z Last Event Time: 2021-05-12T23:42:27.064831Z Monitor Start Time: 2021-05-12T23:42:20.056037Z
For more on how to inspect Tilt’s file watches, see this post.
The second line creates a command that restarts whenever the FileWatch sees new events.
$ tilt create cmd --filewatch=pkg-model pkg-model-test go test ./pkg/model/... cmd.tilt.dev/pkg-model-test created
Cmd is also a first-class Tilt API object, so we can inspect it:
$ tilt describe cmd pkg-model-test Name: pkg-model-test Namespace: Labels: <none> Annotations: <none> API Version: tilt.dev/v1alpha1 Kind: Cmd Metadata: Creation Timestamp: 2021-05-12T23:45:53Z Resource Version: 3 UID: 7c0645ce-ca60-4849-910a-f44cd9d733f2 Spec: Args: go test ./pkg/model/... Dir: /home/nick/src/tilt Restart On: File Watches: pkg-model Status: Ready: true Terminated: Exit Code: 0 Finished At: 2021-05-12T23:45:54.911704Z Pid: 1032214 Started At: 2021-05-12T23:45:53.915968Z
In this example, I can see that the command immediately ran in about 1 second.
The third line tails the Tilt logs.
-f argument streams the logs and waits for new logs.
To put it all together, I can delete a file and look at the logs to see the result of the command re-run:
$ rm pkg/model/secret.go $ tilt logs | tail Running cmd: go test ./pkg/model/... # github.com/tilt-dev/tilt/pkg/model/logstore [github.com/tilt-dev/tilt/pkg/model/logstore.test] pkg/model/logstore/logstore.go:237:51: undefined: model.SecretSet pkg/model/logstore/logstore.go:246:48: undefined: model.SecretSet pkg/model/logstore/logstore_test.go:122:15: undefined: model.SecretSet ok github.com/tilt-dev/tilt/pkg/model (cached) FAIL github.com/tilt-dev/tilt/pkg/model/logstore [build failed] FAIL go test ./pkg/model/... exited with exit code 2
Tilt immediately ran the tests, which failed, because the symbols
secret.go are missing.
And with only three lines of
tilt invocations, we recreated our
entr Stage Right
I honestly think
entr should be a go-to tool for anyone that messes around
a lot in the terminal.
We don’t want Tilt to replace
entr! But we think it can complement
you’re ready to move from hackable one-offs to composable systems.
One of the goals we’re trying to accomplish with the Tilt API server is to have
a more robust, Kubernetes-inspired API for building dev environments. It will definitely
be more verbose than
entr! But it will also be more debuggable and maintainable
for the next person on your team.