Skip to main content

rb-sys-test-helpers

The rb-sys-test-helpers crate provides utilities for testing Ruby extensions from Rust. It makes it easy to run tests with a valid Ruby VM.

Usage

Add this to your Cargo.toml:

[dev-dependencies]
rb-sys-env = { version = "0.1" }
rb-sys-test-helpers = { version = "0.2" }

Then, in your crate's build.rs:

pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let _ = rb_sys::activate()?;

Ok(())
}

Then, you can use the ruby_test attribute macro in your tests. All of your tests can be placed in a single mod tests block, with all the necessary imports at the top.

#[cfg(test)]
mod tests {
use rb_sys_test_helpers::ruby_test;
use rb_sys::{
rb_num2fix, rb_int2big, FIXNUM_P, rb_cObject, rb_funcall, rb_str_new_cstr, rb_iv_set, rb_intern
};
use magnus::{Ruby, RString};
use proptest::prelude::*;
use std::ffi::CString;

#[ruby_test]
fn test_something() {
let int = unsafe { rb_num2fix(1) };
let big = unsafe { rb_int2big(9999999) };

assert!(FIXNUM_P(int));
assert!(!FIXNUM_P(big));
}

#[ruby_test]
fn test_value_conversion() {
unsafe {
let obj = rb_cObject;
// Use a literal C string for tests
let value = rb_str_new_cstr(c"test".as_ptr());

rb_iv_set(obj, c"@name".as_ptr() as *const _, value);

let result = rb_funcall(obj, rb_intern(c"instance_variable_get".as_ptr()), 1,
rb_str_new_cstr(c"@name".as_ptr()));

assert_eq!(value, result);
}
}

#[ruby_test]
fn test_with_magnus() {
let _ruby = unsafe { Ruby::get_unchecked() };
let string = RString::new("Hello, world!");

assert_eq!(string.to_string().unwrap_or_default(), "Hello, world!");
}

#[ruby_test]
fn test_with_proptest() {
proptest!(|(s in "[a-zA-Z0-9]*")| {
let _ruby = unsafe { Ruby::get_unchecked() };
let ruby_string = RString::new(&s);
assert_eq!(ruby_string.to_string().unwrap_or_default(), s);
});
}
}

How It Works

The ruby_test macro sets up a Ruby VM before running your test and tears it down afterward. This allows you to interact with Ruby from your Rust code during tests without having to set up the VM yourself.

The test helpers are compatible with both rb-sys for low-level C API access and magnus for higher-level Ruby interactions.

Testing Multiple Ruby Versions

To test against multiple Ruby versions, you can use environment variables and CI configuration:

# .github/workflows/test.yml
jobs:
test:
strategy:
matrix:
ruby: ["2.7", "3.0", "3.1", "3.2", "3.3"]
steps:
- uses: actions/checkout@v4
- uses: oxidize-rb/actions/setup-ruby-and-rust@v1
with:
ruby-version: ${{ matrix.ruby }}
- run: cargo test

Your tests will run against each Ruby version in the matrix, helping you ensure compatibility.