1use core::fmt::{Debug, Display};
2
3use crate::AssertInfo;
4
5#[cfg(feature = "std")]
6#[must_use = "assertions do not fire unless returned from a test"]
7pub trait Assertion: Debug + Display + std::process::Termination {
9 fn test(&self) -> bool;
11
12 fn should_fail(self) -> ShouldFail<Self>
14 where
15 Self: Sized,
16 {
17 ShouldFail(self)
18 }
19
20 #[allow(
26 clippy::panic,
27 reason = "I agree with you clippy, but some people don't care about panics."
28 )]
29 fn unwrap(&self) {
30 assert!(self.test(), "failed assertion was unwrapped:\n{self}");
31 }
32}
33
34#[cfg(not(feature = "std"))]
35#[must_use = "assertions do not fire unless returned from a test"]
36pub trait Assertion: Debug + Display {
38 fn test(&self) -> bool;
40
41 fn should_fail(self) -> ShouldFail<Self>
43 where
44 Self: Sized,
45 {
46 ShouldFail(self)
47 }
48
49 #[allow(
55 clippy::panic,
56 reason = "I agree with you clippy, but some people don't care about panics."
57 )]
58 fn unwrap(&self) {
59 assert!(self.test(), "failed assertion was unwrapped:\n{self}");
60 }
61}
62
63#[must_use = "assertions do not fire unless returned from a test"]
65pub struct BinaryAssertion<T, U> {
66 lhs: T,
67 rhs: U,
68 test_result: bool,
69 test_repr: &'static str,
70 info: AssertInfo,
71}
72
73impl<T, U> Display for BinaryAssertion<T, U>
74where
75 T: Debug,
76 U: Debug,
77{
78 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
79 let Self {
80 lhs,
81 rhs,
82 test_result: _,
83 test_repr,
84 info: AssertInfo { file, line, column },
85 } = self;
86
87 write!(
88 f,
89 "assertion starting at {file}:{line}:{column} failed: `{test_repr}`\n lhs: {lhs:?}\n rhs: {rhs:?}"
90 )
91 }
92}
93
94impl<T, U> Debug for BinaryAssertion<T, U>
95where
96 T: Debug,
97 U: Debug,
98{
99 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
100 Display::fmt(self, f)
101 }
102}
103
104impl<T, U> BinaryAssertion<T, U> {
105 pub(crate) const fn new(
106 lhs: T,
107 rhs: U,
108 test_result: bool,
109 test_repr: &'static str,
110 info: AssertInfo,
111 ) -> Self {
112 Self {
113 lhs,
114 rhs,
115 test_result,
116 test_repr,
117 info,
118 }
119 }
120}
121
122#[cfg(feature = "std")]
123impl<T, U> std::process::Termination for BinaryAssertion<T, U>
124where
125 T: Debug,
126 U: Debug,
127{
128 fn report(self) -> std::process::ExitCode {
129 if self.test() {
130 std::process::ExitCode::SUCCESS
131 } else {
132 println!("{self}");
133 std::process::ExitCode::FAILURE
134 }
135 }
136}
137
138impl<T, U> Assertion for BinaryAssertion<T, U>
139where
140 T: Debug,
141 U: Debug,
142{
143 fn test(&self) -> bool {
144 self.test_result
145 }
146}
147
148#[must_use = "assertions do not fire unless returned from a test"]
150pub struct UnaryAssertion<T> {
151 this: T,
152 test_result: bool,
153 test_repr: &'static str,
154 info: AssertInfo,
155}
156
157impl<T> Display for UnaryAssertion<T>
158where
159 T: Debug,
160{
161 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
162 let Self {
163 this,
164 test_result: _,
165 test_repr,
166 info: AssertInfo { file, line, column },
167 } = self;
168
169 write!(
170 f,
171 "assertion starting at {file}:{line}:{column} failed: `{test_repr}`\nthis: {this:?}"
172 )
173 }
174}
175
176impl<T> Debug for UnaryAssertion<T>
177where
178 T: Debug,
179{
180 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
181 Display::fmt(self, f)
182 }
183}
184
185impl<T> UnaryAssertion<T> {
186 pub(crate) const fn new(
187 this: T,
188 test_result: bool,
189 test_repr: &'static str,
190 info: AssertInfo,
191 ) -> Self {
192 Self {
193 this,
194 test_result,
195 test_repr,
196 info,
197 }
198 }
199}
200
201#[cfg(feature = "std")]
202impl<T> std::process::Termination for UnaryAssertion<T>
203where
204 T: Debug,
205{
206 fn report(self) -> std::process::ExitCode {
207 if self.test() {
208 std::process::ExitCode::SUCCESS
209 } else {
210 println!("{self}");
211 std::process::ExitCode::FAILURE
212 }
213 }
214}
215
216impl<T> Assertion for UnaryAssertion<T>
217where
218 T: Debug,
219{
220 fn test(&self) -> bool {
221 self.test_result
222 }
223}
224
225#[must_use = "assertions do not fire unless returned from a test"]
227pub struct Succeed {
228 info: AssertInfo,
229}
230
231impl Display for Succeed {
232 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
233 let AssertInfo { file, line, column } = self.info;
234
235 write!(
236 f,
237 "assertion starting at {file}:{line}:{column} was forced success"
238 )
239 }
240}
241
242impl Debug for Succeed {
243 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
244 Display::fmt(self, f)
245 }
246}
247
248impl Succeed {
249 #[doc(hidden)]
250 pub const fn manual_constructor(file: &'static str, line: u32, column: u32) -> Self {
252 Self {
253 info: AssertInfo { file, line, column },
254 }
255 }
256}
257
258#[cfg(feature = "std")]
259impl std::process::Termination for Succeed {
260 fn report(self) -> std::process::ExitCode {
261 std::process::ExitCode::SUCCESS
262 }
263}
264
265impl Assertion for Succeed {
266 fn test(&self) -> bool {
267 true
268 }
269}
270
271#[macro_export]
280macro_rules! succeed {
281 () => {
282 $crate::assertion::Succeed::manual_constructor(
283 ::core::file!(),
284 ::core::line!(),
285 ::core::column!(),
286 )
287 };
288}
289
290#[must_use = "assertions do not fire unless returned from a test"]
292pub struct Fail {
293 info: AssertInfo,
294}
295
296impl Display for Fail {
297 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
298 let AssertInfo { file, line, column } = self.info;
299
300 write!(
301 f,
302 "assertion starting at {file}:{line}:{column} was forced fail"
303 )
304 }
305}
306
307impl Debug for Fail {
308 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
309 Display::fmt(self, f)
310 }
311}
312
313impl Fail {
314 #[doc(hidden)]
315 pub const fn manual_constructor(file: &'static str, line: u32, column: u32) -> Self {
317 Self {
318 info: AssertInfo { file, line, column },
319 }
320 }
321}
322
323#[cfg(feature = "std")]
324impl std::process::Termination for Fail {
325 fn report(self) -> std::process::ExitCode {
326 std::process::ExitCode::FAILURE
327 }
328}
329
330impl Assertion for Fail {
331 fn test(&self) -> bool {
332 false
333 }
334}
335
336#[macro_export]
345macro_rules! fail {
346 () => {
347 $crate::assertion::Fail::manual_constructor(
348 ::core::file!(),
349 ::core::line!(),
350 ::core::column!(),
351 )
352 };
353}
354
355pub struct ShouldFail<A: Assertion>(A);
358
359impl<A: Assertion> Display for ShouldFail<A> {
360 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
361 let inner = &self.0;
362 write!(f, "assertion expected to fail succeeded:\n{inner}")
363 }
364}
365
366#[cfg(feature = "std")]
367impl<A: Assertion> std::process::Termination for ShouldFail<A> {
368 fn report(self) -> std::process::ExitCode {
369 if self.0.test() {
370 println!("{self}");
371 std::process::ExitCode::FAILURE
372 } else {
373 std::process::ExitCode::SUCCESS
374 }
375 }
376}
377
378impl<A: Assertion> Assertion for ShouldFail<A> {
379 fn test(&self) -> bool {
380 !self.0.test()
381 }
382}
383
384impl<A: Assertion> Debug for ShouldFail<A> {
385 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
386 Display::fmt(self, f)
387 }
388}
389
390#[cfg(feature = "alloc")]
391pub use multi_dyn_assertion::MultiDynAssertion;
392
393#[cfg(feature = "alloc")]
394mod multi_dyn_assertion {
395 use super::{Assertion, Debug, Display};
396
397 extern crate alloc;
398 use alloc::boxed::Box;
399
400 pub struct MultiDynAssertion<const N: usize>([Box<dyn Assertion>; N]);
405
406 impl<const N: usize> Display for MultiDynAssertion<N> {
407 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
408 write!(f, "assertion failures in a multi-assert:")?;
409 for (i, assertion) in self.0.iter().enumerate() {
410 if assertion.test() {
411 write!(f, "\n{i}. [ok]")?;
412 } else {
413 writeln!(f, "\n{i}. {assertion}")?;
414 }
415 }
416 Ok(())
417 }
418 }
419
420 impl<const N: usize> MultiDynAssertion<N> {
421 #[must_use]
422 #[doc(hidden)]
423 pub fn new(assertions: [Box<dyn Assertion>; N]) -> Self {
425 Self(assertions)
426 }
427 }
428
429 #[cfg(feature = "std")]
430 impl<const N: usize> std::process::Termination for MultiDynAssertion<N> {
431 fn report(self) -> std::process::ExitCode {
432 if self.test() {
433 std::process::ExitCode::SUCCESS
434 } else {
435 println!("{self}");
436 std::process::ExitCode::FAILURE
437 }
438 }
439 }
440
441 impl<const N: usize> Assertion for MultiDynAssertion<N> {
442 fn test(&self) -> bool {
443 self.0.iter().all(|a| a.test())
444 }
445 }
446
447 impl<const N: usize> Debug for MultiDynAssertion<N> {
448 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
449 Display::fmt(self, f)
450 }
451 }
452}
453
454pub struct MultiAssertion<A: Assertion, const N: usize>([A; N]);
459
460impl<A: Assertion, const N: usize> Display for MultiAssertion<A, N> {
461 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
462 write!(f, "assertion failures in a multi-assert:")?;
463 for (i, assertion) in self.0.iter().enumerate() {
464 if assertion.test() {
465 write!(f, "\n{i}. [ok]")?;
466 } else {
467 writeln!(f, "\n{i}. {assertion}")?;
468 }
469 }
470 Ok(())
471 }
472}
473
474impl<A: Assertion, const N: usize> MultiAssertion<A, N> {
475 #[must_use]
476 pub const fn new(assertions: [A; N]) -> Self {
478 Self(assertions)
479 }
480}
481
482#[cfg(feature = "std")]
483impl<A: Assertion, const N: usize> std::process::Termination for MultiAssertion<A, N> {
484 fn report(self) -> std::process::ExitCode {
485 if self.test() {
486 std::process::ExitCode::SUCCESS
487 } else {
488 println!("{self}");
489 std::process::ExitCode::FAILURE
490 }
491 }
492}
493
494impl<A: Assertion, const N: usize> Assertion for MultiAssertion<A, N> {
495 fn test(&self) -> bool {
496 self.0.iter().all(Assertion::test)
497 }
498}
499
500impl<A: Assertion, const N: usize> Debug for MultiAssertion<A, N> {
501 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
502 Display::fmt(self, f)
503 }
504}
505
506#[cfg(feature = "alloc")]
507#[cfg_attr(doc, doc = include_str!("multi_assert_doc.md"))]
508#[macro_export]
509macro_rules! multi_assert {
510 [#[dyn] $($this:expr),+ $(,)?] => {
511 $crate::assertion::MultiDynAssertion::new([
512 $(Box::new($this),)+
513 ])
514 };
515 [$($this:expr),+ $(,)?] => {
516 $crate::assertion::MultiAssertion::new([
517 $($this,)+
518 ])
519 };
520}
521
522#[cfg(not(feature = "alloc"))]
523#[cfg_attr(doc, doc = include_str!("multi_assert_doc.md"))]
524#[macro_export]
525macro_rules! multi_assert {
526 [#[dyn] $($this:expr),+ $(,)?] => {
527 compile_error!("cannot use #[dyn] if the no_alloc feature is enabled")
528 };
529 [$($this:expr),+ $(,)?] => {
530 $crate::assertion::MultiAssertion::new([
531 $($this,)+
532 ])
533 };
534}