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.