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.
*/
int foo_count_letters(const char *source, size_t *count)
{
if (!source || !count) return 0;
for (*count = 0; *source; (*count)++, source++)
;
return 1;
}
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 :rubygems
gem 'ffi'
Then write your rspec tests spec/foo_spec.rb
:
#!/usr/bin/env ruby
require 'ffi'
# This module is your bridge from Ruby to C and back
module FOO
extend FFI::Library
# Use an absolute path to the executable under test, otherwise ffi will search LD_LIBRARY_PATH.
ffi_lib File.absolute_path(File.join(File.dirname(__FILE__), "..", "foo"))
# Function signatures for each function to be tested
attach_function :foo_count_letters, [:string, :pointer], :int
end
describe "unit tests for foo.c" do
before(:each) do
end
it "should really foo" do
# This function takes a pointer-to-uint32 out-param
out = FFI::MemoryPointer.new :uint32
res = FOO.foo_count_letters("Hello", out)
# Read back the pointers to Ruby data types, then use rspec's verification functions
out.get_uint32(0).should == 5
res.should == 1
end
end
I’m excited about this approach because the tests run under rspec
along with the rest of your spec tests.