#![allow(clippy::expect_used, clippy::panic)]
use super::panic::catch_callback_panic;
use super::{HostStage, LEAN_ERROR_MESSAGE_LIMIT, LeanDiagnosticCode, LeanError, LeanExceptionKind};
#[test]
fn host_constructor_bounds_oversize_message() {
let oversize = "x".repeat(LEAN_ERROR_MESSAGE_LIMIT + 1024);
let LeanError::Host(host) = LeanError::abi_conversion(oversize) else {
panic!("expected Host variant");
};
assert!(host.message().len() <= LEAN_ERROR_MESSAGE_LIMIT);
assert_eq!(host.stage(), HostStage::Conversion);
assert_eq!(host.code(), LeanDiagnosticCode::AbiConversion);
}
#[test]
fn host_constructor_passes_short_message_through() {
let LeanError::Host(host) = LeanError::abi_conversion("ok") else {
panic!("expected Host variant");
};
assert_eq!(host.message(), "ok");
assert_eq!(host.stage(), HostStage::Conversion);
assert_eq!(host.code(), LeanDiagnosticCode::AbiConversion);
}
#[test]
fn lean_error_code_projects_from_variant() {
assert_eq!(LeanError::linking("x").code(), LeanDiagnosticCode::Linking);
assert_eq!(LeanError::module_init("x").code(), LeanDiagnosticCode::ModuleInit);
assert_eq!(LeanError::symbol_lookup("x").code(), LeanDiagnosticCode::SymbolLookup);
assert_eq!(LeanError::runtime_init("x").code(), LeanDiagnosticCode::RuntimeInit);
assert_eq!(
LeanError::lean_exception(LeanExceptionKind::UserError, "x").code(),
LeanDiagnosticCode::LeanException,
);
}
#[test]
fn diagnostic_code_as_str_is_stable() {
assert_eq!(LeanDiagnosticCode::RuntimeInit.as_str(), "lean_rs.runtime_init");
assert_eq!(LeanDiagnosticCode::Linking.as_str(), "lean_rs.linking");
assert_eq!(LeanDiagnosticCode::ModuleInit.as_str(), "lean_rs.module_init");
assert_eq!(LeanDiagnosticCode::SymbolLookup.as_str(), "lean_rs.symbol_lookup");
assert_eq!(LeanDiagnosticCode::AbiConversion.as_str(), "lean_rs.abi_conversion");
assert_eq!(LeanDiagnosticCode::LeanException.as_str(), "lean_rs.lean_exception");
assert_eq!(LeanDiagnosticCode::Elaboration.as_str(), "lean_rs.elaboration");
assert_eq!(LeanDiagnosticCode::Unsupported.as_str(), "lean_rs.unsupported");
assert_eq!(LeanDiagnosticCode::Internal.as_str(), "lean_rs.internal");
}
#[test]
fn lean_exception_constructor_bounds_oversize_message() {
let oversize: String = "\u{1F600}".repeat(LEAN_ERROR_MESSAGE_LIMIT / 2);
let LeanError::LeanException(exc) = LeanError::lean_exception(LeanExceptionKind::UserError, oversize) else {
panic!("expected LeanException variant");
};
assert!(exc.message().len() <= LEAN_ERROR_MESSAGE_LIMIT);
assert!(exc.message().is_char_boundary(exc.message().len()));
assert_eq!(exc.kind(), LeanExceptionKind::UserError);
}
#[test]
fn lean_error_display_includes_stage_and_message() {
let err = LeanError::runtime_init("boom");
let rendered = err.to_string();
assert!(rendered.starts_with("lean-rs:"), "got {rendered:?}");
assert!(rendered.contains("RuntimeInit"), "got {rendered:?}");
assert!(rendered.contains("boom"), "got {rendered:?}");
assert!(
rendered.contains("lean_rs.runtime_init"),
"expected diagnostic code in render, got {rendered:?}"
);
}
#[test]
fn lean_error_display_includes_kind_and_message() {
let err = LeanError::lean_exception(LeanExceptionKind::UserError, "kaboom");
let rendered = err.to_string();
assert!(rendered.starts_with("lean-rs:"), "got {rendered:?}");
assert!(rendered.contains("UserError"), "got {rendered:?}");
assert!(rendered.contains("kaboom"), "got {rendered:?}");
}
#[test]
fn catch_callback_panic_returns_ok_when_closure_returns_ok() {
let outcome: LeanError = match catch_callback_panic(|| Ok::<u32, _>(7)) {
Ok(value) => {
assert_eq!(value, 7);
return;
}
Err(e) => e,
};
panic!("expected Ok, got {outcome:?}");
}
#[test]
fn catch_callback_panic_propagates_explicit_lean_error() {
let err = catch_callback_panic(|| Err::<(), _>(LeanError::abi_conversion("explicit")))
.expect_err("closure returned Err; helper should pass it through");
let LeanError::Host(host) = err else {
panic!("expected Host");
};
assert_eq!(host.stage(), HostStage::Conversion);
assert_eq!(host.message(), "explicit");
}
#[test]
fn catch_callback_panic_converts_str_payload_into_callback_panic() {
let err = catch_callback_panic::<_, ()>(|| panic!("boom")).expect_err("closure panicked");
let LeanError::Host(host) = err else {
panic!("expected Host");
};
assert_eq!(host.stage(), HostStage::CallbackPanic);
assert!(host.message().contains("boom"), "got {:?}", host.message());
}
#[test]
fn catch_callback_panic_converts_string_payload_into_callback_panic() {
let err = catch_callback_panic::<_, ()>(|| panic!("dynamic: {}", 42)).expect_err("closure panicked");
let LeanError::Host(host) = err else {
panic!("expected Host");
};
assert_eq!(host.stage(), HostStage::CallbackPanic);
assert!(host.message().contains("dynamic: 42"), "got {:?}", host.message());
}
#[test]
fn catch_callback_panic_bounds_oversize_panic_payload() {
let huge: String = "x".repeat(LEAN_ERROR_MESSAGE_LIMIT + 4096);
let err = catch_callback_panic::<_, ()>(move || panic!("{huge}")).expect_err("closure panicked");
let LeanError::Host(host) = err else {
panic!("expected Host");
};
assert!(host.message().len() <= LEAN_ERROR_MESSAGE_LIMIT);
}
#[test]
fn catch_callback_panic_loop_remains_independent() {
let iters = std::env::var("LEAN_RS_REFCOUNT_STRESS_ITERS")
.ok()
.and_then(|raw| raw.parse::<usize>().ok())
.unwrap_or(512);
for i in 0..iters {
let err = catch_callback_panic::<_, ()>(move || panic!("iter {i}")).expect_err("closure panicked");
let LeanError::Host(host) = err else {
panic!("expected Host");
};
assert_eq!(host.stage(), HostStage::CallbackPanic);
let expected = format!("iter {i}");
assert!(
host.message().contains(&expected),
"iteration {i} produced unrelated payload {:?}",
host.message(),
);
}
}
#[test]
fn catch_callback_panic_handles_non_string_payload() {
struct DisplayMarker;
impl std::fmt::Display for DisplayMarker {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("display-marker")
}
}
let err = catch_callback_panic::<_, ()>(|| std::panic::panic_any(DisplayMarker))
.expect_err("panic_any with custom payload should still be caught");
let LeanError::Host(host) = err else {
panic!("expected Host");
};
assert_eq!(host.stage(), HostStage::CallbackPanic);
assert!(
!host.message().is_empty(),
"non-string panic payloads must still surface a non-empty diagnostic",
);
assert!(host.message().len() <= LEAN_ERROR_MESSAGE_LIMIT);
}