|
| 1 | +# rb-sys-test-helpers |
| 2 | + |
| 3 | +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. |
| 4 | + |
| 5 | +## Usage |
| 6 | + |
| 7 | +Add this to your `Cargo.toml`: |
| 8 | + |
| 9 | +```toml |
| 10 | +[dev-dependencies] |
| 11 | +rb-sys-env = { version = "0.1" } |
| 12 | +rb-sys-test-helpers = { version = "0.2" } |
| 13 | +``` |
| 14 | + |
| 15 | +Then, in your crate's `build.rs`: |
| 16 | + |
| 17 | +```rust |
| 18 | +pub fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 19 | + let _ = rb_sys_env::activate()?; |
| 20 | + |
| 21 | + Ok(()) |
| 22 | +} |
| 23 | +``` |
| 24 | + |
| 25 | +Then, you can use the `ruby_test` attribute macro in your tests: |
| 26 | + |
| 27 | +```rust |
| 28 | +#[cfg(test)] |
| 29 | +mod tests { |
| 30 | + use rb_sys_test_helpers::ruby_test; |
| 31 | + use rb_sys::{rb_num2fix, rb_int2big, FIXNUM_P}; |
| 32 | + |
| 33 | + #[ruby_test] |
| 34 | + fn test_something() { |
| 35 | + // Your test code here will have a valid Ruby VM (hint: this works with |
| 36 | + // the `magnus` crate, too!) |
| 37 | + // |
| 38 | + // ... |
| 39 | + |
| 40 | + let int = unsafe { rb_num2fix(1) }; |
| 41 | + let big = unsafe { rb_int2big(9999999) }; |
| 42 | + |
| 43 | + assert!(FIXNUM_P(int)); |
| 44 | + assert!(!FIXNUM_P(big)); |
| 45 | + } |
| 46 | +} |
| 47 | +``` |
| 48 | + |
| 49 | +## How It Works |
| 50 | + |
| 51 | +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. |
| 52 | + |
| 53 | +The test helpers are compatible with both `rb-sys` for low-level C API access and `magnus` for higher-level Ruby interactions. |
| 54 | + |
| 55 | +## Common Testing Patterns |
| 56 | + |
| 57 | +### Testing Value Conversions |
| 58 | + |
| 59 | +```rust |
| 60 | +#[ruby_test] |
| 61 | +fn test_value_conversion() { |
| 62 | + use rb_sys::{rb_cObject, rb_funcall, rb_str_new_cstr, rb_iv_set}; |
| 63 | + use std::ffi::CString; |
| 64 | + |
| 65 | + unsafe { |
| 66 | + let obj = rb_cObject; |
| 67 | + let name = CString::new("test").unwrap(); |
| 68 | + let value = rb_str_new_cstr(name.as_ptr()); |
| 69 | + |
| 70 | + rb_iv_set(obj, b"@name\0".as_ptr() as *const _, value); |
| 71 | + |
| 72 | + let result = rb_funcall(obj, b"instance_variable_get\0".as_ptr() as *const _, 1, |
| 73 | + rb_str_new_cstr(b"@name\0".as_ptr() as *const _)); |
| 74 | + |
| 75 | + assert_eq!(value, result); |
| 76 | + } |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +### Testing with Magnus |
| 81 | + |
| 82 | +```rust |
| 83 | +#[ruby_test] |
| 84 | +fn test_with_magnus() { |
| 85 | + use magnus::{Ruby, RString, Value}; |
| 86 | + |
| 87 | + let ruby = unsafe { Ruby::get().unwrap() }; |
| 88 | + let string = RString::new(ruby, "Hello, world!").unwrap(); |
| 89 | + |
| 90 | + assert_eq!(string.to_string().unwrap(), "Hello, world!"); |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +## Testing Multiple Ruby Versions |
| 95 | + |
| 96 | +To test against multiple Ruby versions, you can use environment variables and CI configuration: |
| 97 | + |
| 98 | +```yaml |
| 99 | +# .github/workflows/test.yml |
| 100 | +jobs: |
| 101 | + test: |
| 102 | + strategy: |
| 103 | + matrix: |
| 104 | + ruby: ['2.7', '3.0', '3.1', '3.2', '3.3'] |
| 105 | + steps: |
| 106 | + - uses: actions/checkout@v4 |
| 107 | + - uses: oxidize-rb/actions/setup-ruby-and-rust@v1 |
| 108 | + with: |
| 109 | + ruby-version: ${{ matrix.ruby }} |
| 110 | + - run: cargo test |
| 111 | +``` |
| 112 | +
|
| 113 | +Your tests will run against each Ruby version in the matrix, helping you ensure compatibility. |
| 114 | +
|
| 115 | +## Integration with Other Test Frameworks |
| 116 | +
|
| 117 | +The `ruby_test` attribute works with common Rust test frameworks like `proptest` and `quickcheck`: |
| 118 | + |
| 119 | +```rust |
| 120 | +#[ruby_test] |
| 121 | +fn test_with_proptest() { |
| 122 | + use proptest::prelude::*; |
| 123 | + |
| 124 | + proptest!(|(s in "[a-zA-Z0-9]*")| { |
| 125 | + let ruby = unsafe { Ruby::get().unwrap() }; |
| 126 | + let ruby_string = RString::new(ruby, &s).unwrap(); |
| 127 | + assert_eq!(ruby_string.to_string().unwrap(), s); |
| 128 | + }); |
| 129 | +} |
| 130 | +``` |
0 commit comments