2 releases

new 0.1.1 Feb 15, 2025
0.1.0 Jul 15, 2024

#863 in Testing

Download history 4/week @ 2024-12-07

63 downloads per month

MIT/Apache

50KB
699 lines

A Rust crate with a more powerful assert!() macro

Tests Crates.io Documentation Dependency status

One Assert

TL;DR

Why have separate macros for assert_eq and assert_ne (and assert_gt etc. with other crates) when you can just get the same output with assert!(a == b) (or assert!(a != b), assert!(a > b), …)? This crate provides a single assert! macro that analyzes the expression to provide more detailed output on failure.

Introduction

Rust’s standard library provides the assert, assert_eq and assert_ne macros. There are however some inconveniences with these, like how there are no specialization for other inequalities, like assert_ge for >= etc, or how the names only differ in one or two letters (assert_eq, assert_ne, assert_ge, assert_gt, …) and are thus easy to mix up at a glance.

The main reason for not adding more macros is that they can be represented just fine with assert!(a >= b), so there is no need for a separate macro for every use case.

But that begs the question: Why do we have assert_eq and assert_ne in the first place?

The practical reason: assert_eq!(a, b) provides better output than assert!(a == b):

let x = 1;
let msg = catch_panic!({ assert!(x == 2); });
assert_eq!(msg, "assertion failed: x == 2");

let msg = catch_panic!({ assert_eq!(x, 2); });
assert_eq!(msg, "assertion `left == right` failed
  left: 1
 right: 2"
);

As you can see, assert_eq is able to provide detailed info on what the individual values were. But: That doesn’t have to be the case. Rust has hygienic and procedural macros, so we can just make assert!(a == b) work the same as assert_eq!(a, b):

let x = 1;
let msg = catch_panic!({ one_assert::assert!(x == 2); });
assert_eq!(msg, "assertion `x == 2` failed
     left: 1
    right: 2"
);

And now we can expand this to as many operators as we want:

let x = 1;
let msg = catch_panic!({ one_assert::assert!(x > 2); });
assert_eq!(msg, "assertion `x > 2` failed
     left: 1
    right: 2"
);

Examples

let x = 1;
let msg = catch_panic!({ one_assert::assert!(x > 2); });
assert_eq!(msg, "assertion `x > 2` failed
     left: 1
    right: 2"
);

let msg = catch_panic!({ one_assert::assert!(x != 1, "x ({}) should not be 1", x); });
assert_eq!(msg, "assertion `x != 1` failed: x (1) should not be 1
     left: 1
    right: 1"
);

let s = "Hello World";
let msg = catch_panic!({ one_assert::assert!(s.starts_with("hello")); });
assert_eq!(msg, r#"assertion `s.starts_with("hello")` failed
     self: "Hello World"
    arg 0: "hello""#
);

Limitations

  • Several Components need to implement Debug
    • The macro will take whatever part of the expression is considered useful and debug print it. This means that those parts need to implement Debug.
    • What is printed as part of any given expression type is subject to change, so it is recommended to only use this in code where pretty much everything implements Debug.
  • Debug printing happens even if the assertion passes
    • Because this macro prints more than just the two sides of an == or != comparison, it has to deal with the fact that some values are moved during the evaluation of the expression. This means that the values have to be printed in advance.
    • Consequence: Don’t use this macro in performance-critical code.
    • Note however, that the expression and each part of it is only evaluated once.
      • (Though it is worth noting that fail-fast operators like && might normally only evaluate the left side and stop, but with this macro it will always evaluate both sides)

Changelog

See Changelog.md

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.

Dependencies

~215–660KB
~16K SLoC