When describe'ing it ain't enough
One of the things I like about the RSpec syntax is that it packs a lot of information into a few concise, consistent constructs. It’s relatively easy to read through a spec file and pick out what I am looking for. The use of blocks both enable flexible execution strategies and provide simple containment boundaries.
Perhaps the most valuable aspect, though, is the ability to extend the RSpec syntax constructs easily and consistently. No need to grow a third arm here. In Rubinius, we recently encountered a situation needing some extra sauce when fixing our compiler specs.
A compiler can be thought of as something that chews up data in one form and spits it out in another, equivalent, form. Typically, these transformations from one form to another happen in a particular order. And there may be several of them from the very beginning to the very end of the compilation process.
To write specs for such a process, it would be nice to focus just on the forms of the data (that’s what we care about) with as little noise as possible about how they got there. Here’s what we have in Rubinius:
1 describe "An And node" do 2 relates "(a and b)" do 3 parse do 4 [:and, [:call, nil, :a, [:arglist]], [:call, nil, :b, [:arglist]]] 5 end 6 7 compile do |g| 8 g.push :self 9 g.send :a, 0, true 10 g.dup 11 12 lhs_true = g.new_label 13 g.gif lhs_true 14 15 g.pop 16 g.push :self 17 g.send :b, 0, true 18 19 lhs_true.set! 20 end 21 end 22 end
relates block introduces the Ruby source code and contains the blocks that show various intermediate forms. A single word like
compile encapsulates the process of generating that particular form, as well as concisely documenting the specs.
The format is sufficiently flexible to allow for other forms. For instance,
ast for generating an AST directly from the parse tree rather than using the sexp as an intermediate form. Or
llvm to emit LLVM IR directly from our compiler.
Another interesting aspect of this, it was possible with only a few custom extensions to MSpec. Recently, I had added custom options to the MSpec runner scripts to enable such things as our
--gc-stats. I didn’t know how easy it would be to add something more extensive. Turns out it was pretty easy. You can check out the source in our spec/custom directory.