use crate::test_case::TestCase;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum HealthCheck {
FilterTooMuch,
TooSlow,
TestCasesTooLarge,
LargeInitialTestCase,
}
impl HealthCheck {
pub const fn all() -> [HealthCheck; 4] {
[
HealthCheck::FilterTooMuch,
HealthCheck::TooSlow,
HealthCheck::TestCasesTooLarge,
HealthCheck::LargeInitialTestCase,
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Phase {
Explicit,
Reuse,
Generate,
Target,
Shrink,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Backend {
Default,
Urandom,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Verbosity {
Quiet,
Normal,
Verbose,
Debug,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum NondeterminismStrictness {
Quiet,
Warn,
Error,
}
#[derive(Debug, Clone)]
pub struct Settings {
pub(crate) test_cases: u64,
pub(crate) verbosity: Verbosity,
pub(crate) nondeterminism_strictness: NondeterminismStrictness,
pub(crate) seed: Option<u64>,
pub(crate) derandomize: bool,
pub(crate) database: Database,
pub(crate) suppress_health_check: Vec<HealthCheck>,
pub(crate) phases: Vec<Phase>,
pub(crate) report_multiple_failures: bool,
pub(crate) show_statistics: bool,
pub(crate) print_blob: bool,
pub(crate) backend: Backend,
}
impl Settings {
pub fn new() -> Self {
Self::from_resolution(crate::ffi::settings_from_profile(None))
}
pub fn from_profile(name: &str) -> Self {
Self::from_resolution(Self::try_from_profile(name).map_err(|e| e.message))
}
pub fn try_from_profile(name: &str) -> Result<Self, ProfileError> {
crate::ffi::settings_from_profile(Some(name)).map_err(|message| ProfileError { message })
}
fn from_resolution(resolution: Result<Self, String>) -> Self {
resolution.unwrap_or_else(|message| crate::test_case::invalid_argument!("{message}"))
}
pub fn set_default_profile(name: &str) {
if let Err(message) = crate::ffi::set_default_profile(Some(name)) {
crate::test_case::invalid_argument!("{message}");
}
}
pub fn register_profile(name: &str, settings: Settings) {
if let Err(message) = crate::ffi::register_profile(name, &settings) {
crate::test_case::invalid_argument!("{message}");
}
}
pub fn backend(mut self, backend: Backend) -> Self {
self.backend = backend;
self
}
pub fn test_cases(mut self, n: u64) -> Self {
self.test_cases = n;
self
}
pub fn verbosity(mut self, verbosity: Verbosity) -> Self {
self.verbosity = verbosity;
self
}
pub fn nondeterminism_strictness(mut self, strictness: NondeterminismStrictness) -> Self {
self.nondeterminism_strictness = strictness;
self
}
pub fn seed(mut self, seed: Option<u64>) -> Self {
self.seed = seed;
self
}
pub fn derandomize(mut self, derandomize: bool) -> Self {
self.derandomize = derandomize;
self
}
pub fn database(mut self, database: Option<String>) -> Self {
self.database = match database {
None => Database::Disabled,
Some(path) => Database::Path(path),
};
self
}
pub fn phases(mut self, phases: impl IntoIterator<Item = Phase>) -> Self {
self.phases = phases.into_iter().collect();
self
}
pub fn print_blob(mut self, print_blob: bool) -> Self {
self.print_blob = print_blob;
self
}
pub fn suppress_health_check(mut self, checks: impl IntoIterator<Item = HealthCheck>) -> Self {
self.suppress_health_check = checks.into_iter().collect();
self
}
pub fn has_phase(&self, phase: Phase) -> bool {
self.phases.contains(&phase)
}
pub fn show_statistics(mut self, show_statistics: bool) -> Self {
self.show_statistics = show_statistics;
self
}
pub(crate) fn for_single_test_case(mut self) -> Self {
self.test_cases = 1;
for check in [HealthCheck::TooSlow, HealthCheck::TestCasesTooLarge] {
if !self.suppress_health_check.contains(&check) {
self.suppress_health_check.push(check);
}
}
self
}
pub fn report_multiple_failures(mut self, report_multiple_failures: bool) -> Self {
self.report_multiple_failures = report_multiple_failures;
self
}
}
impl Default for Settings {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Database {
Unset,
Disabled,
Path(String),
}
#[derive(Debug, Clone)]
pub struct ProfileError {
pub(crate) message: String,
}
impl std::fmt::Display for ProfileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for ProfileError {}
#[doc(hidden)]
pub fn hegel<F>(test_fn: F)
where
F: FnMut(TestCase),
{
Hegel::new(test_fn).run();
}
pub(crate) fn parse_bool(s: &str) -> Option<bool> {
match s {
"true" | "1" | "yes" => Some(true),
"false" | "0" | "no" => Some(false),
_ => None,
}
}
#[doc(hidden)]
pub struct TestLocation {
pub function: String,
pub file: String,
pub class: String,
pub begin_line: u32,
}
#[doc(hidden)]
pub struct Hegel<F> {
test_fn: F,
database_key: Option<String>,
test_location: Option<TestLocation>,
settings: Settings,
reproduce_failure: Option<String>,
single_test_case: bool,
}
impl<F> Hegel<F>
where
F: FnMut(TestCase),
{
pub fn new(test_fn: F) -> Self {
Self {
test_fn,
database_key: None,
settings: Settings::new(),
test_location: None,
reproduce_failure: None,
single_test_case: false,
}
}
pub fn settings(mut self, settings: Settings) -> Self {
self.settings = settings;
self
}
#[doc(hidden)]
pub fn __database_key(mut self, key: String) -> Self {
self.database_key = Some(key);
self
}
#[doc(hidden)]
pub fn __single_test_case(mut self) -> Self {
self.single_test_case = true;
self
}
#[doc(hidden)]
pub fn test_location(mut self, location: TestLocation) -> Self {
self.test_location = Some(location);
self
}
pub fn reproduce_failure(mut self, blob: impl Into<String>) -> Self {
if self.reproduce_failure.is_none() {
self.reproduce_failure = Some(blob.into());
}
self
}
pub fn run(self) {
let mut settings = self.settings;
if self.single_test_case {
settings = settings.for_single_test_case();
}
if let Some(blob) = self.reproduce_failure {
crate::run_lifecycle::drive_blob_replay(
self.test_fn,
&settings,
self.database_key.as_deref(),
&blob,
self.test_location.as_ref(),
);
return;
}
crate::run_lifecycle::drive(
self.test_fn,
&settings,
self.database_key.as_deref(),
self.test_location.as_ref(),
);
}
}
#[cfg(test)]
#[path = "../tests/embedded/runner_tests.rs"]
mod tests;