use idakit_sys::{
EXIT_TRAPPED, FATAL_ABORT, FATAL_EXIT, FATAL_INTERR, drop_probe_count, drop_probe_make,
ext_throw_coded, ext_throw_interr, ext_throw_plain_int, last_exit_code, probe_throw,
test_fatal, test_fatal_through_cxx, was_trapped,
};
use rstest::rstest;
const TRAP_EXIT_CODE: i32 = 42;
const TRAP_ABORT_CODE: i32 = 134;
#[rstest]
#[case::exit(FATAL_EXIT, TRAP_EXIT_CODE)]
#[case::abort(FATAL_ABORT, TRAP_ABORT_CODE)]
fn fatal_inside_guarded_call_is_trapped(#[case] kind: i32, #[case] expected_exit_code: i32) {
let rc = unsafe { test_fatal(kind) };
assert_eq!(rc, EXIT_TRAPPED);
assert_eq!(
unsafe { was_trapped() },
1,
"guarded<> must record that it trapped a fatal"
);
assert_eq!(
unsafe { last_exit_code() },
expected_exit_code,
"the trapped status/signal code must survive the longjmp"
);
}
#[test]
#[cfg_attr(
not(target_os = "linux"),
ignore = "interr trapping needs a catchable throw across the libida boundary (Linux-only)"
)]
fn interr_inside_guarded_call_is_trapped() {
let rc = unsafe { test_fatal(FATAL_INTERR) };
assert_eq!(rc, EXIT_TRAPPED);
assert_eq!(
unsafe { was_trapped() },
1,
"guarded<>'s interr_exc_t catch must record a trap the same as the longjmp paths"
);
}
#[rstest]
#[case::exit(FATAL_EXIT, TRAP_EXIT_CODE)]
#[case::abort(FATAL_ABORT, TRAP_ABORT_CODE)]
fn fatal_longjmp_across_cxx_shim_is_trapped(#[case] kind: i32, #[case] expected_exit_code: i32) {
let rc = unsafe { test_fatal_through_cxx(kind) };
assert_eq!(rc, EXIT_TRAPPED);
assert_eq!(
unsafe { was_trapped() },
1,
"guarded<> must record the trap even when the longjmp crossed a cxx shim frame"
);
assert_eq!(
unsafe { last_exit_code() },
expected_exit_code,
"the trapped status/signal code must survive a longjmp across a cxx shim frame"
);
}
#[test]
#[cfg_attr(
not(target_os = "linux"),
ignore = "interr throws across the libida boundary only on Linux"
)]
fn interr_across_cxx_shim_is_intercepted_by_cxx() {
let rc = unsafe { test_fatal_through_cxx(FATAL_INTERR) };
assert_eq!(
rc, 1,
"cxx should intercept the interr throw before the trap"
);
assert_eq!(
unsafe { was_trapped() },
0,
"the throw never reaches guarded<>'s own catch, so it must not be recorded as a trap"
);
}
#[rstest]
#[case::runtime_error(0, "probe_throw: runtime_error from C++")]
#[case::out_of_range(1, "probe_throw: out_of_range from C++")]
fn cxx_surfaces_std_exception_as_flat_err(#[case] kind: i32, #[case] expected: &str) {
let err = probe_throw(kind).unwrap_err();
assert_eq!(err.what(), expected);
}
#[test]
fn custom_trycatch_catches_non_std_exception() {
let err = ext_throw_plain_int().unwrap_err();
assert!(
err.what().contains("non-std::exception"),
"catch(...) arm should surface a non-std::exception throw as Err, got: {}",
err.what()
);
}
#[test]
fn custom_trycatch_enriches_interr_with_its_code() {
let err = ext_throw_interr(42).unwrap_err();
assert!(
err.what().contains("code=42"),
"interr arm should format the code into the message, got: {}",
err.what()
);
}
#[test]
fn cxx_exception_only_carries_a_string_code_must_be_encoded() {
let err = ext_throw_coded(7).unwrap_err();
let msg = err.what();
let code: i32 = msg
.strip_prefix("idakit:qerrno=")
.expect("encoded code prefix")
.parse()
.expect("code parses back out of the message");
assert_eq!(code, 7, "the only channel is the what() string");
}
#[test]
fn cxx_unique_ptr_runs_cpp_deleter_on_drop() {
assert_eq!(drop_probe_count(), 0, "counter should start clean");
let probe = drop_probe_make();
assert!(!probe.is_null(), "make should hand back a live handle");
assert_eq!(
drop_probe_count(),
0,
"the deleter must not run while the UniquePtr is alive"
);
drop(probe);
assert_eq!(
drop_probe_count(),
1,
"dropping the UniquePtr must run the C++ destructor exactly once"
);
}