use crate::Settings;
use ciborium::Value;
#[doc(hidden)]
#[derive(Debug)]
#[non_exhaustive]
pub enum DataSourceError {
StopTest,
Assume,
InvalidArgument(String),
}
impl std::fmt::Display for DataSourceError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DataSourceError::StopTest => {
write!(f, "Backend ran out of data for this test case (StopTest)")
}
DataSourceError::Assume => write!(f, "Backend rejected the current draw (Assume)"),
DataSourceError::InvalidArgument(msg) => write!(f, "{}", msg),
}
}
}
impl std::error::Error for DataSourceError {}
pub trait DataSource: Send + Sync {
fn generate(&self, schema: &Value) -> Result<Value, DataSourceError>;
fn start_span(&self, label: u64) -> Result<(), DataSourceError>;
fn stop_span(&self, discard: bool) -> Result<(), DataSourceError>;
fn new_collection(&self, min_size: u64, max_size: Option<u64>) -> Result<i64, DataSourceError>;
fn collection_more(&self, collection_id: i64) -> Result<bool, DataSourceError>;
fn collection_reject(
&self,
collection_id: i64,
why: Option<&str>,
) -> Result<(), DataSourceError>;
fn new_state_machine(
&self,
rule_names: &[&str],
invariant_names: &[&str],
) -> Result<i64, DataSourceError>;
fn state_machine_next_rule(&self, state_machine_id: i64) -> Result<i64, DataSourceError>;
fn primitive_boolean(&self, p: f64, forced: Option<bool>) -> Result<bool, DataSourceError>;
fn new_pool(&self) -> Result<i64, DataSourceError>;
fn pool_add(&self, pool_id: i64) -> Result<i64, DataSourceError>;
fn pool_generate(&self, pool_id: i64, consume: bool) -> Result<i64, DataSourceError>;
fn target_observation(&self, score: f64, label: &str);
fn mark_complete(&self, result: &TestCaseResult);
}
#[derive(Debug, Clone)]
pub struct Failure {
pub panic_message: String,
pub origin: String,
pub reproduce_blob: Option<String>,
}
#[derive(Debug, Clone)]
pub enum TestCaseResult {
Valid,
Invalid,
Overrun,
Interesting(Failure),
}
#[derive(Debug, Clone)]
pub enum RunError {
HealthCheck(String),
Flaky(String),
NonDeterministic(String),
StaleBlob(String),
}
impl std::fmt::Display for RunError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RunError::HealthCheck(msg)
| RunError::Flaky(msg)
| RunError::NonDeterministic(msg)
| RunError::StaleBlob(msg) => write!(f, "{}", msg),
}
}
}
impl std::error::Error for RunError {}
#[derive(Debug)]
pub enum Exploration<C> {
Passed,
Counterexamples(Vec<C>),
}
#[derive(Debug)]
pub struct TestRunResult {
pub failures: Vec<Failure>,
}
pub trait TestRunner {
type Counterexample;
fn explore(
&self,
settings: &Settings,
database_key: Option<&str>,
run_case: &mut dyn FnMut(Box<dyn DataSource + Send + Sync>),
) -> Result<Exploration<Self::Counterexample>, RunError>;
fn replay_final(
&self,
counterexample: Self::Counterexample,
run_case: &mut dyn FnMut(Box<dyn DataSource + Send + Sync>),
) -> Result<Failure, RunError>;
}
pub(crate) fn collect_failures<R: TestRunner>(
runner: &R,
exploration: Exploration<R::Counterexample>,
run_case: &mut dyn FnMut(Box<dyn DataSource + Send + Sync>),
) -> Result<TestRunResult, RunError> {
match exploration {
Exploration::Passed => Ok(TestRunResult {
failures: Vec::new(),
}),
Exploration::Counterexamples(counterexamples) => {
let mut failures = Vec::new();
for counterexample in counterexamples {
failures.push(runner.replay_final(counterexample, run_case)?);
}
Ok(TestRunResult { failures })
}
}
}