use std::process::ExitCode;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Verdict {
Success,
Error,
}
impl Verdict {
pub fn label(self) -> &'static str {
match self {
Verdict::Success => "SUCCESS",
Verdict::Error => "ERROR",
}
}
pub fn exit_code(self) -> ExitCode {
match self {
Verdict::Success => ExitCode::SUCCESS,
Verdict::Error => ExitCode::from(1),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Expect {
AtLeast(u64),
Eq(u64),
Gt(u64),
Lt(u64),
}
impl Default for Expect {
fn default() -> Self {
Expect::AtLeast(1)
}
}
impl Expect {
pub fn parse(spec: &str) -> Result<Expect, String> {
let spec = spec.trim();
match spec {
"any" => return Ok(Expect::AtLeast(1)),
"none" => return Ok(Expect::Eq(0)),
"" => return Err("empty --expect spec".to_string()),
_ => {}
}
let (ctor, body): (fn(u64) -> Expect, &str) = if let Some(r) = spec.strip_prefix('=') {
(Expect::Eq, r)
} else if let Some(r) = spec.strip_prefix('+') {
(Expect::Gt, r)
} else if let Some(r) = spec.strip_prefix('-') {
(Expect::Lt, r)
} else {
(Expect::AtLeast, spec)
};
let n: u64 = body
.trim()
.parse()
.map_err(|_| format!("invalid count in --expect '{spec}'"))?;
Ok(ctor(n))
}
pub fn eval(self, count: u64) -> Verdict {
let pass = match self {
Expect::AtLeast(n) => count >= n,
Expect::Eq(n) => count == n,
Expect::Gt(n) => count > n,
Expect::Lt(n) => count < n,
};
if pass {
Verdict::Success
} else {
Verdict::Error
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_expectation_is_any() {
assert_eq!(Expect::default(), Expect::AtLeast(1));
assert_eq!(Expect::default().eval(0), Verdict::Error);
assert_eq!(Expect::default().eval(3), Verdict::Success);
}
#[test]
fn keywords_parse_to_numeric_forms() {
assert_eq!(Expect::parse("any").unwrap(), Expect::AtLeast(1));
assert_eq!(Expect::parse("none").unwrap(), Expect::Eq(0));
}
#[test]
fn threshold_grammar_matches_size_conventions() {
assert_eq!(Expect::parse("3").unwrap(), Expect::AtLeast(3));
assert_eq!(Expect::parse("+3").unwrap(), Expect::Gt(3));
assert_eq!(Expect::parse("-3").unwrap(), Expect::Lt(3));
assert_eq!(Expect::parse("=3").unwrap(), Expect::Eq(3));
}
#[test]
fn none_passes_only_on_zero() {
let none = Expect::parse("none").unwrap();
assert_eq!(none.eval(0), Verdict::Success);
assert_eq!(none.eval(1), Verdict::Error);
}
#[test]
fn thresholds_classify_counts() {
assert_eq!(Expect::Gt(0).eval(0), Verdict::Error);
assert_eq!(Expect::Gt(0).eval(1), Verdict::Success);
assert_eq!(Expect::Lt(10).eval(9), Verdict::Success);
assert_eq!(Expect::Lt(10).eval(10), Verdict::Error);
assert_eq!(Expect::Eq(1).eval(1), Verdict::Success);
assert_eq!(Expect::Eq(1).eval(2), Verdict::Error);
}
#[test]
fn rejects_non_numeric_specs() {
assert!(Expect::parse("lots").is_err());
assert!(Expect::parse("+").is_err());
assert!(Expect::parse("").is_err());
}
}