(This is a repost of an old blog post of mine from Teknik.)
#lfe #erlang
While working on the LFE Swank Server, I noticed that Erlang's architecture rewards creating multiple tiny specialized processes. Because of this, I will want to have a simple test framework in LFE that leverages this architecture and is able to create "mock" processes. Essentially, what I need is a macro that is capable of spawning processes, binding their PIDs to variables, and cleaning them up after the test is over.
A WITH-PROCESSES
macro.
But, since the land of Erlang prefers underscores, I instead named it WITH_PROCESSES
.
Writing macros in LFE is a really weird feeling to me, at least when compared to Common Lisp. I have no destructuring lambda lists which I am used to, but I have pattern matching which is slightly more powerful than CL's destructuring. I also got to know Erlang's/LFE's try/catch/after
form - from a Lisper's perspective, it's CL:HANDLER-CASE
and CL:UNWIND-PROTECT
combined.
See the code for the macro and a very simple test case below. To make sure that the processes actually die, substitute the echo
function with a call to (timer:sleep 100000)
, and then call the (i)
function inside and just outside the WITH_PROCESSES
body to verify there are timer:sleep
calls in the process list inside the macro body, but none of them remain after it is finished.
(defmacro with_processes
(`(() . ,body)
`(progn ,@body))
(`(((,symbol ,function) . ,rest) . ,body)
`(let ((,symbol (spawn ,function)))
(try (with-processes ,rest ,@body)
(after (erlang:exit ,symbol 'kill))))))
(defun test_with_processes ()
(flet ((echo () (receive (`#(,pid ,x) (! pid x)))))
(with-processes
((foo #'echo/0)
(bar #'echo/0))
(! foo `#(,(self) 2))
(receive (2 'ok)
(after 1000 (erlang:error "test failed")))
(! bar `#(,(self) 10))
(receive (10 'ok)
(after 1000 (erlang:error "test failed"))))))