rb_sys_test_helpers/
utils.rs

1/// Memoizes the result [`rb_sys::VALUE`] of the given expression.
2#[macro_export]
3macro_rules! memoized {
4    ($e:expr) => {{
5        pub static INIT: std::sync::Once = std::sync::Once::new();
6        pub static mut MEMOIZED_VAL: Option<rb_sys::VALUE> = None;
7
8        INIT.call_once(|| unsafe {
9            MEMOIZED_VAL.replace($e);
10        });
11
12        unsafe { *MEMOIZED_VAL.as_ref().unwrap() }
13    }};
14}
15
16/// Creates a new Ruby string from a Rust string.
17#[macro_export]
18macro_rules! rstring {
19    ($s:expr) => {
20        unsafe { rb_sys::rb_utf8_str_new($s.as_ptr() as _, $s.len() as _) }
21    };
22}
23
24/// Creates a new Ruby symbol from a Rust literal str.
25#[macro_export]
26macro_rules! rsymbol {
27    ($s:literal) => {
28        unsafe { rb_sys::rb_id2sym(rb_sys::rb_intern(concat!($s, "\0").as_ptr() as _)) }
29    };
30}
31
32/// Evaluates a Ruby expression.
33#[macro_export]
34macro_rules! eval {
35    () => {
36        unsafe { rb_sys::rb_eval_string("\0".as_ptr() as _) }
37    };
38    ($s:expr) => {
39        unsafe { rb_sys::rb_eval_string(concat!($s, "\0").as_ptr() as _) }
40    };
41}
42
43/// Captures the GC stat before and after the expression.
44#[macro_export]
45macro_rules! capture_gc_stat_for {
46    ($id:literal, $e:expr) => {{
47        let id = $crate::memoized! { $crate::rsymbol!($id) };
48
49        unsafe {
50            $crate::trigger_full_gc!();
51
52            let before = unsafe { rb_sys::rb_gc_stat(id) };
53            let result = $e;
54            let after = unsafe { rb_sys::rb_gc_stat(id) };
55
56            (result, after as isize - before as isize)
57        }
58    }};
59}
60
61/// Allows you to convert a Ruby string to a Rust string.
62#[macro_export]
63macro_rules! rstring_to_string {
64    ($v:expr) => {{
65        let cstr = rb_sys::rb_string_value_cstr(&mut $v);
66
67        std::ffi::CStr::from_ptr(cstr)
68            .to_string_lossy()
69            .into_owned()
70    }};
71}
72
73/// This is a macro that allows you to call a method on a Ruby object, and get
74/// an `Option` back. If the type matches, it will return `Some`, otherwise it
75/// will return `None`.
76#[macro_export]
77macro_rules! rb_funcall_typed {
78    ($v:expr, $m:expr, $args:expr, $t:expr) => {{
79        {
80            let args: &mut [rb_sys::VALUE] = &mut $args[..];
81            let id = rb_sys::rb_intern(concat!($m, "\0").as_ptr() as _);
82            let argv = $args.as_ptr();
83            let result = rb_sys::rb_check_funcall($v, id, args.len() as _, argv);
84
85            if !RB_TYPE_P(result, $t as _) {
86                None
87            } else {
88                Some(result)
89            }
90        }
91    }};
92}
93
94/// Runs the garbage collector 10 times to ensure that we have a clean slate.
95#[macro_export]
96macro_rules! trigger_full_gc {
97    () => {
98        let cmd = "GC.start(full_mark: false, immediate_sweep: false)\0".as_ptr() as *const _;
99
100        for _ in 0..20 {
101            unsafe { rb_sys::rb_eval_string(cmd) };
102            std::thread::yield_now();
103        }
104    };
105}