use anyhow::anyhow;
#[doc(hidden)]
pub fn msg(file: &str, line: u32, expr: &str, msg: &str) -> anyhow::Error {
anyhow!("\n\n\x1B[1m[{file}:{line}]\x1B[0m \x1B[4m{expr}\x1B[0m: \x1B[31m{msg}\x1B[0m\n")
}
#[macro_export]
macro_rules! expect {
( $a:expr ) => {{
if $a {
Ok(())
} else {
let expr_str = format!("expect({})", stringify!($a));
let msg = format!("Expected true but was false.");
Err($crate::msg(file!(), line!(), &expr_str, &msg))
}
}?};
}
#[macro_export]
macro_rules! expect_ok {
( $result:expr ) => {
match $result {
Ok(t) => Ok(t),
Err(ref e) => {
let expr_str = format!("expect_ok({})", stringify!($result));
let msg = format!("Expected Ok, got Err:\n{e:?})");
Err($crate::msg(file!(), line!(), &expr_str, &msg))
}
}?
};
}
#[macro_export]
macro_rules! expect_err {
( $result:expr ) => {
match $result {
Err(e) => Ok(e),
Ok(t) => {
let expr_str = format!("expect_err({})", stringify!($result));
let msg = format!("Expected Err, got Ok:\n{t:?}");
Err($crate::msg(file!(), line!(), &expr_str, &msg))
}
}?
};
}
#[macro_export]
macro_rules! expect_some {
( $option:expr ) => {
match $option {
Some(t) => Ok(t),
None => {
let expr_str = format!("expect_some({})", stringify!($option));
let msg = format!("Expected Some, got None");
Err($crate::msg(file!(), line!(), &expr_str, &msg))
}
}?
};
}
#[macro_export]
macro_rules! expect_none {
( $option:expr ) => {
match $option {
None => Ok(()),
Some(t) => {
let expr_str = format!("expect_none({})", stringify!($option));
let msg = format!("Expected None, got Some:\n{t:?})");
Err($crate::msg(file!(), line!(), &expr_str, &msg))
}
}?
};
}
#[macro_export]
macro_rules! expect_eq {
( $a:expr, $b:expr ) => {{
let a = $a;
let b = $b;
if a == b {
Ok(())
} else {
let expr_str = format!("expect_eq({}, {})", stringify!($a), stringify!($b));
let msg = format!("{:?} != {:?}, expected them to be equal.", a, b);
Err($crate::msg(file!(), line!(), &expr_str, &msg))
}
}?};
}
#[macro_export]
macro_rules! expect_ne {
( $a:expr, $b:expr ) => {{
let a = $a;
let b = $b;
if a != b {
Ok(())
} else {
let expr_str = format!("expect_ne({}, {})", stringify!($a), stringify!($b));
let msg = format!("{:?} == {:?}, expected them *not* to be equal.", a, b);
Err($crate::msg(file!(), line!(), &expr_str, &msg))
}
}?};
}
#[macro_export]
macro_rules! expect_empty {
( $a:expr ) => {{
let a = $a;
if a.is_empty() {
Ok(())
} else {
let expr_str = format!("expect_empty({})", stringify!($a));
let msg = format!("Expected empty, but contained elements:\n{:?}", a);
Err($crate::msg(file!(), line!(), &expr_str, &msg))
}
}?};
}
#[macro_export]
macro_rules! expect_not_empty {
( $a:expr ) => {{
let a = $a;
if !a.is_empty() {
Ok(())
} else {
let expr_str = format!("expect_not_empty({})", stringify!($a));
let msg = "Expected non-empty, but was empty.";
Err($crate::msg(file!(), line!(), &expr_str, &msg))
}
}?};
}
#[macro_export]
macro_rules! expect_contains {
( $container:expr, $element_or_substr:expr ) => {{
let container = $container;
let element_or_substr = $element_or_substr;
if container.contains(&element_or_substr) {
Ok(())
} else {
let expr_str = format!(
"expect_contains({}, {})",
stringify!($container),
stringify!($element_or_substr)
);
let msg = format!(
"Expected to contain {:?}, but did not:\n{:?}",
element_or_substr, container,
);
Err($crate::msg(file!(), line!(), &expr_str, &msg))
}
}?};
}
#[macro_export]
macro_rules! expect_some_eq {
( $some_a:expr, $b:expr ) => {{
let b = $b;
let msg: String = match $some_a {
Some(a) => {
if a == b {
"".to_string()
} else {
format!("{:?} != {:?}, expected them to be equal.", a, b)
}
}
None => format!("Expected {} to be Some, was None.", stringify!($some_a)),
};
if msg.is_empty() {
Ok(())
} else {
let expr_str = format!(
"expect_some_eq({}, {})",
stringify!($some_a),
stringify!($b)
);
Err($crate::msg(file!(), line!(), &expr_str, &msg))
}
}?};
}
#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
use anyhow::{anyhow, Result};
#[test]
fn expect__success__no_early_return() {
let test = || -> Result<()> {
expect!(1 + 1 == 2);
Err(anyhow!("no early return"))
};
assert!(test().is_err_and(|e| e.to_string().contains("no early return")));
}
#[test]
fn expect__failure__causes_early_return() {
let test = || -> Result<()> {
expect!(1 + 1 == 69); Ok(())
};
assert!(test().is_err());
}
#[test]
fn expect_ok__success__returns_ok_value() {
let test = || -> Result<i64> {
let result: Result<i64> = Ok(69);
let contents = expect_ok!(result);
Ok(contents)
};
let result = test();
assert!(result.is_ok());
assert_eq!(result.unwrap(), 69);
}
#[test]
fn expect_ok__failure__causes_early_return() {
let test = || -> Result<()> {
let result: Result<i64> = Err(anyhow!("ruh roh!"));
let _ = expect_ok!(result); Ok(()) };
assert!(test().is_err());
}
#[test]
fn expect_err__success__returns_err_value() {
let test = || -> Result<anyhow::Error> {
let result: Result<i64> = Err(anyhow!("ruh roh!"));
let err = expect_err!(result);
Ok(err)
};
let result = test();
assert!(result.is_ok());
assert!(result.unwrap().to_string().contains("ruh roh!"));
}
#[test]
fn expect_err__failure__causes_early_return() {
let test = || -> Result<()> {
let result: Result<i64> = Ok(69);
let _ = expect_err!(result); Ok(()) };
assert!(test().is_err());
}
#[test]
fn expect_some__success__returns_some_value() {
let test = || -> Result<i32> {
let some = Some(1337);
let contents = expect_some!(some);
Ok(contents)
};
let result = test();
assert!(result.is_ok());
assert_eq!(result.unwrap(), 1337);
}
#[test]
fn expect_some__failure__causes_early_return() {
let test = || -> Result<()> {
let _ = expect_some!(None::<i32>); Ok(()) };
assert!(test().is_err());
}
#[test]
fn expect_none__success__no_early_return() {
let test = || -> Result<()> {
expect_none!(None::<i64>);
Err(anyhow!("no early return"))
};
assert!(test().is_err_and(|e| e.to_string().contains("no early return")));
}
#[test]
fn expect_none__failure__causes_early_return() {
let test = || -> Result<()> {
expect_none!(Some(1337)); Ok(()) };
assert!(test().is_err());
}
#[test]
fn expect_eq__success__no_early_return() {
let test = || -> Result<()> {
expect_eq!(1, 1);
Err(anyhow!("no early return"))
};
assert!(test().is_err_and(|e| e.to_string().contains("no early return")));
}
#[test]
fn expect_eq__failure__causes_early_return() {
let test = || -> Result<()> {
expect_eq!(1, 2); Ok(()) };
assert!(test().is_err());
}
#[test]
fn expect_ne__success__no_early_return() {
let test = || -> Result<()> {
expect_ne!(1, 2);
Err(anyhow!("no early return"))
};
assert!(test().is_err_and(|e| e.to_string().contains("no early return")));
}
#[test]
fn expect_ne__failure__causes_early_return() {
let test = || -> Result<()> {
expect_ne!(1, 1); Ok(()) };
assert!(test().is_err());
}
#[test]
fn expect_empty__success__no_early_return() {
let test = || -> Result<()> {
let empty: Vec<i64> = vec![];
expect_empty!(empty);
Err(anyhow!("no early return"))
};
assert!(test().is_err_and(|e| e.to_string().contains("no early return")));
}
#[test]
fn expect_empty__failure__causes_early_return() {
let test = || -> Result<()> {
let not_empty: Vec<i64> = vec![1];
expect_empty!(not_empty); Ok(())
};
assert!(test().is_err());
}
#[test]
fn expect_not_empty__success__no_early_return() {
let test = || -> Result<()> {
let not_empty: Vec<i64> = vec![1];
expect_not_empty!(not_empty);
Err(anyhow!("no early return"))
};
assert!(test().is_err_and(|e| e.to_string().contains("no early return")));
}
#[test]
fn expect_not_empty__failure__causes_early_return() {
let test = || -> Result<()> {
let empty: Vec<i64> = vec![];
expect_not_empty!(empty); Ok(())
};
assert!(test().is_err());
}
#[test]
fn expect_not_empty__string__success__no_early_return() {
let test = || -> Result<()> {
expect_not_empty!("asdf");
Err(anyhow!("no early return"))
};
assert!(test().is_err_and(|e| e.to_string().contains("no early return")));
}
#[test]
fn expect_not_empty__string__failure__causes_early_return() {
let test = || -> Result<()> {
expect_not_empty!(""); Ok(())
};
assert!(test().is_err());
}
#[test]
fn expect_contains__vec_of_int_success__no_early_return() {
let test = || -> Result<()> {
let v: Vec<i64> = vec![42, 1337];
expect_contains!(v, 42);
Err(anyhow!("no early return"))
};
assert!(test().is_err_and(|e| e.to_string().contains("no early return")));
}
#[test]
fn expect_contains__vec_of_int_failure__causes_early_return() {
let test = || -> Result<()> {
let v: Vec<i64> = vec![42, 1337];
expect_contains!(v, 69);
Ok(())
};
assert!(test().is_err());
}
#[test]
fn expect_contains__string_success__no_early_return() {
let test = || -> Result<()> {
let superstring = "angelheaded hipsters burning for the ancient heavenly connection";
expect_contains!(superstring, "hipsters");
Err(anyhow!("no early return"))
};
assert!(test().is_err_and(|e| e.to_string().contains("no early return")));
}
#[test]
fn expect_contains__string_failure__causes_early_return() {
let test = || -> Result<()> {
let superstring = "angelheaded hipsters burning for the ancient heavenly connection";
expect_contains!(superstring, "yuppies");
Ok(())
};
assert!(test().is_err());
}
#[test]
fn expect_contains__string_failure__returns_correct_err_message() {
let test = || -> Result<()> {
let superstring = "angelheaded hipsters burning";
expect_contains!(superstring, "yuppies");
Ok(())
};
let result = test();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains(
"Expected to contain \"yuppies\", but did not:\n\"angelheaded hipsters burning\""
));
}
#[test]
fn expect_some_eq__int_success__no_early_return() {
let test = || -> Result<()> {
expect_some_eq!(Some(1), 1);
Err(anyhow!("no early return"))
};
assert!(test().is_err_and(|e| e.to_string().contains("no early return")));
}
#[test]
fn expect_some_eq__int_wrong_contents__causes_early_return() {
let test = || -> Result<()> {
expect_some_eq!(Some(1), 2); Ok(()) };
assert!(test().is_err());
}
#[test]
fn expect_some_eq__int_none__causes_early_return() {
let test = || -> Result<()> {
expect_some_eq!(None::<i32>, 2); Ok(()) };
assert!(test().is_err());
}
#[test]
fn expect_some_eq__string_success__no_early_return() {
let test = || -> Result<()> {
let contents = "george";
expect_some_eq!(Some(contents.to_string()), contents);
Err(anyhow!("no early return"))
};
assert!(test().is_err_and(|e| e.to_string().contains("no early return")));
}
}