#[cfg(feature = "ffi")]
mod ffi_error_tests {
use std::cell::RefCell;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;
thread_local! {
static LAST_ERROR: RefCell<Option<CString>> = RefCell::new(None);
}
static ERROR_CALLBACK: Mutex<Option<unsafe extern "C" fn(*const c_char)>> = Mutex::new(None);
fn set_last_error(msg: &str) {
LAST_ERROR.with(|error| {
if let Ok(cstring) = CString::new(msg) {
*error.borrow_mut() = Some(cstring.clone());
if let Ok(callback_guard) = ERROR_CALLBACK.lock() {
if let Some(callback) = *callback_guard {
unsafe {
callback(cstring.as_ptr());
}
}
}
}
});
}
unsafe fn get_last_error() -> *const c_char {
LAST_ERROR.with(|error| match error.borrow().as_ref() {
Some(cstring) => cstring.as_ptr(),
None => {
static NO_ERROR_MSG: &[u8] = b"No error information available\0";
NO_ERROR_MSG.as_ptr() as *const c_char
}
})
}
unsafe fn set_error_callback(callback: Option<unsafe extern "C" fn(*const c_char)>) {
if let Ok(mut callback_guard) = ERROR_CALLBACK.lock() {
*callback_guard = callback;
}
}
#[test]
fn test_basic_error_handling() {
unsafe {
let error_ptr = get_last_error();
assert!(!error_ptr.is_null());
let error_msg = CStr::from_ptr(error_ptr).to_str().unwrap();
assert_eq!(error_msg, "No error information available");
set_last_error("Test error message");
let error_ptr = get_last_error();
assert!(!error_ptr.is_null());
let error_msg = CStr::from_ptr(error_ptr).to_str().unwrap();
assert_eq!(error_msg, "Test error message");
}
}
#[test]
fn test_error_callback() {
use std::sync::atomic::{AtomicPtr, Ordering};
static CALLBACK_CALLED: AtomicBool = AtomicBool::new(false);
static CALLBACK_MESSAGE: Mutex<Option<String>> = Mutex::new(None);
unsafe extern "C" fn test_callback(msg: *const c_char) {
CALLBACK_CALLED.store(true, Ordering::SeqCst);
let c_str = unsafe { CStr::from_ptr(msg) };
if let Ok(str_slice) = c_str.to_str() {
if let Ok(mut callback_msg) = CALLBACK_MESSAGE.lock() {
*callback_msg = Some(str_slice.to_string());
}
}
}
unsafe {
set_error_callback(Some(test_callback));
CALLBACK_CALLED.store(false, Ordering::SeqCst);
if let Ok(mut callback_msg) = CALLBACK_MESSAGE.lock() {
*callback_msg = None;
}
set_last_error("Test callback message");
assert!(CALLBACK_CALLED.load(Ordering::SeqCst));
if let Ok(callback_msg) = CALLBACK_MESSAGE.lock() {
assert!(callback_msg.is_some());
assert_eq!(callback_msg.as_ref().unwrap(), "Test callback message");
}
set_error_callback(None);
CALLBACK_CALLED.store(false, Ordering::SeqCst);
if let Ok(mut callback_msg) = CALLBACK_MESSAGE.lock() {
*callback_msg = None;
}
set_last_error("Another test message");
assert!(!CALLBACK_CALLED.load(Ordering::SeqCst));
if let Ok(callback_msg) = CALLBACK_MESSAGE.lock() {
assert!(callback_msg.is_none());
}
}
}
#[test]
fn test_thread_local_errors() {
let results = Arc::new(Mutex::new(Vec::new()));
let mut handles = Vec::new();
for i in 0..3 {
let results_clone = Arc::clone(&results);
let handle = thread::spawn(move || {
unsafe {
let error_msg = match i {
0 => "Thread 0 error",
1 => "Thread 1 error",
2 => "Thread 2 error",
_ => "Unknown thread error",
};
set_last_error(error_msg);
let error_ptr = get_last_error();
let retrieved_msg = if !error_ptr.is_null() {
CStr::from_ptr(error_ptr)
.to_str()
.unwrap_or("Invalid UTF-8")
.to_string()
} else {
"Null pointer".to_string()
};
let mut results_guard = results_clone.lock().unwrap();
results_guard.push((i, retrieved_msg));
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let results_guard = results.lock().unwrap();
assert_eq!(results_guard.len(), 3);
let mut found_thread0 = false;
let mut found_thread1 = false;
let mut found_thread2 = false;
for (thread_id, error_msg) in results_guard.iter() {
match *thread_id {
0 => {
assert_eq!(error_msg, "Thread 0 error");
found_thread0 = true;
}
1 => {
assert_eq!(error_msg, "Thread 1 error");
found_thread1 = true;
}
2 => {
assert_eq!(error_msg, "Thread 2 error");
found_thread2 = true;
}
_ => {}
}
}
assert!(found_thread0 && found_thread1 && found_thread2);
}
#[test]
fn test_error_message_persistence() {
unsafe {
set_last_error("Persistent error");
let error_ptr1 = get_last_error();
let error_msg1 = CStr::from_ptr(error_ptr1).to_str().unwrap();
let error_ptr2 = get_last_error();
let error_msg2 = CStr::from_ptr(error_ptr2).to_str().unwrap();
assert_eq!(error_msg1, "Persistent error");
assert_eq!(error_msg2, "Persistent error");
assert_eq!(error_ptr1, error_ptr2); }
}
#[test]
fn test_error_message_replacement() {
unsafe {
set_last_error("First error");
let error_ptr = get_last_error();
let error_msg = CStr::from_ptr(error_ptr).to_str().unwrap();
assert_eq!(error_msg, "First error");
set_last_error("Second error");
let error_ptr = get_last_error();
let error_msg = CStr::from_ptr(error_ptr).to_str().unwrap();
assert_eq!(error_msg, "Second error");
}
}
}