use crate::runner::{Backend, Database, HealthCheck, Mode, Phase, Settings, Verbosity};
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int};
use std::ptr;
pub(crate) fn last_error_string() -> String {
let p = hegel_c::hegel_last_error_message();
if p.is_null() {
return String::new(); }
unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
}
fn cstring_lossy(s: &str) -> CString {
CString::new(s).unwrap_or_else(|_| CString::new(s.replace('\0', "\u{FFFD}")).unwrap())
}
pub(crate) struct SettingsHandle {
raw: *mut hegel_c::HegelSettings,
}
impl SettingsHandle {
pub(crate) fn build(settings: &Settings, database_key: Option<&str>) -> Self {
let raw = hegel_c::hegel_settings_new();
unsafe {
hegel_c::hegel_settings_mode(raw, map_mode(settings.mode));
hegel_c::hegel_settings_test_cases(raw, settings.test_cases);
hegel_c::hegel_settings_verbosity(raw, map_verbosity(settings.verbosity));
match settings.seed {
Some(seed) => hegel_c::hegel_settings_seed(raw, seed, true),
None => hegel_c::hegel_settings_seed(raw, 0, false),
}
hegel_c::hegel_settings_derandomize(raw, settings.derandomize);
hegel_c::hegel_settings_report_multiple_failures(
raw,
settings.report_multiple_failures,
);
match &settings.database {
Database::Disabled => {
let empty = CString::new("").unwrap();
hegel_c::hegel_settings_database(raw, empty.as_ptr());
}
Database::Path(path) => {
let c = cstring_lossy(path);
hegel_c::hegel_settings_database(raw, c.as_ptr());
}
Database::Unset => {}
}
if let Some(key) = database_key {
let c = cstring_lossy(key);
hegel_c::hegel_settings_database_key(raw, c.as_ptr());
}
hegel_c::hegel_settings_phases(raw, phases_bitmask(&settings.phases));
hegel_c::hegel_settings_suppress_health_check(
raw,
health_check_bitmask(&settings.suppress_health_check),
);
hegel_c::hegel_settings_backend(raw, map_backend(settings.backend));
}
SettingsHandle { raw }
}
pub(crate) fn as_ptr(&self) -> *const hegel_c::HegelSettings {
self.raw
}
}
impl Drop for SettingsHandle {
fn drop(&mut self) {
unsafe { hegel_c::hegel_settings_free(self.raw) };
}
}
pub(crate) struct RunHandle {
raw: *mut hegel_c::HegelRun,
}
impl RunHandle {
pub(crate) fn start(settings: &SettingsHandle) -> Result<Self, String> {
let raw = unsafe { hegel_c::hegel_run_start(settings.as_ptr()) };
if raw.is_null() {
return Err(last_error_string()); }
Ok(RunHandle { raw })
}
pub(crate) fn next_test_case(&self) -> Option<CTestCase> {
let raw = unsafe { hegel_c::hegel_next_test_case(self.raw) };
if raw.is_null() {
None
} else {
Some(CTestCase { raw, owned: false })
}
}
pub(crate) fn result(&self) -> RunResult<'_> {
let raw = unsafe { hegel_c::hegel_run_result(self.raw) };
RunResult {
raw,
_run: std::marker::PhantomData,
}
}
}
impl Drop for RunHandle {
fn drop(&mut self) {
unsafe { hegel_c::hegel_run_free(self.raw) };
}
}
pub(crate) struct CTestCase {
raw: *mut hegel_c::HegelTestCase,
owned: bool,
}
unsafe impl Send for CTestCase {}
unsafe impl Sync for CTestCase {}
impl CTestCase {
pub(crate) fn from_blob(settings: &SettingsHandle, blob: &str) -> Result<Self, String> {
let c_blob = cstring_lossy(blob);
let raw = unsafe { hegel_c::hegel_test_case_from_blob(settings.as_ptr(), c_blob.as_ptr()) };
if raw.is_null() {
return Err(last_error_string());
}
Ok(CTestCase { raw, owned: true })
}
pub(crate) fn generate(&self, schema_cbor: &[u8]) -> Result<Vec<u8>, c_int> {
let mut out_ptr: *const u8 = ptr::null();
let mut out_len: usize = 0;
let rc = unsafe {
hegel_c::hegel_generate(
self.raw,
schema_cbor.as_ptr(),
schema_cbor.len(),
&mut out_ptr,
&mut out_len,
)
};
if rc != hegel_c::HEGEL_OK {
return Err(rc);
}
let bytes = unsafe { std::slice::from_raw_parts(out_ptr, out_len) };
Ok(bytes.to_vec())
}
pub(crate) fn start_span(&self, label: u64) -> Result<(), c_int> {
rc_to_unit(unsafe { hegel_c::hegel_start_span(self.raw, label) })
}
pub(crate) fn stop_span(&self, discard: bool) -> Result<(), c_int> {
rc_to_unit(unsafe { hegel_c::hegel_stop_span(self.raw, discard) })
}
pub(crate) fn new_collection(
&self,
min_size: u64,
max_size: Option<u64>,
) -> Result<i64, c_int> {
let mut id: i64 = 0;
let rc = unsafe {
hegel_c::hegel_new_collection(self.raw, min_size, max_size.unwrap_or(u64::MAX), &mut id)
};
rc_to_value(rc, id)
}
pub(crate) fn collection_more(&self, collection_id: i64) -> Result<bool, c_int> {
let mut more = false;
let rc = unsafe { hegel_c::hegel_collection_more(self.raw, collection_id, &mut more) };
rc_to_value(rc, more)
}
pub(crate) fn collection_reject(
&self,
collection_id: i64,
why: Option<&str>,
) -> Result<(), c_int> {
let c_why = why.map(cstring_lossy);
let why_ptr = c_why.as_ref().map_or(ptr::null(), |c| c.as_ptr());
rc_to_unit(unsafe { hegel_c::hegel_collection_reject(self.raw, collection_id, why_ptr) })
}
pub(crate) fn new_pool(&self) -> Result<i64, c_int> {
let mut id: i64 = 0;
let rc = unsafe { hegel_c::hegel_new_pool(self.raw, &mut id) };
rc_to_value(rc, id)
}
pub(crate) fn pool_add(&self, pool_id: i64) -> Result<i64, c_int> {
let mut id: i64 = 0;
let rc = unsafe { hegel_c::hegel_pool_add(self.raw, pool_id, &mut id) };
rc_to_value(rc, id)
}
pub(crate) fn pool_generate(&self, pool_id: i64, consume: bool) -> Result<i64, c_int> {
let mut id: i64 = 0;
let rc = unsafe { hegel_c::hegel_pool_generate(self.raw, pool_id, consume, &mut id) };
rc_to_value(rc, id)
}
pub(crate) fn new_state_machine(
&self,
rule_names: &[&str],
invariant_names: &[&str],
) -> Result<i64, c_int> {
let rule_cstrings: Vec<CString> = rule_names.iter().map(|s| cstring_lossy(s)).collect();
let invariant_cstrings: Vec<CString> =
invariant_names.iter().map(|s| cstring_lossy(s)).collect();
let rule_ptrs: Vec<*const c_char> = rule_cstrings.iter().map(|c| c.as_ptr()).collect();
let invariant_ptrs: Vec<*const c_char> =
invariant_cstrings.iter().map(|c| c.as_ptr()).collect();
let mut id: i64 = 0;
let rc = unsafe {
hegel_c::hegel_new_state_machine(
self.raw,
rule_ptrs.as_ptr(),
rule_ptrs.len(),
invariant_ptrs.as_ptr(),
invariant_ptrs.len(),
&mut id,
)
};
rc_to_value(rc, id)
}
pub(crate) fn state_machine_next_rule(&self, state_machine_id: i64) -> Result<i64, c_int> {
let mut out: i64 = 0;
let rc =
unsafe { hegel_c::hegel_state_machine_next_rule(self.raw, state_machine_id, &mut out) };
rc_to_value(rc, out)
}
pub(crate) fn target(&self, score: f64, label: &str) -> Result<(), c_int> {
let c_label = cstring_lossy(label);
rc_to_unit(unsafe { hegel_c::hegel_target(self.raw, score, c_label.as_ptr()) })
}
pub(crate) fn mark_complete(
&self,
status: hegel_c::hegel_status_t,
origin: Option<&str>,
) -> Result<(), c_int> {
let c_origin = origin.map(cstring_lossy);
let origin_ptr = c_origin.as_ref().map_or(ptr::null(), |c| c.as_ptr());
rc_to_unit(unsafe { hegel_c::hegel_mark_complete(self.raw, status, origin_ptr) })
}
pub(crate) fn is_final_replay(&self) -> bool {
unsafe { hegel_c::hegel_test_case_is_final_replay(self.raw) }
}
}
impl Drop for CTestCase {
fn drop(&mut self) {
if self.owned {
unsafe { hegel_c::hegel_test_case_free(self.raw) };
}
}
}
pub(crate) struct RunResult<'run> {
raw: *const hegel_c::HegelRunResult,
_run: std::marker::PhantomData<&'run RunHandle>,
}
impl RunResult<'_> {
pub(crate) fn status(&self) -> hegel_c::hegel_run_status_t {
unsafe { hegel_c::hegel_run_result_status(self.raw) }
}
pub(crate) fn error(&self) -> Option<String> {
let p = unsafe { hegel_c::hegel_run_result_error(self.raw) };
cstr_opt(p)
}
pub(crate) fn failure_count(&self) -> usize {
unsafe { hegel_c::hegel_run_result_failure_count(self.raw) }
}
pub(crate) fn failure(&self, index: usize) -> Option<Failure> {
let f = unsafe { hegel_c::hegel_run_result_failure(self.raw, index) };
if f.is_null() {
return None;
}
Some(Failure {
panic_message: cstr_opt(unsafe { hegel_c::hegel_failure_panic_message(f) })
.unwrap_or_default(),
reproduce_blob: cstr_opt(unsafe { hegel_c::hegel_failure_reproduction_blob(f) }),
})
}
}
pub(crate) struct Failure {
pub(crate) panic_message: String,
pub(crate) reproduce_blob: Option<String>,
}
fn rc_to_unit(rc: c_int) -> Result<(), c_int> {
if rc == hegel_c::HEGEL_OK {
Ok(())
} else {
Err(rc)
}
}
fn rc_to_value<T>(rc: c_int, value: T) -> Result<T, c_int> {
if rc == hegel_c::HEGEL_OK {
Ok(value)
} else {
Err(rc)
}
}
fn cstr_opt(p: *const c_char) -> Option<String> {
if p.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned())
}
}
fn map_mode(mode: Mode) -> hegel_c::hegel_mode_t {
match mode {
Mode::TestRun => hegel_c::hegel_mode_t::HEGEL_MODE_TEST_RUN,
Mode::SingleTestCase => hegel_c::hegel_mode_t::HEGEL_MODE_SINGLE_TEST_CASE,
}
}
fn map_verbosity(v: Verbosity) -> hegel_c::hegel_verbosity_t {
match v {
Verbosity::Quiet => hegel_c::hegel_verbosity_t::HEGEL_VERBOSITY_QUIET,
Verbosity::Normal => hegel_c::hegel_verbosity_t::HEGEL_VERBOSITY_NORMAL,
Verbosity::Verbose => hegel_c::hegel_verbosity_t::HEGEL_VERBOSITY_VERBOSE,
Verbosity::Debug => hegel_c::hegel_verbosity_t::HEGEL_VERBOSITY_DEBUG,
}
}
fn map_backend(backend: Option<Backend>) -> hegel_c::hegel_backend_t {
match backend {
None => hegel_c::hegel_backend_t::HEGEL_BACKEND_AUTO,
Some(Backend::Default) => hegel_c::hegel_backend_t::HEGEL_BACKEND_DEFAULT,
Some(Backend::Urandom) => hegel_c::hegel_backend_t::HEGEL_BACKEND_URANDOM,
}
}
fn phases_bitmask(phases: &[Phase]) -> u32 {
let mut mask = 0;
for phase in phases {
mask |= match phase {
Phase::Explicit => hegel_c::HEGEL_PHASE_EXPLICIT,
Phase::Reuse => hegel_c::HEGEL_PHASE_REUSE,
Phase::Generate => hegel_c::HEGEL_PHASE_GENERATE,
Phase::Target => hegel_c::HEGEL_PHASE_TARGET,
Phase::Shrink => hegel_c::HEGEL_PHASE_SHRINK,
};
}
mask
}
fn health_check_bitmask(checks: &[HealthCheck]) -> u32 {
let mut mask = 0;
for check in checks {
mask |= match check {
HealthCheck::FilterTooMuch => hegel_c::HEGEL_HC_FILTER_TOO_MUCH,
HealthCheck::TooSlow => hegel_c::HEGEL_HC_TOO_SLOW,
HealthCheck::TestCasesTooLarge => hegel_c::HEGEL_HC_TEST_CASES_TOO_LARGE,
HealthCheck::LargeInitialTestCase => hegel_c::HEGEL_HC_LARGE_INITIAL_TEST_CASE,
};
}
mask
}
#[cfg(test)]
#[path = "../tests/embedded/ffi_tests.rs"]
mod tests;