1 unstable release
0.1.0 | Jul 15, 2024 |
---|
#470 in Testing
49KB
699 lines
A Rust crate with a more powerful assert!()
macro
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
.
- 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
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)
- (Though it is worth noting that fail-fast operators like
- Because this macro prints more than just the two sides of an
Dependencies
~1.3–2MB
~35K SLoC