pub(crate) mod sys;
use self::sys as hegel_c;
use crate::control::hegel_internal_error;
use crate::runner::{Backend, Database, HealthCheck, Phase, Settings, TestLocation, Verbosity};
use crate::test_case::OutputSink;
use hegel_c::hegel_result_t;
use std::ffi::{CStr, CString, c_void};
use std::os::raw::c_char;
use std::ptr;
struct Context {
raw: *mut hegel_c::HegelContext,
}
impl Context {
fn new() -> Self {
Context {
raw: unsafe { 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()))
}
fn free_on_drop(f: impl FnOnce(*mut hegel_c::HegelContext) -> hegel_c::hegel_result_t) {
let _ = CONTEXT.try_with(|c| require_ok(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())
}
fn string_from_engine_bytes(bytes: Vec<u8>) -> String {
String::from_utf8(bytes).unwrap_or_else(|e| {
crate::control::hegel_internal_error!("libhegel returned invalid UTF-8: {e}")
})
}
pub(crate) struct SettingsHandle {
raw: *mut hegel_c::HegelSettings,
}
impl SettingsHandle {
pub(crate) fn build(
settings: &Settings,
database_key: Option<&str>,
test_location: Option<&TestLocation>,
) -> Self {
with_context(|ctx| {
let mut raw: *mut hegel_c::HegelSettings = ptr::null_mut();
let base_profile = CString::new("base").unwrap();
unsafe {
require_ok(hegel_c::hegel_settings_new_for_profile(
ctx,
base_profile.as_ptr(),
&mut raw,
));
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,
));
require_ok(hegel_c::hegel_settings_set_show_statistics(
ctx,
raw,
settings.show_statistics,
));
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 => {
require_ok(hegel_c::hegel_settings_set_database(ctx, raw, ptr::null()));
}
}
require_ok(hegel_c::hegel_settings_set_print_blob(
ctx,
raw,
settings.print_blob,
));
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(),
));
}
if let Some(location) = test_location {
let file = cstring_lossy(&location.file);
let class = cstring_lossy(&location.class);
let function = cstring_lossy(&location.function);
require_ok(hegel_c::hegel_settings_set_test_location(
ctx,
raw,
file.as_ptr(),
location.begin_line,
class.as_ptr(),
function.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) {
free_on_drop(|ctx| unsafe { hegel_c::hegel_settings_free(ctx, self.raw) });
}
}
pub(crate) fn settings_from_profile(name: Option<&str>) -> Result<Settings, String> {
with_context(|ctx| {
let mut raw: *mut hegel_c::HegelSettings = ptr::null_mut();
let rc = unsafe {
match name {
Some(name) => {
let c = cstring_lossy(name);
hegel_c::hegel_settings_new_for_profile(ctx, c.as_ptr(), &mut raw)
}
None => hegel_c::hegel_settings_new(ctx, &mut raw),
}
};
if rc == hegel_result_t::HEGEL_E_INVALID_ARG {
return Err(last_error_string());
}
require_ok(rc);
let handle = SettingsHandle { raw };
Ok(read_settings(ctx, handle.as_ptr()))
})
}
fn read_settings(ctx: *mut hegel_c::HegelContext, raw: *const hegel_c::HegelSettings) -> Settings {
let mut test_cases = 0u64;
let mut verbosity = hegel_c::hegel_verbosity_t::HEGEL_VERBOSITY_NORMAL;
let mut seed = 0u64;
let mut has_seed = false;
let mut derandomize = false;
let mut database: *const c_char = ptr::null();
let mut phases = 0u32;
let mut suppress_health_check = 0u32;
let mut report_multiple_failures = false;
let mut show_statistics = false;
let mut print_blob = false;
let mut backend = hegel_c::hegel_backend_t::HEGEL_BACKEND_DEFAULT;
unsafe {
require_ok(hegel_c::hegel_settings_get_test_cases(
ctx,
raw,
&mut test_cases,
));
require_ok(hegel_c::hegel_settings_get_verbosity(
ctx,
raw,
&mut verbosity,
));
require_ok(hegel_c::hegel_settings_get_seed(
ctx,
raw,
&mut seed,
&mut has_seed,
));
require_ok(hegel_c::hegel_settings_get_derandomize(
ctx,
raw,
&mut derandomize,
));
require_ok(hegel_c::hegel_settings_get_database(
ctx,
raw,
&mut database,
));
require_ok(hegel_c::hegel_settings_get_phases(ctx, raw, &mut phases));
require_ok(hegel_c::hegel_settings_get_suppress_health_check(
ctx,
raw,
&mut suppress_health_check,
));
require_ok(hegel_c::hegel_settings_get_report_multiple_failures(
ctx,
raw,
&mut report_multiple_failures,
));
require_ok(hegel_c::hegel_settings_get_show_statistics(
ctx,
raw,
&mut show_statistics,
));
require_ok(hegel_c::hegel_settings_get_print_blob(
ctx,
raw,
&mut print_blob,
));
require_ok(hegel_c::hegel_settings_get_backend(ctx, raw, &mut backend));
}
let database = match cstr_opt(database) {
None => Database::Unset,
Some(path) if path.is_empty() => Database::Disabled,
Some(path) => Database::Path(path),
};
Settings {
test_cases,
verbosity: verbosity_from_c(verbosity),
seed: has_seed.then_some(seed),
derandomize,
database,
suppress_health_check: health_checks_from_bitmask(suppress_health_check),
phases: phases_from_bitmask(phases),
report_multiple_failures,
show_statistics,
print_blob,
backend: backend_from_c(backend),
}
}
pub(crate) fn set_default_profile(name: Option<&str>) -> Result<(), String> {
with_context(|ctx| {
let c = name.map(cstring_lossy);
let ptr = c.as_ref().map_or(ptr::null(), |c| c.as_ptr());
let rc = unsafe { hegel_c::hegel_set_default_profile(ctx, ptr) };
if rc == hegel_result_t::HEGEL_E_INVALID_ARG {
return Err(last_error_string());
}
require_ok(rc);
Ok(())
})
}
pub(crate) fn register_profile(name: &str, settings: &Settings) -> Result<(), String> {
let handle = SettingsHandle::build(settings, None, None);
with_context(|ctx| {
let c = cstring_lossy(name);
let rc =
unsafe { hegel_c::hegel_settings_register_profile(ctx, c.as_ptr(), handle.as_ptr()) };
if rc == hegel_result_t::HEGEL_E_INVALID_ARG {
return Err(last_error_string());
}
require_ok(rc);
Ok(())
})
}
unsafe extern "C" fn engine_output_trampoline(
user_data: *mut c_void,
line: *const c_char,
len: usize,
) {
let sink = unsafe { &*user_data.cast::<OutputSink>() };
let bytes = unsafe { std::slice::from_raw_parts(line.cast::<u8>(), len) };
sink(&String::from_utf8_lossy(bytes));
}
fn output_args(
sink_ptr: Option<*const OutputSink>,
) -> (hegel_c::hegel_output_callback_t, *mut c_void) {
match sink_ptr {
Some(p) => (Some(engine_output_trampoline), p.cast_mut().cast()),
None => (None, ptr::null_mut()),
}
}
pub(crate) struct RunHandle {
raw: *mut hegel_c::HegelRun,
output: Option<*mut OutputSink>,
}
impl RunHandle {
pub(crate) fn start(
settings: &SettingsHandle,
sink: Option<&OutputSink>,
) -> Result<Self, String> {
let output = sink.map(|s| Box::into_raw(Box::new(s.clone())));
let (callback, user_data) = output_args(output.map(|p| p.cast_const()));
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(), callback, user_data, &mut raw)
});
let run = RunHandle { raw, output };
if rc != hegel_result_t::HEGEL_OK {
return Err(last_error_string()); }
Ok(run)
}
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) });
require_ok(rc);
if 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) {
free_on_drop(|ctx| unsafe { hegel_c::hegel_run_free(ctx, self.raw) });
if let Some(p) = self.output {
drop(unsafe { Box::from_raw(p) });
}
}
}
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,
sink: Option<&OutputSink>,
) -> Result<Self, String> {
let c_blob = cstring_lossy(blob);
let (callback, user_data) = output_args(sink.map(ptr::from_ref));
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(),
callback,
user_data,
&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 block_handle(&self, indent: u64) -> CTestCase {
let mut raw: *mut hegel_c::HegelTestCase = ptr::null_mut();
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_test_case_block(ctx, self.raw, indent, &mut raw)
}));
CTestCase { raw }
}
pub(crate) fn set_worker(&self, worker_index: i64) {
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_test_case_set_worker(ctx, self.raw, worker_index)
}));
}
pub(crate) fn is_nondeterministic(&self) -> bool {
let mut out = false;
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_test_case_is_nondeterministic(ctx, self.raw, &mut out)
}));
out
}
pub(crate) fn generate_integer(
&self,
min_value: i64,
max_value: i64,
) -> Result<i64, hegel_result_t> {
let mut out: i64 = 0;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_generate_integer(ctx, self.raw, min_value, max_value, &mut out)
});
rc_to_value(rc, out)
}
pub(crate) fn generate_integer_big(
&self,
min_value: &[u8],
max_value: &[u8],
) -> Result<[u8; 17], hegel_result_t> {
let mut out = [0u8; 17];
let mut out_len: usize = 0;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_generate_integer_big(
ctx,
self.raw,
min_value.as_ptr(),
min_value.len(),
max_value.as_ptr(),
max_value.len(),
out.as_mut_ptr(),
out.len(),
&mut out_len,
)
});
if rc != hegel_result_t::HEGEL_OK {
return Err(rc);
}
Ok(out)
}
pub(crate) fn generate_float(
&self,
width: u32,
min_value: f64,
max_value: f64,
allow_nan: bool,
allow_infinity: bool,
exclude_min: bool,
exclude_max: bool,
smallest_nonzero_magnitude: f64,
) -> Result<f64, hegel_result_t> {
let mut out: f64 = 0.0;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_generate_float(
ctx,
self.raw,
width,
min_value,
max_value,
allow_nan,
allow_infinity,
exclude_min,
exclude_max,
smallest_nonzero_magnitude,
&mut out,
)
});
rc_to_value(rc, out)
}
pub(crate) fn generate_boolean(&self, p: f64) -> Result<bool, hegel_result_t> {
let mut out = false;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_generate_boolean(ctx, self.raw, p, false, false, &mut out)
});
rc_to_value(rc, out)
}
pub(crate) fn generate_bytes(
&self,
min_size: u64,
max_size: u64,
) -> Result<Vec<u8>, hegel_result_t> {
let mut result = hegel_c::hegel_generate_bytes_result_t {
data: ptr::null_mut(),
len: 0,
};
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_generate_bytes(ctx, self.raw, min_size, max_size, &mut result)
});
if rc != hegel_result_t::HEGEL_OK {
return Err(rc);
}
let bytes = unsafe { std::slice::from_raw_parts(result.data, result.len) }.to_vec();
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_generate_bytes_result_free(ctx, &mut result)
}));
Ok(bytes)
}
pub(crate) fn generate_string(
&self,
generator: &StringGenerator,
) -> Result<String, hegel_result_t> {
let mut result = hegel_c::hegel_generate_string_result_t {
data: ptr::null_mut(),
len: 0,
};
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_generate_string(ctx, self.raw, generator.raw, &mut result)
});
if rc != hegel_result_t::HEGEL_OK {
return Err(rc);
}
let bytes =
unsafe { std::slice::from_raw_parts(result.data.cast::<u8>(), result.len) }.to_vec();
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_generate_string_result_free(ctx, &mut result)
}));
Ok(string_from_engine_bytes(bytes))
}
pub(crate) fn generate_date(
&self,
min: hegel_c::hegel_date_t,
max: hegel_c::hegel_date_t,
) -> Result<hegel_c::hegel_date_t, hegel_result_t> {
let mut out = min;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_generate_date(ctx, self.raw, min, max, &mut out)
});
rc_to_value(rc, out)
}
pub(crate) fn generate_time(
&self,
min: hegel_c::hegel_time_t,
max: hegel_c::hegel_time_t,
) -> Result<hegel_c::hegel_time_t, hegel_result_t> {
let mut out = min;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_generate_time(ctx, self.raw, min, max, &mut out)
});
rc_to_value(rc, out)
}
pub(crate) fn generate_datetime(
&self,
min: hegel_c::hegel_datetime_t,
max: hegel_c::hegel_datetime_t,
) -> Result<hegel_c::hegel_datetime_t, hegel_result_t> {
let mut out = min;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_generate_datetime(ctx, self.raw, min, max, &mut out)
});
rc_to_value(rc, out)
}
pub(crate) fn generate_uuid(&self, version: Option<u8>) -> Result<[u8; 16], hegel_result_t> {
let mut out = [0u8; 16];
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_generate_uuid(
ctx,
self.raw,
version.unwrap_or(0),
version.is_some(),
out.as_mut_ptr(),
)
});
rc_to_value(rc, out)
}
pub(crate) fn generate_ipv4(&self) -> Result<std::net::Ipv4Addr, hegel_result_t> {
let mut out = [0u8; 4];
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_generate_ipv4(ctx, self.raw, out.as_mut_ptr())
});
rc_to_value(rc, std::net::Ipv4Addr::from(out))
}
pub(crate) fn generate_ipv6(&self) -> Result<std::net::Ipv6Addr, hegel_result_t> {
let mut out = [0u8; 16];
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_generate_ipv6(ctx, self.raw, out.as_mut_ptr())
});
rc_to_value(rc, std::net::Ipv6Addr::from(out))
}
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<CollectionHandle, hegel_result_t> {
let mut raw: *mut hegel_c::HegelCollection = ptr::null_mut();
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_new_collection(
ctx,
self.raw,
min_size,
max_size.unwrap_or(u64::MAX),
&mut raw,
)
});
if rc != hegel_result_t::HEGEL_OK {
return Err(rc);
}
Ok(CollectionHandle { raw })
}
pub(crate) fn collection_more(
&self,
collection: &CollectionHandle,
) -> Result<bool, hegel_result_t> {
let mut more = false;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_collection_more(ctx, self.raw, collection.raw, &mut more)
});
rc_to_value(rc, more)
}
pub(crate) fn collection_reject(
&self,
collection: &CollectionHandle,
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.raw, why_ptr)
}))
}
pub(crate) fn new_recursion(
&self,
max_depth: u64,
max_leaves: u64,
) -> Result<RecursionHandle, hegel_result_t> {
let mut raw: *mut hegel_c::HegelRecursion = ptr::null_mut();
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_new_recursion(ctx, self.raw, max_depth, max_leaves, &mut raw)
});
if rc != hegel_result_t::HEGEL_OK {
return Err(rc);
}
Ok(RecursionHandle { raw })
}
pub(crate) fn recursion_branch(
&self,
recursion: &RecursionHandle,
depth: u64,
) -> Result<bool, hegel_result_t> {
let mut branch = false;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_recursion_branch(ctx, self.raw, recursion.raw, depth, &mut branch)
});
rc_to_value(rc, branch)
}
pub(crate) fn recursion_leaf(&self, recursion: &RecursionHandle) -> Result<(), hegel_result_t> {
rc_to_unit(with_context(|ctx| unsafe {
hegel_c::hegel_recursion_leaf(ctx, self.raw, recursion.raw)
}))
}
pub(crate) fn recursion_retry(
&self,
recursion: &RecursionHandle,
) -> Result<(), hegel_result_t> {
rc_to_unit(with_context(|ctx| unsafe {
hegel_c::hegel_recursion_retry(ctx, self.raw, recursion.raw)
}))
}
pub(crate) fn recursion_finish(
&self,
recursion: &RecursionHandle,
) -> Result<(), hegel_result_t> {
rc_to_unit(with_context(|ctx| unsafe {
hegel_c::hegel_recursion_finish(ctx, self.raw, recursion.raw)
}))
}
pub(crate) fn new_pool(&self) -> Result<PoolHandle, hegel_result_t> {
let mut raw: *mut hegel_c::HegelPool = ptr::null_mut();
let rc = with_context(|ctx| unsafe { hegel_c::hegel_new_pool(ctx, self.raw, &mut raw) });
if rc != hegel_result_t::HEGEL_OK {
return Err(rc);
}
Ok(PoolHandle { raw })
}
pub(crate) fn pool_add(&self, pool: &PoolHandle) -> 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.raw, &mut id)
});
rc_to_value(rc, id)
}
pub(crate) fn pool_generate(
&self,
pool: &PoolHandle,
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.raw, consume, &mut id)
});
rc_to_value(rc, id)
}
pub(crate) fn new_state_machine(
&self,
rule_names: &[&str],
rule_groups: &[i64],
invariant_names: &[&str],
invariant_always_check: &[bool],
min_concurrency: i64,
max_concurrency: i64,
step_count: i64,
) -> Result<(StateMachineHandle, 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 raw: *mut hegel_c::HegelStateMachine = ptr::null_mut();
let mut concurrency: i64 = 0;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_new_state_machine(
ctx,
self.raw,
rule_ptrs.as_ptr(),
rule_groups.as_ptr(),
rule_ptrs.len(),
invariant_ptrs.as_ptr(),
invariant_always_check.as_ptr(),
invariant_ptrs.len(),
min_concurrency,
max_concurrency,
step_count,
&mut raw,
&mut concurrency,
)
});
if rc != hegel_result_t::HEGEL_OK {
return Err(rc);
}
Ok((StateMachineHandle { raw }, concurrency))
}
pub(crate) fn state_machine_next_group(
&self,
state_machine: &StateMachineHandle,
) -> Result<Option<i64>, hegel_result_t> {
let mut out: i64 = 0;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_state_machine_next_group(ctx, self.raw, state_machine.raw, &mut out)
});
let group = if out == hegel_c::HEGEL_STATE_MACHINE_DONE {
None
} else {
Some(out)
};
rc_to_value(rc, group)
}
pub(crate) fn state_machine_next_rule(
&self,
state_machine: &StateMachineHandle,
worker_index: i64,
) -> Result<Option<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.raw,
worker_index,
&mut out,
)
});
let index = if out == hegel_c::HEGEL_STATE_MACHINE_DONE {
None
} else {
Some(out)
};
rc_to_value(rc, index)
}
pub(crate) fn state_machine_rule_rejected(
&self,
state_machine: &StateMachineHandle,
worker_index: i64,
) -> Result<(), hegel_result_t> {
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_state_machine_rule_rejected(
ctx,
self.raw,
state_machine.raw,
worker_index,
)
});
rc_to_value(rc, ())
}
pub(crate) fn state_machine_should_check_invariant(
&self,
state_machine: &StateMachineHandle,
invariant_index: i64,
) -> Result<bool, hegel_result_t> {
let mut out = false;
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_state_machine_should_check_invariant(
ctx,
self.raw,
state_machine.raw,
invariant_index,
&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 event(&self, label: &str) -> Result<(), hegel_result_t> {
let c_label = cstring_lossy(label);
rc_to_unit(with_context(|ctx| unsafe {
hegel_c::hegel_event(ctx, self.raw, c_label.as_ptr())
}))
}
pub(crate) fn event_value(&self, label: &str, value: f64) -> Result<(), hegel_result_t> {
let c_label = cstring_lossy(label);
rc_to_unit(with_context(|ctx| unsafe {
hegel_c::hegel_event_value(ctx, self.raw, value, c_label.as_ptr())
}))
}
pub(crate) fn printer(&self, max_width: u64) -> PrinterHandle {
let mut raw: *mut hegel_c::HegelPrinter = ptr::null_mut();
with_printer_options(max_width, |options| {
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_test_case_printer(ctx, self.raw, options, &mut raw)
}));
});
PrinterHandle { raw }
}
pub(crate) fn note(&self, text: &str) -> Result<(), PrinterCallError> {
PrinterHandle::check(with_context(|ctx| unsafe {
hegel_c::hegel_note(ctx, self.raw, text.as_ptr(), text.len())
}))
}
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 as u32, origin_ptr)
}))
}
}
impl Drop for CTestCase {
fn drop(&mut self) {
free_on_drop(|ctx| unsafe { hegel_c::hegel_test_case_free(ctx, self.raw) });
}
}
pub(crate) struct CollectionHandle {
raw: *mut hegel_c::HegelCollection,
}
unsafe impl Send for CollectionHandle {}
unsafe impl Sync for CollectionHandle {}
impl Drop for CollectionHandle {
fn drop(&mut self) {
free_on_drop(|ctx| unsafe { hegel_c::hegel_collection_free(ctx, self.raw) });
}
}
pub(crate) struct RecursionHandle {
raw: *mut hegel_c::HegelRecursion,
}
unsafe impl Send for RecursionHandle {}
unsafe impl Sync for RecursionHandle {}
impl Drop for RecursionHandle {
fn drop(&mut self) {
free_on_drop(|ctx| unsafe { hegel_c::hegel_recursion_free(ctx, self.raw) });
}
}
pub(crate) struct PoolHandle {
raw: *mut hegel_c::HegelPool,
}
unsafe impl Send for PoolHandle {}
unsafe impl Sync for PoolHandle {}
impl Drop for PoolHandle {
fn drop(&mut self) {
free_on_drop(|ctx| unsafe { hegel_c::hegel_pool_free(ctx, self.raw) });
}
}
pub(crate) struct StateMachineHandle {
raw: *mut hegel_c::HegelStateMachine,
}
unsafe impl Send for StateMachineHandle {}
unsafe impl Sync for StateMachineHandle {}
impl Drop for StateMachineHandle {
fn drop(&mut self) {
free_on_drop(|ctx| unsafe { hegel_c::hegel_state_machine_free(ctx, self.raw) });
}
}
pub(crate) struct StringGenerator {
raw: *mut hegel_c::HegelStringGenerator,
}
unsafe impl Send for StringGenerator {}
unsafe impl Sync for StringGenerator {}
impl std::fmt::Debug for StringGenerator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StringGenerator").finish_non_exhaustive()
}
}
impl StringGenerator {
pub(crate) fn text(
min_size: u64,
max_size: u64,
codec: Option<&str>,
min_codepoint: u32,
max_codepoint: Option<u32>,
categories: Option<&[String]>,
exclude_categories: Option<&[String]>,
include_characters: Option<&str>,
exclude_characters: Option<&str>,
) -> Result<Self, String> {
let c_codec = codec.map(cstring_lossy);
let c_categories: Option<Vec<CString>> =
categories.map(|cats| cats.iter().map(|c| cstring_lossy(c)).collect());
let c_exclude_categories: Option<Vec<CString>> =
exclude_categories.map(|cats| cats.iter().map(|c| cstring_lossy(c)).collect());
let category_ptrs: Option<Vec<*const c_char>> = c_categories
.as_ref()
.map(|cats| cats.iter().map(|c| c.as_ptr()).collect());
let exclude_category_ptrs: Option<Vec<*const c_char>> = c_exclude_categories
.as_ref()
.map(|cats| cats.iter().map(|c| c.as_ptr()).collect());
let mut raw: *mut hegel_c::HegelStringGenerator = ptr::null_mut();
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_string_generator_text(
ctx,
min_size,
max_size,
c_codec.as_ref().map_or(ptr::null(), |c| c.as_ptr()),
min_codepoint,
max_codepoint.unwrap_or(u32::MAX),
category_ptrs.as_ref().map_or(ptr::null(), |p| p.as_ptr()),
category_ptrs.as_ref().map_or(0, |p| p.len()),
exclude_category_ptrs
.as_ref()
.map_or(ptr::null(), |p| p.as_ptr()),
exclude_category_ptrs.as_ref().map_or(0, |p| p.len()),
include_characters.map_or(ptr::null(), |s| s.as_ptr()),
include_characters.map_or(0, |s| s.len()),
exclude_characters.map_or(ptr::null(), |s| s.as_ptr()),
exclude_characters.map_or(0, |s| s.len()),
&mut raw,
)
});
Self::from_construction(rc, raw)
}
pub(crate) fn regex(
pattern: &str,
fullmatch: bool,
alphabet: Option<&StringGenerator>,
) -> Result<Self, String> {
let c_pattern = cstring_lossy(pattern);
let mut raw: *mut hegel_c::HegelStringGenerator = ptr::null_mut();
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_string_generator_regex(
ctx,
c_pattern.as_ptr(),
fullmatch,
alphabet.map_or(ptr::null(), |a| a.raw),
&mut raw,
)
});
Self::from_construction(rc, raw)
}
pub(crate) fn email() -> Result<Self, String> {
let mut raw: *mut hegel_c::HegelStringGenerator = ptr::null_mut();
let rc =
with_context(|ctx| unsafe { hegel_c::hegel_string_generator_email(ctx, &mut raw) });
Self::from_construction(rc, raw)
}
pub(crate) fn url() -> Result<Self, String> {
let mut raw: *mut hegel_c::HegelStringGenerator = ptr::null_mut();
let rc = with_context(|ctx| unsafe { hegel_c::hegel_string_generator_url(ctx, &mut raw) });
Self::from_construction(rc, raw)
}
pub(crate) fn domain(max_length: u64) -> Result<Self, String> {
let mut raw: *mut hegel_c::HegelStringGenerator = ptr::null_mut();
let rc = with_context(|ctx| unsafe {
hegel_c::hegel_string_generator_domain(ctx, max_length, &mut raw)
});
Self::from_construction(rc, raw)
}
fn from_construction(
rc: hegel_result_t,
raw: *mut hegel_c::HegelStringGenerator,
) -> Result<Self, String> {
if rc != hegel_result_t::HEGEL_OK {
return Err(last_error_string());
}
Ok(StringGenerator { raw })
}
}
impl Drop for StringGenerator {
fn drop(&mut self) {
free_on_drop(|ctx| unsafe { hegel_c::hegel_string_generator_free(ctx, self.raw) });
}
}
pub(crate) struct PrinterHandle {
raw: *mut hegel_c::HegelPrinter,
}
unsafe impl Send for PrinterHandle {}
unsafe impl Sync for PrinterHandle {}
impl std::fmt::Debug for PrinterHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PrinterHandle").finish_non_exhaustive()
}
}
pub(crate) enum PrinterCallError {
DeadRegion,
Other(String),
}
fn with_printer_options(max_width: u64, f: impl FnOnce(*const hegel_c::HegelPrinterOptions)) {
let mut options: *mut hegel_c::HegelPrinterOptions = ptr::null_mut();
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_printer_options_new(ctx, &mut options)
}));
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_printer_options_set_max_width(ctx, options, max_width)
}));
f(options);
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_printer_options_free(ctx, options)
}));
}
impl PrinterHandle {
pub(crate) fn new(max_width: u64) -> Self {
let mut raw: *mut hegel_c::HegelPrinter = ptr::null_mut();
with_printer_options(max_width, |options| {
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_printer_new(ctx, options, &mut raw)
}));
});
PrinterHandle { raw }
}
fn check(rc: hegel_result_t) -> Result<(), PrinterCallError> {
match rc {
hegel_result_t::HEGEL_OK => Ok(()),
hegel_result_t::HEGEL_E_INVALID_HANDLE => Err(PrinterCallError::DeadRegion),
_ => Err(PrinterCallError::Other(last_error_string())),
}
}
pub(crate) fn is_live(&self) -> bool {
let mut live = false;
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_printer_is_live(ctx, self.raw, &mut live)
}));
live
}
pub(crate) fn text(&self, s: &str) -> Result<(), PrinterCallError> {
Self::check(with_context(|ctx| unsafe {
hegel_c::hegel_printer_text(ctx, self.raw, s.as_ptr(), s.len())
}))
}
pub(crate) fn breakable(&self, sep: &str) -> Result<(), PrinterCallError> {
Self::check(with_context(|ctx| unsafe {
hegel_c::hegel_printer_breakable(ctx, self.raw, sep.as_ptr(), sep.len())
}))
}
pub(crate) fn hard_break(&self) -> Result<(), PrinterCallError> {
Self::check(with_context(|ctx| unsafe {
hegel_c::hegel_printer_hard_break(ctx, self.raw)
}))
}
pub(crate) fn comment(&self, text: &str) -> Result<(), PrinterCallError> {
Self::check(with_context(|ctx| unsafe {
hegel_c::hegel_printer_comment(ctx, self.raw, text.as_ptr(), text.len())
}))
}
pub(crate) fn begin_group(&self, indent: u64, open: &str) -> Result<(), PrinterCallError> {
Self::check(with_context(|ctx| unsafe {
hegel_c::hegel_printer_begin_group(ctx, self.raw, indent, open.as_ptr(), open.len())
}))
}
pub(crate) fn end_group(&self, close: &str) -> Result<(), PrinterCallError> {
Self::check(with_context(|ctx| unsafe {
hegel_c::hegel_printer_end_group(ctx, self.raw, close.as_ptr(), close.len())
}))
}
pub(crate) fn shift_indent(&self, delta: i64) -> Result<(), PrinterCallError> {
Self::check(with_context(|ctx| unsafe {
hegel_c::hegel_printer_shift_indent(ctx, self.raw, delta)
}))
}
pub(crate) fn deferred(&self) -> Result<PrinterHandle, PrinterCallError> {
let mut raw: *mut hegel_c::HegelPrinter = ptr::null_mut();
Self::check(with_context(|ctx| unsafe {
hegel_c::hegel_printer_deferred(ctx, self.raw, &mut raw)
}))?;
Ok(PrinterHandle { raw })
}
pub(crate) fn resolve(&self) -> Result<(), PrinterCallError> {
Self::check(with_context(|ctx| unsafe {
hegel_c::hegel_printer_resolve(ctx, self.raw)
}))
}
pub(crate) fn begin_speculative(&self) -> Result<(), PrinterCallError> {
Self::check(with_context(|ctx| unsafe {
hegel_c::hegel_printer_begin_speculative(ctx, self.raw)
}))
}
pub(crate) fn commit_speculative(&self) -> Result<(), PrinterCallError> {
Self::check(with_context(|ctx| unsafe {
hegel_c::hegel_printer_commit_speculative(ctx, self.raw)
}))
}
pub(crate) fn abort_speculative(&self) -> Result<(), PrinterCallError> {
Self::check(with_context(|ctx| unsafe {
hegel_c::hegel_printer_abort_speculative(ctx, self.raw)
}))
}
pub(crate) fn value(&self) -> Result<String, PrinterCallError> {
let mut result = hegel_c::hegel_printer_value_result_t {
data: ptr::null_mut(),
len: 0,
};
Self::check(with_context(|ctx| unsafe {
hegel_c::hegel_printer_value(ctx, self.raw, &mut result)
}))?;
let bytes =
unsafe { std::slice::from_raw_parts(result.data.cast::<u8>(), result.len) }.to_vec();
require_ok(with_context(|ctx| unsafe {
hegel_c::hegel_printer_value_result_free(ctx, &mut result)
}));
Ok(string_from_engine_bytes(bytes))
}
}
impl Drop for PrinterHandle {
fn drop(&mut self) {
free_on_drop(|ctx| unsafe { hegel_c::hegel_printer_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 origin: *const c_char = ptr::null();
let mut blob: *const c_char = ptr::null();
with_context(|ctx| unsafe {
require_ok(hegel_c::hegel_failure_origin(ctx, f, &mut origin));
require_ok(hegel_c::hegel_failure_reproduction_blob(ctx, f, &mut blob));
let failure = Failure {
origin: cstr_opt(origin)
.unwrap_or_else(|| hegel_internal_error!("failure {index} has no origin")),
reproduce_blob: cstr_opt(blob),
};
require_ok(hegel_c::hegel_failure_free(ctx, f));
failure
})
}
}
impl Drop for RunResult {
fn drop(&mut self) {
free_on_drop(|ctx| unsafe { hegel_c::hegel_run_result_free(ctx, self.raw) });
}
}
pub(crate) struct Failure {
pub(crate) origin: String,
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_verbosity(v: Verbosity) -> u32 {
match v {
Verbosity::Quiet => hegel_c::hegel_verbosity_t::HEGEL_VERBOSITY_QUIET as u32,
Verbosity::Normal => hegel_c::hegel_verbosity_t::HEGEL_VERBOSITY_NORMAL as u32,
Verbosity::Verbose => hegel_c::hegel_verbosity_t::HEGEL_VERBOSITY_VERBOSE as u32,
Verbosity::Debug => hegel_c::hegel_verbosity_t::HEGEL_VERBOSITY_DEBUG as u32,
}
}
fn map_backend(backend: Backend) -> u32 {
match backend {
Backend::Default => hegel_c::hegel_backend_t::HEGEL_BACKEND_DEFAULT as u32,
Backend::Urandom => hegel_c::hegel_backend_t::HEGEL_BACKEND_URANDOM as u32,
}
}
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
}
fn verbosity_from_c(v: hegel_c::hegel_verbosity_t) -> Verbosity {
match v {
hegel_c::hegel_verbosity_t::HEGEL_VERBOSITY_QUIET => Verbosity::Quiet,
hegel_c::hegel_verbosity_t::HEGEL_VERBOSITY_NORMAL => Verbosity::Normal,
hegel_c::hegel_verbosity_t::HEGEL_VERBOSITY_VERBOSE => Verbosity::Verbose,
hegel_c::hegel_verbosity_t::HEGEL_VERBOSITY_DEBUG => Verbosity::Debug,
}
}
fn backend_from_c(backend: hegel_c::hegel_backend_t) -> Backend {
match backend {
hegel_c::hegel_backend_t::HEGEL_BACKEND_DEFAULT => Backend::Default,
hegel_c::hegel_backend_t::HEGEL_BACKEND_URANDOM => Backend::Urandom,
}
}
fn phases_from_bitmask(mask: u32) -> Vec<Phase> {
[
(
hegel_c::hegel_phase_t::HEGEL_PHASE_EXPLICIT,
Phase::Explicit,
),
(hegel_c::hegel_phase_t::HEGEL_PHASE_REUSE, Phase::Reuse),
(
hegel_c::hegel_phase_t::HEGEL_PHASE_GENERATE,
Phase::Generate,
),
(hegel_c::hegel_phase_t::HEGEL_PHASE_TARGET, Phase::Target),
(hegel_c::hegel_phase_t::HEGEL_PHASE_SHRINK, Phase::Shrink),
]
.into_iter()
.filter(|(bit, _)| mask & (*bit as u32) != 0)
.map(|(_, phase)| phase)
.collect()
}
fn health_checks_from_bitmask(mask: u32) -> Vec<HealthCheck> {
[
(
hegel_c::hegel_health_check_t::HEGEL_HC_FILTER_TOO_MUCH,
HealthCheck::FilterTooMuch,
),
(
hegel_c::hegel_health_check_t::HEGEL_HC_TOO_SLOW,
HealthCheck::TooSlow,
),
(
hegel_c::hegel_health_check_t::HEGEL_HC_TEST_CASES_TOO_LARGE,
HealthCheck::TestCasesTooLarge,
),
(
hegel_c::hegel_health_check_t::HEGEL_HC_LARGE_INITIAL_TEST_CASE,
HealthCheck::LargeInitialTestCase,
),
]
.into_iter()
.filter(|(bit, _)| mask & (*bit as u32) != 0)
.map(|(_, check)| check)
.collect()
}
#[cfg(test)]
#[path = "../tests/embedded/ffi_tests.rs"]
mod tests;