The corrosion of Aaron Stone

about

aaron@serendipity.cx

Writing C Unit Tests in Ruby

Let’s say you usually code in Ruby, and your company and its build systems are
built around Rakefiles and the like. Today you’ve written some C code, and you
want to add unit tests. In this blog post, I present a method of writing those
C unit tests in Ruby using FFI and
RSpec.

/**
* This is a very silly function that clearly requires some unit tests.
*/intfoo_count_letters(constchar*source,size_t*count){if(!source||!count)return0;for(*count=0;*source;(*count)++,source++);return1;}

First, compile your C code as position independent and symbols exported. This allows you to dlopen() the executable:

$ gcc -pie -rdynamic -o foo foo.c

Next, add the FFI gem to your Gemset, in Gemfile:

source:rubygemsgem'ffi'

Then write your rspec tests spec/foo_spec.rb:

#!/usr/bin/env rubyrequire'ffi'# This module is your bridge from Ruby to C and backmoduleFOOextendFFI::Library# Use an absolute path to the executable under test, otherwise ffi will search LD_LIBRARY_PATH.ffi_libFile.absolute_path(File.join(File.dirname(__FILE__),"..","foo"))# Function signatures for each function to be testedattach_function:foo_count_letters,[:string,:pointer],:intenddescribe"unit tests for foo.c"dobefore(:each)doendit"should really foo"do# This function takes a pointer-to-uint32 out-paramout=FFI::MemoryPointer.new:uint32res=FOO.foo_count_letters("Hello",out)# Read back the pointers to Ruby data types, then use rspec's verification functionsout.get_uint32(0).should==5res.should==1endend

I’m excited about this approach because the tests run under rspec along with the rest of your spec tests.