use std::panic::Location;
use crate::GeneratedValue;
use crate::rng::Feature;
use crate::runner::Stats;
pub type TestResult<T = ()> = std::result::Result<T, Box<dyn std::error::Error>>;
pub type RunResult = std::result::Result<(), RunError>;
pub struct RunError {
seed: u64,
case_index: usize,
cases: usize,
kind: ErrorKind,
generated: Vec<GeneratedValue>,
stats: Box<Stats>,
policy: SearchPolicy,
semantic: Option<Box<SemanticFailureReport>>,
}
struct SemanticFailureReport {
features: Vec<Feature>,
candidate_index: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SearchPolicy {
Uniform,
FeedbackGuided,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RunErrorKind {
PropertyFailure,
TooManyRejections,
}
enum ErrorKind {
Panic { message: String },
TooManyRejections {
rejected_cases: usize,
last_reject_location: &'static Location<'static>,
},
}
impl RunErrorKind {
fn of(kind: &ErrorKind) -> Self {
match kind {
ErrorKind::Panic { .. } => RunErrorKind::PropertyFailure,
ErrorKind::TooManyRejections { .. } => RunErrorKind::TooManyRejections,
}
}
}
impl RunError {
pub(crate) fn from_panic(
seed: u64,
cases: usize,
message: String,
generated: Vec<GeneratedValue>,
stats: Stats,
policy: SearchPolicy,
) -> Self {
Self::new(
seed,
stats.accepted_cases,
cases,
ErrorKind::Panic { message },
generated,
stats,
policy,
)
}
pub(crate) fn with_semantic(mut self, features: Vec<Feature>, candidate_index: usize) -> Self {
self.semantic = Some(Box::new(SemanticFailureReport {
features,
candidate_index,
}));
self
}
pub(crate) fn from_too_many_rejections(
seed: u64,
cases: usize,
last_reject_location: &'static Location<'static>,
generated: Vec<GeneratedValue>,
stats: Stats,
policy: SearchPolicy,
) -> Self {
Self::new(
seed,
stats.accepted_cases,
cases,
ErrorKind::TooManyRejections {
rejected_cases: stats.rejected_cases,
last_reject_location,
},
generated,
stats,
policy,
)
}
fn new(
seed: u64,
case_index: usize,
cases: usize,
kind: ErrorKind,
generated: Vec<GeneratedValue>,
stats: Stats,
policy: SearchPolicy,
) -> Self {
Self {
seed,
case_index,
cases,
kind,
generated,
stats: Box::new(stats),
policy,
semantic: None,
}
}
pub fn seed(&self) -> u64 {
self.seed
}
pub fn case_index(&self) -> usize {
self.case_index
}
pub fn generated(&self) -> &[GeneratedValue] {
&self.generated
}
pub fn stats(&self) -> Stats {
*self.stats
}
pub fn kind(&self) -> RunErrorKind {
RunErrorKind::of(&self.kind)
}
}
impl RunError {
fn reproduce_command(&self) -> String {
match self.policy {
SearchPolicy::Uniform => format!(
"noprop::Runner::new({:#018x}).run({}, |ctx| ...)",
self.seed, self.cases
),
SearchPolicy::FeedbackGuided => format!(
"noprop::Runner::new({:#018x}).run_feedback_guided({}, |ctx| ...)",
self.seed, self.cases
),
}
}
}
impl std::fmt::Debug for RunError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "RunError {{")?;
writeln!(f, " seed: {:#018x},", self.seed)?;
writeln!(f, " case_index: {},", self.case_index)?;
if let Some(report) = &self.semantic {
writeln!(f, " candidate_index: {},", report.candidate_index)?;
}
writeln!(f, " policy: {:?},", self.policy)?;
match &self.kind {
ErrorKind::Panic { message } => {
writeln!(f, " panic: {message:?},")?;
}
ErrorKind::TooManyRejections {
rejected_cases,
last_reject_location,
} => {
writeln!(
f,
" too_many_rejections: {{ rejected: {rejected_cases}, last_reject_at: {}:{} }},",
last_reject_location.file(),
last_reject_location.line(),
)?;
}
}
writeln!(f, " reproduce: {},", self.reproduce_command())?;
writeln!(
f,
" stats: {{ accepted: {}, rejected: {}, total_samples: {}, discovered_features: {}, max_corpus_size: {} }},",
self.stats.accepted_cases,
self.stats.rejected_cases,
self.stats.total_samples,
self.stats.discovered_features,
self.stats.max_corpus_size,
)?;
if self.generated.is_empty() {
writeln!(f, " generated: [],")?;
} else {
writeln!(f, " generated: [")?;
for entry in &self.generated {
writeln!(f, " {entry:?}")?;
}
writeln!(f, " ],")?;
}
if let Some(report) = &self.semantic
&& !report.features.is_empty()
{
writeln!(f, " semantic_features: [")?;
for feature in &report.features {
writeln!(f, " {},", feature.display_repr())?;
}
writeln!(f, " ],")?;
}
write!(f, "}}")
}
}
impl std::fmt::Display for RunError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.kind {
ErrorKind::Panic { message } => {
writeln!(
f,
"noprop failure at case {} (seed={:#018x}): {}",
self.case_index, self.seed, message
)?;
}
ErrorKind::TooManyRejections {
rejected_cases,
last_reject_location,
} => {
writeln!(
f,
"noprop too many rejections at case {} (seed={:#018x}): \
{rejected_cases} rejected case(s), last reject at {}:{}",
self.case_index,
self.seed,
last_reject_location.file(),
last_reject_location.line(),
)?;
}
}
writeln!(f, "reproduce with: {}", self.reproduce_command())?;
writeln!(
f,
"stats: accepted={}, rejected={}, total_samples={}, discovered_features={}, max_corpus_size={}",
self.stats.accepted_cases,
self.stats.rejected_cases,
self.stats.total_samples,
self.stats.discovered_features,
self.stats.max_corpus_size,
)?;
if !self.generated.is_empty() {
writeln!(f, "Generated values:")?;
for entry in &self.generated {
writeln!(f, " {entry:?}")?;
}
}
if let Some(report) = &self.semantic
&& !report.features.is_empty()
{
writeln!(f, "Semantic features:")?;
for feature in &report.features {
writeln!(f, " {}", feature.display_repr())?;
}
}
Ok(())
}
}
impl std::error::Error for RunError {}