#![allow(unused_imports, dead_code)]
#[macro_use]
extern crate lazy_static;
#[cfg(windows)]
#[macro_export]
macro_rules! rutie_callback {
(type fn $($signature:tt)*) => { extern "C-unwind" fn $($signature)* };
($(#[$attribute:meta])* $visibility:vis fn $($function:tt)*) => {
$(#[$attribute])* $visibility extern "C-unwind" fn $($function)*
};
}
#[cfg(not(windows))]
#[macro_export]
macro_rules! rutie_callback {
(type fn $($signature:tt)*) => { extern "C" fn $($signature)* };
($(#[$attribute:meta])* $visibility:vis fn $($function:tt)*) => {
$(#[$attribute])* $visibility extern "C" fn $($function)*
};
}
mod binding;
mod class;
mod helpers;
pub mod rubysys;
#[macro_use]
pub mod dsl;
pub mod typed_data;
pub mod types;
pub mod util;
#[cfg(not(test))]
include!(concat!(env!("OUT_DIR"), "/link_ruby.rs"));
pub use crate::class::{
any_exception::AnyException,
any_object::AnyObject,
array::Array,
binding::Binding,
boolean::Boolean,
class::Class,
complex::Complex,
debug::{DebugInspector, ProfileFrame},
encoding::Encoding,
enumerator::{Enumerator, EnumeratorIterator},
fiber::Fiber,
fixnum::Fixnum,
float::Float,
gc::GC,
global_variable::GlobalVariable,
hash::{Hash, HashIterator},
integer::Integer,
io::{File, IO},
io_buffer::IOBuffer,
marshal::Marshal,
memory_view::MemoryView,
method::Method,
module::Module,
mutex::{Mutex, MutexGuard},
nil_class::NilClass,
ractor::{Ractor, RactorLocalKey},
range::Range,
rational::Rational,
regexp::{MatchData, Regexp},
rproc::Proc,
rstruct::Struct,
string::{CodeRange, RString},
symbol::Symbol,
thread::Thread,
time::Time,
vm::VM,
};
#[cfg(ruby_gte_3_2)]
pub use crate::class::thread::{InternalThreadEvent, InternalThreadEventHook};
#[cfg(ruby_gte_3_3)]
pub use crate::class::{debug::PostponedJob, thread::InternalThreadSpecificKey};
pub use crate::class::traits::{
encoding_support::EncodingSupport, exception::Exception, object::Object,
try_convert::TryConvert, verified_object::VerifiedObject,
};
pub use crate::helpers::{
codepoint_iterator::CodepointIterator,
scan_args::{KeywordArgs, ScannedArgs},
};
use std::sync::{Arc, RwLock};
#[cfg(test)]
lazy_static! {
pub static ref LOCK_FOR_TEST: RwLock<i32> = RwLock::new(0);
}
#[cfg(test)]
pub(crate) fn on_ruby_thread<F>(test: F)
where
F: FnOnce() + Send + 'static,
{
use std::{
panic::{self, AssertUnwindSafe},
sync::{mpsc, Mutex},
thread,
};
type Job = (
String,
Box<dyn FnOnce() + Send>,
mpsc::Sender<thread::Result<()>>,
);
lazy_static! {
static ref RUBY_THREAD: Mutex<mpsc::Sender<Job>> = {
let (sender, receiver) = mpsc::channel::<Job>();
thread::Builder::new()
.name("ruby".to_string())
.stack_size(16 * 1024 * 1024)
.spawn(move || {
VM::init();
let trace = std::env::var_os("RUTIE_TEST_TRACE").is_some();
for (name, test, result) in receiver {
if trace {
use std::io::Write;
let _ = writeln!(std::io::stderr(), "[ruby thread] running {}", name);
}
let outcome = {
let _guard = LOCK_FOR_TEST.write().unwrap_or_else(|e| e.into_inner());
panic::catch_unwind(AssertUnwindSafe(test))
};
let _ = result.send(outcome);
}
})
.expect("failed to start the Ruby test thread");
Mutex::new(sender)
};
}
let (sender, receiver) = mpsc::channel();
RUBY_THREAD
.lock()
.unwrap_or_else(|e| e.into_inner())
.send((
thread::current().name().unwrap_or("<unnamed>").to_string(),
Box::new(test),
sender,
))
.expect("the Ruby test thread has stopped");
if let Err(payload) = receiver.recv().expect("the Ruby test thread has stopped") {
let message = payload
.downcast_ref::<&str>()
.map(|message| message.to_string())
.or_else(|| payload.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "(non-string panic payload)".to_string());
eprintln!("panicked on the Ruby test thread: {}", message);
panic::resume_unwind(payload);
}
}
#[cfg(test)]
mod current_ruby {
use super::{Object, RString, VM, *};
use std::process::Command;
#[test]
fn is_linked_ruby() {
crate::on_ruby_thread(|| {
let rv = RString::from(VM::eval("RUBY_VERSION").unwrap().value()).to_string();
let ruby = std::env::var_os("RUBY").unwrap_or_else(|| "ruby".into());
let output = Command::new(ruby)
.arg("-e")
.arg("printf RUBY_VERSION")
.output()
.unwrap()
.stdout;
let crv = String::from_utf8_lossy(&output);
assert_eq!(
rv, crv,
"\nCurrent console Ruby is version {} but the \
linked Ruby is version {} \
Please run `cargo clean` first to remove previously used symbolic link in \
the dependency directory.",
crv, rv
);
});
}
#[test]
fn cfg_flags_match_linked_ruby() {
crate::on_ruby_thread(|| {
let version = RString::from(
VM::eval("RUBY_VERSION.split('.')[0, 2].join('.')")
.unwrap()
.value(),
)
.to_string();
let cfg_version = if cfg!(ruby_3_1) {
"3.1"
} else if cfg!(ruby_3_2) {
"3.2"
} else if cfg!(ruby_3_3) {
"3.3"
} else {
"unsupported"
};
assert_eq!(version, cfg_version, "Ruby version cfg flag mismatch");
assert!(cfg!(ruby_gte_3_1));
assert_eq!(cfg!(ruby_gte_3_2), version.as_str() >= "3.2");
assert_eq!(cfg!(ruby_gte_3_3), version.as_str() >= "3.3");
});
}
#[test]
fn rust_panics_unwind_with_libruby_linked() {
use std::panic::{self, AssertUnwindSafe};
let caught = panic::catch_unwind(|| panic!("first")).unwrap_err();
let resumed = panic::catch_unwind(AssertUnwindSafe(|| panic::resume_unwind(caught)));
assert_eq!(resumed.unwrap_err().downcast_ref::<&str>(), Some(&"first"));
}
}