feat: improve cstr_ffi module
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
//! Integration tests for the `cstr_ffi` module.
|
||||
//!
|
||||
//! These exercise the public API only. The thread-local cache is reset at the start of every test
|
||||
//! to keep cases independent. Tests that hand raw pointers back into the parse functions do so
|
||||
//! immediately, within the scope where the backing storage is guaranteed to live.
|
||||
|
||||
use sarasacw_omrf::cstr_ffi;
|
||||
|
||||
fn cptr_of(bytes: &[u8]) -> cstr_ffi::CStrPtr {
|
||||
bytes.as_ptr() as cstr_ffi::CStrPtr
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_single_string_round_trip() {
|
||||
cstr_ffi::clear_ffi_strings();
|
||||
let ptr = cstr_ffi::push_ffi_string("hello").unwrap();
|
||||
assert!(!ptr.is_null());
|
||||
assert_eq!(cstr_ffi::parse_ffi_string(ptr).unwrap(), "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pushed_strings_remain_valid_after_more_pushes() {
|
||||
cstr_ffi::clear_ffi_strings();
|
||||
let p1 = cstr_ffi::push_ffi_string("first").unwrap();
|
||||
// Force the internal Vec to reallocate by pushing many entries.
|
||||
let later: Vec<_> = (0..64).map(|_| cstr_ffi::push_ffi_string("filler").unwrap()).collect();
|
||||
let p2 = cstr_ffi::push_ffi_string("second").unwrap();
|
||||
let p3 = cstr_ffi::push_ffi_string("third").unwrap();
|
||||
assert_eq!(cstr_ffi::parse_ffi_string(p1).unwrap(), "first");
|
||||
assert_eq!(cstr_ffi::parse_ffi_string(p2).unwrap(), "second");
|
||||
assert_eq!(cstr_ffi::parse_ffi_string(p3).unwrap(), "third");
|
||||
assert_eq!(cstr_ffi::parse_ffi_string(later[40]).unwrap(), "filler");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_ffi_string_rejects_interior_nul() {
|
||||
cstr_ffi::clear_ffi_strings();
|
||||
assert!(cstr_ffi::push_ffi_string("a\x00b").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ffi_string_null_ptr_errors() {
|
||||
assert!(cstr_ffi::parse_ffi_string(std::ptr::null()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ffi_string_invalid_utf8_errors() {
|
||||
let mut buf: Vec<u8> = vec![b'h', b'i', 0xFF];
|
||||
buf.push(0);
|
||||
let ptr = cptr_of(&buf);
|
||||
assert!(cstr_ffi::parse_ffi_string(ptr).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_string_vec_nullptr_terminated_round_trip() {
|
||||
cstr_ffi::clear_ffi_strings();
|
||||
let head = cstr_ffi::push_ffi_string_vec(&["a", "bb", "ccc"]).unwrap();
|
||||
let collected = cstr_ffi::parse_ffi_string_vec(head).unwrap();
|
||||
assert_eq!(collected, vec!["a", "bb", "ccc"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_string_vec_empty_is_valid_empty_list() {
|
||||
cstr_ffi::clear_ffi_strings();
|
||||
let head = cstr_ffi::push_ffi_string_vec(&[]).unwrap();
|
||||
let collected = cstr_ffi::parse_ffi_string_vec(head).unwrap();
|
||||
assert!(collected.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_string_vec_rejects_interior_nul() {
|
||||
cstr_ffi::clear_ffi_strings();
|
||||
assert!(cstr_ffi::push_ffi_string_vec(&["ok", "ba\x00d"]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ffi_string_vec_null_ptr_errors() {
|
||||
assert!(cstr_ffi::parse_ffi_string_vec(std::ptr::null()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_string_vec_with_len_round_trip() {
|
||||
cstr_ffi::clear_ffi_strings();
|
||||
let (head, count) = cstr_ffi::push_ffi_string_vec_with_len(&["x", "yy", "zzz"]).unwrap();
|
||||
assert_eq!(count, 3);
|
||||
let collected = cstr_ffi::parse_ffi_string_vec_with_len(head, count).unwrap();
|
||||
assert_eq!(collected, vec!["x", "yy", "zzz"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_string_vec_with_len_empty() {
|
||||
cstr_ffi::clear_ffi_strings();
|
||||
let (head, count) = cstr_ffi::push_ffi_string_vec_with_len(&[]).unwrap();
|
||||
assert_eq!(count, 0);
|
||||
let collected = cstr_ffi::parse_ffi_string_vec_with_len(head, count).unwrap();
|
||||
assert!(collected.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ffi_string_vec_with_len_null_zero_len_is_empty() {
|
||||
assert!(
|
||||
cstr_ffi::parse_ffi_string_vec_with_len(std::ptr::null(), 0)
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ffi_string_vec_with_len_null_nonzero_errors() {
|
||||
assert!(cstr_ffi::parse_ffi_string_vec_with_len(std::ptr::null(), 1).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_string_vec_slots_coexist() {
|
||||
cstr_ffi::clear_ffi_strings();
|
||||
let single = cstr_ffi::push_ffi_string("solo").unwrap();
|
||||
let (h1, c1) = cstr_ffi::push_ffi_string_vec_with_len(&["l1a", "l1b"]).unwrap();
|
||||
let h2 = cstr_ffi::push_ffi_string_vec(&["l2a", "l2b", "l2c"]).unwrap();
|
||||
assert_eq!(cstr_ffi::parse_ffi_string(single).unwrap(), "solo");
|
||||
assert_eq!(
|
||||
cstr_ffi::parse_ffi_string_vec_with_len(h1, c1).unwrap(),
|
||||
vec!["l1a", "l1b"]
|
||||
);
|
||||
assert_eq!(
|
||||
cstr_ffi::parse_ffi_string_vec(h2).unwrap(),
|
||||
vec!["l2a", "l2b", "l2c"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_ffi_string_view_points_into_source() {
|
||||
cstr_ffi::clear_ffi_strings();
|
||||
let owned = String::from("a long string that we keep alive");
|
||||
let (ptr, len) = cstr_ffi::push_ffi_string_view(&owned);
|
||||
assert_eq!(len, owned.len());
|
||||
assert_eq!(ptr as *const u8, owned.as_ptr());
|
||||
let parsed = cstr_ffi::parse_ffi_string_view(ptr, len).unwrap();
|
||||
assert_eq!(parsed, owned);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_view_allows_interior_nul() {
|
||||
let src: &str = "ab\x00cd";
|
||||
let (ptr, len) = cstr_ffi::push_ffi_string_view(src);
|
||||
assert_eq!(len, 5);
|
||||
let parsed = cstr_ffi::parse_ffi_string_view(ptr, len).unwrap();
|
||||
assert_eq!(parsed.as_bytes(), src.as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ffi_string_view_null_zero_is_empty() {
|
||||
assert_eq!(cstr_ffi::parse_ffi_string_view(std::ptr::null(), 0).unwrap(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ffi_string_view_null_nonzero_errors() {
|
||||
assert!(cstr_ffi::parse_ffi_string_view(std::ptr::null(), 3).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ffi_string_view_invalid_utf8_errors() {
|
||||
let bad: [u8; 2] = [b'a', 0xFF];
|
||||
let ptr = cptr_of(&bad);
|
||||
assert!(cstr_ffi::parse_ffi_string_view(ptr, 2).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_string_view_vec_with_len_round_trip() {
|
||||
cstr_ffi::clear_ffi_strings();
|
||||
let a = String::from("alpha");
|
||||
let b = String::from("beta");
|
||||
let c = String::from("gamma");
|
||||
let (head, count) = cstr_ffi::push_ffi_string_view_vec_with_len(&[&a, &b, &c]);
|
||||
assert_eq!(count, 3);
|
||||
let parsed = cstr_ffi::parse_ffi_string_view_vec_with_len(head, count).unwrap();
|
||||
assert_eq!(parsed, vec!["alpha", "beta", "gamma"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_string_view_vec_with_len_empty() {
|
||||
cstr_ffi::clear_ffi_strings();
|
||||
let (head, count) = cstr_ffi::push_ffi_string_view_vec_with_len(&[]);
|
||||
assert_eq!(count, 0);
|
||||
// count is 0, so head is ignored (may be dangling); parsing must yield an empty vector.
|
||||
let collected = cstr_ffi::parse_ffi_string_view_vec_with_len(head, count).unwrap();
|
||||
assert!(collected.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_view_vec_allows_interior_nul_entries() {
|
||||
cstr_ffi::clear_ffi_strings();
|
||||
let s1: &str = "x\x00y";
|
||||
let s2: &str = "p\x00\x00q";
|
||||
let (head, count) = cstr_ffi::push_ffi_string_view_vec_with_len(&[s1, s2]);
|
||||
assert_eq!(count, 2);
|
||||
let parsed = cstr_ffi::parse_ffi_string_view_vec_with_len(head, count).unwrap();
|
||||
assert_eq!(parsed[0].as_bytes(), s1.as_bytes());
|
||||
assert_eq!(parsed[1].as_bytes(), s2.as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ffi_string_view_vec_null_zero_len_is_empty() {
|
||||
assert!(cstr_ffi::parse_ffi_string_view_vec_with_len(std::ptr::null(), 0)
|
||||
.unwrap()
|
||||
.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ffi_string_view_vec_null_nonzero_errors() {
|
||||
assert!(cstr_ffi::parse_ffi_string_view_vec_with_len(std::ptr::null(), 1).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_makes_cache_reusable() {
|
||||
cstr_ffi::clear_ffi_strings();
|
||||
let _first = cstr_ffi::push_ffi_string("before-clear").unwrap();
|
||||
cstr_ffi::clear_ffi_strings();
|
||||
let after = cstr_ffi::push_ffi_string("after-clear").unwrap();
|
||||
assert_eq!(cstr_ffi::parse_ffi_string(after).unwrap(), "after-clear");
|
||||
}
|
||||
Reference in New Issue
Block a user