use crate::runner::{Backend, Database, HealthCheck, Mode, Phase, Settings, Verbosity};
use hegel_c::hegel_result_t;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ptr;
struct Context {
raw: *mut hegel_c::HegelContext,
}
impl Context {
fn new() -> Self {
Context {
raw: hegel_c::hegel_context_new(),
}
}
fn as_ptr(&self) -> *mut hegel_c::HegelContext {
self.raw
}
fn last_error(&self) -> String {
let p = unsafe { hegel_c::hegel_context_last_error(self.raw) };
if p.is_null() {
return String::new(); }
unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
}
}
impl Drop for Context {
fn drop(&mut self) {
require_ok(unsafe { hegel_c::hegel_context_free(self.raw) });
}
}
thread_local! {
static CONTEXT: Context = Context::new();
}
fn with_context<R>(f: impl FnOnce(*mut hegel_c::HegelContext) -> R) -> R {
CONTEXT.with(|c| f(c.as_ptr()))
}
pub(crate) fn last_error_string() -> String {
CONTEXT.with(|c| c.last_error())
}
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 {
with_context(|ctx| {
let mut raw: *mut hegel_c::HegelSettings = ptr::null_mut();
unsafe {
require_ok(hegel_c::hegel_settings_new(ctx, &mut raw));
require_ok(hegel_c::hegel_settings_set_mode(
ctx,
raw,
map_mode(settings.mode),
));
require_ok(hegel_c::hegel_settings_set_test_cases(
ctx,
raw,
settings.test_cases,
));
require_ok(hegel_c::hegel_settings_set_verbosity(
ctx,
raw,
map_verbosity(settings.verbosity),
));
require_ok(match settings.seed {
Some(seed) => hegel_c::hegel_settings_set_seed(ctx, raw, seed, true),
None => hegel_c::hegel_settings_set_seed(ctx, raw, 0, false),
});
require_ok(hegel_c::hegel_settings_set_derandomize(
ctx,
raw,
settings.derandomize,
));
require_ok(hegel_c::hegel_settings_set_report_multiple_failures(
ctx,
raw,
settings.report_multiple_failures,
));
match &settings.database {
Database::Disabled => {
let empty = CString::new("").unwrap();
require_ok(hegel_c::hegel_settings_set_database(
ctx,
raw,
empty.as_ptr(),
));
}
Database::Path(path) => {
let c = cstring_lossy(path);
require_ok(hegel_c::hegel_settings_set_database(ctx, raw, c.as_ptr()));
}
Database::Unset => {}
}
if let Some(key) = database_key {
let c = cstring_lossy(key);
require_ok(hegel_c::hegel_settings_set_database_key(
ctx,
raw,
c.as_ptr(),
));
}
require_ok(hegel_c::hegel_settings_set_phases(
ctx,
raw,
phases_bitmask(&settings.phases),
));
require_ok(hegel_c::hegel_settings_set_suppress_health_check(
ctx,
raw,
health_check_bitmask(&settings.suppress_health_check),
));
require_ok(hegel_c::hegel_settings_set_backend(
ctx,
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) {
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_settings_free(ctx, self.raw)
}));
}
}
pub(crate) struct RunHandle {
raw: *mut hegel_c::HegelRun,
}
impl RunHandle {
pub(crate) fn start(settings: &SettingsHandle) -> Result<Self, String> {
let mut raw: *mut hegel_c::HegelRun = ptr::null_mut();
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_run_start(ctx, settings.as_ptr(), &mut raw)
});
if rc != hegel_result_t::HEGEL_OK {
return Err(last_error_string()); }
Ok(RunHandle { raw })
}
pub(crate) fn next_test_case(&self) -> Option<CTestCase> {
let mut raw: *mut hegel_c::HegelTestCase = ptr::null_mut();
let rc =
with_context(|ctx| unsafe { hegel_c::hegel_next_test_case(ctx, self.raw, &mut raw) });
if rc != hegel_result_t::HEGEL_OK || raw.is_null() {
None
} else {
Some(CTestCase { raw })
}
}
pub(crate) fn result(&self) -> RunResult {
let mut raw: *mut hegel_c::HegelRunResult = ptr::null_mut();
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_run_result(ctx, self.raw, &mut raw)
}));
RunResult { raw }
}
}
impl Drop for RunHandle {
fn drop(&mut self) {
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_run_free(ctx, self.raw)
}));
}
}
pub(crate) struct CTestCase {
raw: *mut hegel_c::HegelTestCase,
}
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 mut raw: *mut hegel_c::HegelTestCase = ptr::null_mut();
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_test_case_from_blob(ctx, settings.as_ptr(), c_blob.as_ptr(), &mut raw)
});
if rc != hegel_result_t::HEGEL_OK {
return Err(last_error_string());
}
Ok(CTestCase { raw })
}
pub(crate) fn clone_handle(&self) -> CTestCase {
let mut raw: *mut hegel_c::HegelTestCase = ptr::null_mut();
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_test_case_clone(ctx, self.raw, &mut raw)
}));
CTestCase { raw }
}
pub(crate) fn generate(&self, schema_cbor: &[u8]) -> Result<Vec<u8>, hegel_result_t> {
let mut out_ptr: *const u8 = ptr::null();
let mut out_len: usize = 0;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_generate(
ctx,
self.raw,
schema_cbor.as_ptr(),
schema_cbor.len(),
&mut out_ptr,
&mut out_len,
)
});
if rc != hegel_result_t::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<(), hegel_result_t> {
rc_to_unit(with_context(|ctx| unsafe {
hegel_c::hegel_start_span(ctx, self.raw, label)
}))
}
pub(crate) fn stop_span(&self, discard: bool) -> Result<(), hegel_result_t> {
rc_to_unit(with_context(|ctx| unsafe {
hegel_c::hegel_stop_span(ctx, self.raw, discard)
}))
}
pub(crate) fn new_collection(
&self,
min_size: u64,
max_size: Option<u64>,
) -> Result<i64, hegel_result_t> {
let mut id: i64 = 0;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_new_collection(
ctx,
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, hegel_result_t> {
let mut more = false;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_collection_more(ctx, self.raw, collection_id, &mut more)
});
rc_to_value(rc, more)
}
pub(crate) fn collection_reject(
&self,
collection_id: i64,
why: Option<&str>,
) -> Result<(), hegel_result_t> {
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(with_context(|ctx| unsafe {
hegel_c::hegel_collection_reject(ctx, self.raw, collection_id, why_ptr)
}))
}
pub(crate) fn new_pool(&self) -> Result<i64, hegel_result_t> {
let mut id: i64 = 0;
let rc = with_context(|ctx| unsafe { hegel_c::hegel_new_pool(ctx, self.raw, &mut id) });
rc_to_value(rc, id)
}
pub(crate) fn pool_add(&self, pool_id: i64) -> Result<i64, hegel_result_t> {
let mut id: i64 = 0;
let rc =
with_context(|ctx| unsafe { hegel_c::hegel_pool_add(ctx, self.raw, pool_id, &mut id) });
rc_to_value(rc, id)
}
pub(crate) fn pool_generate(&self, pool_id: i64, consume: bool) -> Result<i64, hegel_result_t> {
let mut id: i64 = 0;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_pool_generate(ctx, 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, hegel_result_t> {
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 = with_context(|ctx| unsafe {
hegel_c::hegel_new_state_machine(
ctx,
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, hegel_result_t> {
let mut out: i64 = 0;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_state_machine_next_rule(ctx, self.raw, state_machine_id, &mut out)
});
rc_to_value(rc, out)
}
pub(crate) fn target(&self, score: f64, label: &str) -> Result<(), hegel_result_t> {
let c_label = cstring_lossy(label);
rc_to_unit(with_context(|ctx| unsafe {
hegel_c::hegel_target(ctx, self.raw, score, c_label.as_ptr())
}))
}
pub(crate) fn mark_complete(
&self,
status: hegel_c::hegel_status_t,
origin: Option<&str>,
) -> Result<(), hegel_result_t> {
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(with_context(|ctx| unsafe {
hegel_c::hegel_mark_complete(ctx, self.raw, status, origin_ptr)
}))
}
}
impl Drop for CTestCase {
fn drop(&mut self) {
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_test_case_free(ctx, self.raw)
}));
}
}
pub(crate) struct RunResult {
raw: *mut hegel_c::HegelRunResult,
}
impl RunResult {
pub(crate) fn status(&self) -> hegel_c::hegel_run_status_t {
let mut status = hegel_c::hegel_run_status_t::HEGEL_RUN_STATUS_ERROR;
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_run_result_status(ctx, self.raw, &mut status)
}));
status
}
pub(crate) fn error(&self) -> Option<String> {
let mut p: *const c_char = ptr::null();
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_run_result_error(ctx, self.raw, &mut p)
}));
cstr_opt(p)
}
pub(crate) fn failure_count(&self) -> usize {
let mut count = 0;
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_run_result_failure_count(ctx, self.raw, &mut count)
}));
count
}
pub(crate) fn failure(&self, index: usize) -> Failure {
let mut f: *mut hegel_c::HegelFailure = ptr::null_mut();
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_run_result_failure(ctx, self.raw, index, &mut f)
}));
let mut blob: *const c_char = ptr::null();
let reproduce_blob = with_context(|ctx| unsafe {
require_ok(hegel_c::hegel_failure_reproduction_blob(ctx, f, &mut blob));
let reproduce_blob = cstr_opt(blob);
require_ok(hegel_c::hegel_failure_free(ctx, f));
reproduce_blob
});
Failure { reproduce_blob }
}
}
impl Drop for RunResult {
fn drop(&mut self) {
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_run_result_free(ctx, self.raw)
}));
}
}
pub(crate) struct Failure {
pub(crate) reproduce_blob: Option<String>,
}
fn rc_to_unit(rc: hegel_result_t) -> Result<(), hegel_result_t> {
if rc == hegel_result_t::HEGEL_OK {
Ok(())
} else {
Err(rc)
}
}
fn require_ok(rc: hegel_result_t) {
rc_to_unit(rc).unwrap_or_else(|rc| crate::test_case::raise_for_rc(rc));
}
fn rc_to_value<T>(rc: hegel_result_t, value: T) -> Result<T, hegel_result_t> {
if rc == hegel_result_t::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_t::HEGEL_PHASE_EXPLICIT as u32,
Phase::Reuse => hegel_c::hegel_phase_t::HEGEL_PHASE_REUSE as u32,
Phase::Generate => hegel_c::hegel_phase_t::HEGEL_PHASE_GENERATE as u32,
Phase::Target => hegel_c::hegel_phase_t::HEGEL_PHASE_TARGET as u32,
Phase::Shrink => hegel_c::hegel_phase_t::HEGEL_PHASE_SHRINK as u32,
};
}
mask
}
fn health_check_bitmask(checks: &[HealthCheck]) -> u32 {
let mut mask = 0;
for check in checks {
mask |= match check {
HealthCheck::FilterTooMuch => {
hegel_c::hegel_health_check_t::HEGEL_HC_FILTER_TOO_MUCH as u32
}
HealthCheck::TooSlow => hegel_c::hegel_health_check_t::HEGEL_HC_TOO_SLOW as u32,
HealthCheck::TestCasesTooLarge => {
hegel_c::hegel_health_check_t::HEGEL_HC_TEST_CASES_TOO_LARGE as u32
}
HealthCheck::LargeInitialTestCase => {
hegel_c::hegel_health_check_t::HEGEL_HC_LARGE_INITIAL_TEST_CASE as u32
}
};
}
mask
}
#[cfg(test)]
#[path = "../tests/embedded/ffi_tests.rs"]
mod tests;