use std::panic::Location;
use crate::GeneratedValue;
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: Stats,
}
#[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,
) -> Self {
Self::new(
seed,
stats.accepted_cases,
cases,
ErrorKind::Panic { message },
generated,
stats,
)
}
pub(crate) fn from_too_many_rejections(
seed: u64,
cases: usize,
last_reject_location: &'static Location<'static>,
generated: Vec<GeneratedValue>,
stats: Stats,
) -> Self {
Self::new(
seed,
stats.accepted_cases,
cases,
ErrorKind::TooManyRejections {
rejected_cases: stats.rejected_cases,
last_reject_location,
},
generated,
stats,
)
}
fn new(
seed: u64,
case_index: usize,
cases: usize,
kind: ErrorKind,
generated: Vec<GeneratedValue>,
stats: Stats,
) -> Self {
Self {
seed,
case_index,
cases,
kind,
generated,
stats,
}
}
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 {
format!(
"noprop::Runner::new({:#018x}).run({}, |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)?;
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: {} }},",
self.stats.accepted_cases, self.stats.rejected_cases, self.stats.total_samples,
)?;
if self.generated.is_empty() {
writeln!(f, " generated: [],")?;
} else {
writeln!(f, " generated: [")?;
for entry in &self.generated {
writeln!(f, " {entry:?}")?;
}
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={}",
self.stats.accepted_cases, self.stats.rejected_cases, self.stats.total_samples,
)?;
if !self.generated.is_empty() {
writeln!(f, "Generated values:")?;
for entry in &self.generated {
writeln!(f, " {entry:?}")?;
}
}
Ok(())
}
}
impl std::error::Error for RunError {}