//! Integration tests for the `cffi_wrapper!` macro and the parameter-direction macros. //! //! The wrapper functions below mirror the real FFI surface -- parameters typed via `in_param_ty!` //! / `out_param_ty!` and a `CError` return -- but omit the `extern "C"` / `no_mangle` attributes. //! Each is exercised by a `#[test]` caller, in the same shape as a real `WFProgramCreate`-style //! FFI function. //! //! Note: on the error path the value of any output parameter is undefined behavior; the error-case //! tests provide a valid pointer but intentionally never read it back. use sarasacw_omrf::last_error::{self, CError, CERROR_OK, CStrPtr}; use sarasacw_omrf::{cffi_wrapper, in_param_ty, out_param_ty}; use thiserror::Error; // ---- project-side conventions required by `cffi_wrapper!` ---- #[derive(Debug, Error)] enum TestErr { #[error("not found")] NotFound, #[error("bad input")] Bad, #[error("parse: {0}")] Parse(#[from] core::num::ParseIntError), } impl From for CError { fn from(e: TestErr) -> Self { match e { TestErr::NotFound => 2, TestErr::Bad => 3, TestErr::Parse(_) => 4, } } } type Result = core::result::Result; fn parse_cstr(ptr: CStrPtr) -> String { assert!(!ptr.is_null()); unsafe { std::ffi::CStr::from_ptr(ptr) } .to_string_lossy() .into_owned() } // ============ FFI-style wrapper functions ============ /// Coverage: no inputs, no outputs -- success path. Returns absolute success and leaves no last /// error. fn ping() -> CError { cffi_wrapper!(|| { Ok(()) }) } /// Coverage: no inputs, no outputs -- error path. Returns the mapped code and records the message. fn ping_fail() -> CError { cffi_wrapper!(|| { Err(TestErr::NotFound) }) } /// Coverage: one input, no outputs. Succeeds for non-negative input, fails (`Bad`) otherwise. fn validate(x: in_param_ty!(i32)) -> CError { cffi_wrapper!(|x: i32| { if x < 0 { Err(TestErr::Bad) } else { Ok(()) } }) } /// Coverage: no inputs, one output -- success path. Writes the result through the output pointer. fn make_answer(out: out_param_ty!(i32)) -> CError { cffi_wrapper!(|| -> (out: i32) { Ok(42) }) } /// Coverage: no inputs, one output -- error path. The output value is UB after the call. fn make_fail(out: out_param_ty!(i32)) -> CError { cffi_wrapper!(|| -> (out: i32) { Err(TestErr::NotFound) }) } /// Coverage: one input, one output. fn scale(x: in_param_ty!(i32), out: out_param_ty!(i32)) -> CError { cffi_wrapper!(|x: i32| -> (out: i32) { Ok(x * 2) }) } /// Coverage: two inputs, two outputs (structured destructuring assignment through the pointers). fn add_mul( a: in_param_ty!(i32), b: in_param_ty!(i32), sum: out_param_ty!(i32), prod: out_param_ty!(i32), ) -> CError { cffi_wrapper!(|a: i32, b: i32| -> (sum: i32, prod: i32) { Ok((a + b, a * b)) }) } /// Coverage: no inputs, two outputs (the no-input branch of the multi-output shape). fn make_pair(o1: out_param_ty!(i32), o2: out_param_ty!(i32)) -> CError { cffi_wrapper!(|| -> (o1: i32, o2: i32) { Ok((10, 20)) }) } /// Coverage: the `?` operator propagating a sub-error (`ParseIntError`) into `TestErr` via the /// `#[from]` conversion, exactly like the `WFProgramCreate` example chains `?`. fn parse_num(s: in_param_ty!(&str), out: out_param_ty!(i32)) -> CError { cffi_wrapper!(|s: &str| -> (out: i32) { let n: i32 = s.parse()?; Ok(n) }) } // ============ test callers ============ #[test] fn ping_returns_success() { last_error::clear_last_error(); assert_eq!(ping(), CERROR_OK); assert!(!last_error::has_last_message()); } #[test] fn ping_fail_records_error() { last_error::clear_last_error(); assert_eq!(ping_fail(), 2); assert_eq!(parse_cstr(last_error::get_error_message()), "not found"); } #[test] fn validate_accepts_non_negative() { last_error::clear_last_error(); assert_eq!(validate(5), CERROR_OK); } #[test] fn validate_rejects_negative() { last_error::clear_last_error(); assert_eq!(validate(-1), 3); assert_eq!(parse_cstr(last_error::get_error_message()), "bad input"); } #[test] fn make_answer_writes_output() { last_error::clear_last_error(); let mut out: i32 = 0; assert_eq!(make_answer(&mut out), CERROR_OK); assert_eq!(out, 42); } #[test] fn make_fail_records_error() { last_error::clear_last_error(); let mut out: i32 = 0; // A valid pointer is required, but its value is UB after an error and is intentionally unread. assert_eq!(make_fail(&mut out), 2); assert_eq!(parse_cstr(last_error::get_error_message()), "not found"); } #[test] fn scale_writes_doubled() { last_error::clear_last_error(); let mut out: i32 = 0; assert_eq!(scale(21, &mut out), CERROR_OK); assert_eq!(out, 42); } #[test] fn add_mul_writes_both_outputs() { last_error::clear_last_error(); let mut sum: i32 = 0; let mut prod: i32 = 0; assert_eq!(add_mul(3, 4, &mut sum, &mut prod), CERROR_OK); assert_eq!(sum, 7); assert_eq!(prod, 12); } #[test] fn make_pair_writes_both_outputs() { last_error::clear_last_error(); let mut a: i32 = 0; let mut b: i32 = 0; assert_eq!(make_pair(&mut a, &mut b), CERROR_OK); assert_eq!(a, 10); assert_eq!(b, 20); } #[test] fn parse_num_success() { last_error::clear_last_error(); let mut out: i32 = 0; assert_eq!(parse_num("123", &mut out), CERROR_OK); assert_eq!(out, 123); } #[test] fn parse_num_fail_propagates_suberror() { last_error::clear_last_error(); let mut out: i32 = 0; // Output value is UB after an error and is intentionally unread. assert_eq!(parse_num("not_a_num", &mut out), 4); assert!(parse_cstr(last_error::get_error_message()).contains("parse")); }