#![allow(clippy::expect_used)]
use polyplug::ffi::polyplug_runtime_create;
use polyplug::ffi::polyplug_runtime_destroy;
use polyplug::runtime::host_get_last_error;
use polyplug_abi::HostApi;
fn drain_last_error(host: *const HostApi) -> Vec<u8> {
let mut buf: [u8; 4096] = [0_u8; 4096];
let n: usize = unsafe { ((*host).get_last_error)(host, buf.as_mut_ptr(), buf.len()) };
buf[..n].to_vec()
}
fn peek_error_len(host: *const HostApi) -> usize {
unsafe { ((*host).get_error_len)(host) }
}
fn clear_error(host: *const HostApi) {
drain_last_error(host);
}
fn trigger_error(host: *const HostApi) {
let path: &[u8] = b"/nonexistent/path/that/does/not/exist";
let mut result: polyplug_abi::AbiError = polyplug_abi::AbiError::ok();
unsafe { ((*host).load_bundle)(host, path.as_ptr(), path.len(), &mut result) };
assert_ne!(
result.code,
polyplug_abi::AbiErrorCode::Ok as u32,
"load_bundle with non-existent path must return error"
);
}
#[test]
fn last_error_empty_on_fresh_runtime() {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null(), "runtime creation must succeed");
clear_error(host);
let mut buf: [u8; 64] = [0_u8; 64];
let n: usize = unsafe { ((*host).get_last_error)(host, buf.as_mut_ptr(), buf.len()) };
assert_eq!(
n, 0,
"get_last_error must return 0 when no error is pending"
);
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn last_error_cleared_after_read() {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null(), "runtime creation must succeed");
clear_error(host);
trigger_error(host);
let first: Vec<u8> = drain_last_error(host);
assert!(
!first.is_empty(),
"first read of get_last_error must return non-empty message"
);
let mut buf: [u8; 256] = [0_u8; 256];
let n: usize = unsafe { ((*host).get_last_error)(host, buf.as_mut_ptr(), buf.len()) };
assert_eq!(
n, 0,
"get_last_error must return 0 after the error was already read and cleared"
);
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn error_len_does_not_clear_error() {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null(), "runtime creation must succeed");
clear_error(host);
trigger_error(host);
let len_before: usize = peek_error_len(host);
assert!(
len_before > 0,
"get_error_len must report a non-zero length after trigger_error"
);
let msg: Vec<u8> = drain_last_error(host);
assert_eq!(
msg.len(),
len_before,
"get_last_error length must match the value reported by get_error_len"
);
let len_after: usize = peek_error_len(host);
assert_eq!(
len_after, 0,
"get_error_len must report 0 after get_last_error has been called"
);
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn last_error_truncates_to_buf_len() {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null(), "runtime creation must succeed");
clear_error(host);
trigger_error(host);
let full_len: usize = peek_error_len(host);
assert!(full_len > 0, "error must be non-empty for truncation test");
if full_len < 2 {
drain_last_error(host);
unsafe { polyplug_runtime_destroy(host) };
return;
}
let truncated_len: usize = full_len - 1;
let mut buf: Vec<u8> = vec![0xAA_u8; truncated_len];
let n: usize = unsafe { ((*host).get_last_error)(host, buf.as_mut_ptr(), buf.len()) };
assert_eq!(
n, truncated_len,
"get_last_error must return the number of bytes written (truncated to buf_len)"
);
let mut probe: [u8; 8] = [0_u8; 8];
let n2: usize = unsafe { ((*host).get_last_error)(host, probe.as_mut_ptr(), probe.len()) };
assert_eq!(
n2, 0,
"get_last_error must be cleared even after a truncated read"
);
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn last_error_zero_buf_len_clears_error() {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null(), "runtime creation must succeed");
clear_error(host);
trigger_error(host);
assert!(
peek_error_len(host) > 0,
"error must be set before zero-buf-len test"
);
let mut byte: u8 = 0xBB_u8;
let n: usize = unsafe { ((*host).get_last_error)(host, &mut byte as *mut u8, 0) };
assert_eq!(
n, 0,
"get_last_error with buf_len=0 must return 0 written bytes"
);
let n2: usize = peek_error_len(host);
assert_eq!(
n2, 0,
"get_last_error with buf_len=0 must still clear the error"
);
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn last_error_null_buf_zero_len_clears_error() {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null(), "runtime creation must succeed");
clear_error(host);
trigger_error(host);
let error_len: usize = peek_error_len(host);
assert!(error_len > 0, "error must be set before null-buf test");
let n: usize = unsafe { ((*host).get_last_error)(host, core::ptr::null_mut(), 0) };
assert_eq!(
n, error_len,
"get_last_error(null buf, 0) must return error length"
);
let n2: usize = peek_error_len(host);
assert_eq!(n2, 0, "get_last_error(null buf, 0) must clear the error");
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn last_error_per_runtime_isolation() {
let host1: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host1.is_null(), "runtime 1 creation must succeed");
let host2: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host2.is_null(), "runtime 2 creation must succeed");
clear_error(host1);
clear_error(host2);
trigger_error(host1);
let host1_len: usize = peek_error_len(host1);
assert!(host1_len > 0, "host1 must have an error");
let host2_len: usize = peek_error_len(host2);
assert_eq!(host2_len, 0, "host2 must have no error");
drain_last_error(host1);
assert_eq!(peek_error_len(host1), 0, "host1 error cleared");
assert_eq!(peek_error_len(host2), 0, "host2 still has no error");
unsafe { polyplug_runtime_destroy(host1) };
unsafe { polyplug_runtime_destroy(host2) };
}
#[test]
fn last_error_no_write_when_empty() {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null(), "runtime creation must succeed");
clear_error(host);
let sentinel: u8 = 0xDE_u8;
let mut buf: [u8; 16] = [sentinel; 16];
let n: usize = unsafe { ((*host).get_last_error)(host, buf.as_mut_ptr(), buf.len()) };
assert_eq!(n, 0, "must return 0 when no error is pending");
for (i, &byte) in buf.iter().enumerate() {
assert_eq!(
byte, sentinel,
"buf[{i}] was modified even though get_last_error had nothing to write"
);
}
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn last_error_exact_buf_len_writes_full_message() {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null(), "runtime creation must succeed");
clear_error(host);
trigger_error(host);
let full_len: usize = peek_error_len(host);
assert!(full_len > 0, "error must be non-empty for exact-len test");
let mut buf: Vec<u8> = vec![0_u8; full_len];
let n: usize = unsafe { ((*host).get_last_error)(host, buf.as_mut_ptr(), buf.len()) };
assert_eq!(
n, full_len,
"get_last_error with buf_len == message_len must return full_len"
);
let msg_result: Result<&str, core::str::Utf8Error> = core::str::from_utf8(&buf);
assert!(msg_result.is_ok(), "LAST_ERROR must be valid UTF-8");
let msg: &str = msg_result.unwrap_or("");
assert!(!msg.is_empty(), "decoded error message must be non-empty");
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn last_error_large_message_handling() {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null(), "polyplug_runtime_create must succeed");
clear_error(host);
let long_path: Vec<u8> = core::iter::repeat_n(b'x', 512).collect();
let mut result: polyplug_abi::AbiError = polyplug_abi::AbiError::ok();
unsafe { ((*host).load_bundle)(host, long_path.as_ptr(), long_path.len(), &mut result) };
assert_ne!(
result.code,
polyplug_abi::AbiErrorCode::Ok as u32,
"load_bundle with non-existent path must fail"
);
let msg_len: usize = peek_error_len(host);
if msg_len == 0 {
unsafe { polyplug_runtime_destroy(host) };
return;
}
let mut buf: Vec<u8> = vec![0_u8; msg_len];
let n: usize = unsafe { ((*host).get_last_error)(host, buf.as_mut_ptr(), buf.len()) };
assert_eq!(n, msg_len, "large-message read must return msg_len bytes");
assert!(
core::str::from_utf8(&buf).is_ok(),
"large LAST_ERROR message must be valid UTF-8"
);
let after: usize = peek_error_len(host);
assert_eq!(
after, 0,
"LAST_ERROR must be cleared after large-message read"
);
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn last_error_repeated_cycles_independent() {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null(), "runtime creation must succeed");
clear_error(host);
for _round in 0_u32..16_u32 {
trigger_error(host);
let len: usize = peek_error_len(host);
assert!(len > 0, "each cycle must produce a non-empty error");
let msg: Vec<u8> = drain_last_error(host);
assert_eq!(
msg.len(),
len,
"drained message length must match peek_error_len"
);
let after: usize = peek_error_len(host);
assert_eq!(
after, 0,
"LAST_ERROR must be empty after drain in each cycle"
);
}
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn last_error_null_host_returns_zero() {
let mut buf: [u8; 256] = [0_u8; 256];
let n: usize = unsafe { host_get_last_error(core::ptr::null(), buf.as_mut_ptr(), buf.len()) };
assert!(
n == 0,
"get_last_error with null host must return 0 (no host to have an error)"
);
}