CamlP4 for unit testing
We’ve been mulling over how to add new unit tests to our OCaml codebase. Our first thought was to write a test suite apart from the code itself; we’ve already some end-to-end tests written in that way. The problem with that approach is that the tests are separated from the code they’re testing, which discourages writing tests.
A few weeks ago, we wrote some time- and heap-profiling CamlP4 macros, which add entries to a centralized table as our code runs. When the program finishes, it spits out statistics that we can examine for time and space sinks. The profiling hints — macros, that is — wrap the code to be profiled. So why not use the same approach for our tests?
I wrote a CamlP4 macro which is used like this:
LET_TEST some_fun : test1 = TestCase (fun () -> .... some_fun ...)
Such a test can be put in the code right next to the function some_fun. In fact, there are two versions of the macro. One version registers the test in a centralized table. The other version expands to (essentially) nothing, so the test is compiled away for release versions of our code.
Writing the macro was tricky — mostly because I don’t have a lot of experience with CamlP4. The subtlest part was converting identifiers (some_fun and test1 above) to strings which are passed to the registration function. I could’ve required the user to use strings directly in the code, but that’s ugly.
The macro calls the following function at expansion time:
let string_of_patt p errMsg =
match Ast.ident_of_patt p with
| Ast.IdLid (_loc,x) -> <:expr<$str:x$>>
| _ -> raise (Failure errMsg)
This function extracts an identifier from the pattern, extracts the identifier text, then builds a string, which is syntactically an expression. The <:expr< >> syntax is sugar for an Ast node representing an expression. The _loc in the pattern clause is required, because it binds an identifier in the desugared syntax (boo — variable capture!).
Writing this macro seemed to harder than writing, say, the equivalent Scheme macro. The CamlP4 syntax is kind of baroque, and some of its conventions aren’t intuitive. But it lets us do some very useful things.