rb_sys_test_helpers/
lib.rs1#![allow(rustdoc::bare_urls)]
2#![doc = include_str!("../readme.md")]
3mod once_cell;
4mod ruby_exception;
5mod ruby_test_executor;
6mod utils;
7
8use rb_sys::{rb_errinfo, rb_intern, rb_set_errinfo, Qnil, VALUE};
9use ruby_test_executor::global_executor;
10use std::{error::Error, mem::MaybeUninit, panic::UnwindSafe};
11
12pub use rb_sys_test_helpers_macros::*;
13pub use ruby_exception::RubyException;
14pub use ruby_test_executor::{cleanup_ruby, setup_ruby, setup_ruby_unguarded};
15
16pub fn with_ruby_vm<R, F>(f: F) -> Result<R, Box<dyn Error>>
42where
43 R: Send + 'static,
44 F: FnOnce() -> R + UnwindSafe + Send + 'static,
45{
46 global_executor().run_test(f)
47}
48
49pub fn with_gc_stress<R, F>(f: F) -> R
68where
69 R: Send + 'static,
70 F: FnOnce() -> R + UnwindSafe + Send + 'static,
71{
72 unsafe {
73 let stress_intern = rb_intern("stress\0".as_ptr() as _);
74 let stress_eq_intern = rb_intern("stress=\0".as_ptr() as _);
75 let gc_module = rb_sys::rb_const_get(rb_sys::rb_cObject, rb_intern("GC\0".as_ptr() as _));
76
77 let old_gc_stress = rb_sys::rb_funcall(gc_module, stress_intern, 0);
78 rb_sys::rb_funcall(gc_module, stress_eq_intern, 1, rb_sys::Qtrue);
79 let result = std::panic::catch_unwind(f);
80 rb_sys::rb_funcall(gc_module, stress_eq_intern, 1, old_gc_stress);
81
82 match result {
83 Ok(result) => result,
84 Err(err) => std::panic::resume_unwind(err),
85 }
86 }
87}
88
89pub fn protect<F, T>(f: F) -> Result<T, RubyException>
107where
108 F: FnMut() -> T + std::panic::UnwindSafe,
109{
110 unsafe extern "C" fn ffi_closure<T, F: FnMut() -> T>(args: VALUE) -> VALUE {
111 let args: *mut (Option<*mut F>, *mut Option<T>) = args as _;
112 let args = *args;
113 let (mut func, outbuf) = args;
114 let func = func.take().unwrap();
115 let func = &mut *func;
116 let result = func();
117 outbuf.write_volatile(Some(result));
118 outbuf as _
119 }
120
121 unsafe {
122 let mut state = 0;
123 let func_ref = &Some(f) as *const _;
124 let mut outbuf: MaybeUninit<Option<T>> = MaybeUninit::new(None);
125 let args = &(Some(func_ref), outbuf.as_mut_ptr() as *mut _) as *const _ as VALUE;
126 rb_sys::rb_protect(Some(ffi_closure::<T, F>), args, &mut state);
127
128 if state == 0 {
129 if outbuf.as_mut_ptr().read_volatile().is_some() {
130 Ok(outbuf.assume_init().expect("unreachable"))
131 } else {
132 Err(RubyException::new(rb_errinfo()))
133 }
134 } else {
135 let err = rb_errinfo();
136 rb_set_errinfo(Qnil as _);
137 Err(RubyException::new(err))
138 }
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_protect_returns_correct_value() -> Result<(), Box<dyn Error>> {
148 let ret = with_ruby_vm(|| protect(|| "my val"))?;
149
150 assert_eq!(ret, Ok("my val"));
151
152 Ok(())
153 }
154
155 #[test]
156 fn test_protect_capture_ruby_exception() {
157 with_ruby_vm(|| unsafe {
158 let result = protect(|| {
159 rb_sys::rb_raise(rb_sys::rb_eRuntimeError, "hello world\0".as_ptr() as _);
160 });
161
162 assert!(result.is_err());
163 })
164 .unwrap();
165 }
166}