1use std::fmt::Debug;
2
3#[macro_export]
5macro_rules! expect {
6 ($tested:expr) => {
7 $crate::blank_chain!($tested)
8 };
9}
10
11#[macro_export]
14macro_rules! expect_matches {
15 ( $e:expr , $($pat:pat)|+ ) => {
16 match $e {
17 $($pat)|+ => $crate::blank_chain!($e),
18 ref e => $crate::failure_chain!($e, format!("Expected {:?} to match {}",
19 e, stringify!($($pat)|+)))
20 }
21 };
22 ( $e:expr , $($pat:pat)|+ if $cond:expr ) => {
23 match $e {
24 $($pat)|+ if $cond => $crate::blank_chain!($e),
25 ref e => $crate::failure_chain!($e, format!("Expected {:?} to match {}",
26 e, stringify!($($pat)|+ if $cond)))
27 }
28 };
29 ( $e:expr , $($pat:pat)|+ , $($arg:tt)* ) => {
30 match $e {
31 $($pat)|+ => $crate::blank_chain!($e),
32 ref e => $crate::failure_chain!($e, format!("Expected {:?} to match {}: {}",
33 e, stringify!($($pat)|+), format_args!($($arg)*)))
34 }
35 };
36 ( $e:expr , $($pat:pat)|+ if $cond:expr , $($arg:tt)* ) => {
37 match $e {
38 $($pat)|+ if $cond => $crate::blank_chain!($e),
39 ref e => $crate::failure_chain!($e, format!("Expected {:?} to match {}: {}",
40 e, stringify!($($pat)|+ if $cond), format_args!($($arg)*)))
41 }
42 };
43}
44
45#[macro_export]
46macro_rules! blank_chain {
47 ($tested:expr) => {
48 $crate::ExpectationChain::from_expression($crate::ExpressionUnderTest {
49 actual: $tested,
50 tested_expression: std::stringify!($tested),
51 location: $crate::SourceLocation {
52 file: file!(),
53 line: line!(),
54 column: column!(),
55 },
56 })
57 };
58}
59
60#[macro_export]
61macro_rules! failure_chain {
62 ($tested:expr, $message:expr) => {
63 $crate::blank_chain!($tested).failure($message)
64 };
65}
66
67#[derive(Debug, PartialEq, Copy, Clone)]
68pub struct SourceLocation {
69 pub file: &'static str,
70 pub line: u32,
71 pub column: u32,
72}
73
74pub struct ExpressionUnderTest<'a, T> {
75 pub actual: &'a T,
76 pub tested_expression: &'static str,
77 pub location: SourceLocation,
78}
79
80pub trait Expectation<T> {
81 fn test(&self, actual: &T) -> bool;
82
83 fn message(&self, expression: &str, actual: &T) -> String;
84}
85
86pub struct ExpectationChain<'a, T> {
87 expression: ExpressionUnderTest<'a, T>,
88
89 in_negated_mode: bool,
90
91 negations: Vec<bool>,
92
93 soft_mode: bool,
94
95 expectations: Vec<Box<dyn Expectation<T> + 'a>>,
96}
97
98impl<'a, T> ExpectationChain<'a, T> {
99 pub fn from_expression(expression: ExpressionUnderTest<'a, T>) -> Self {
100 Self {
101 expression,
102 in_negated_mode: false,
103 negations: vec![],
104 soft_mode: false,
105 expectations: vec![],
106 }
107 }
108
109 #[allow(clippy::should_implement_trait)]
110 pub fn not(mut self) -> Self {
111 self.in_negated_mode = !self.in_negated_mode;
112
113 self
114 }
115
116 pub fn and(self) -> Self {
117 self
118 }
119
120 pub fn expecting(mut self, expectation: impl Expectation<T> + 'a) -> Self {
121 self.expectations.push(Box::new(expectation));
122
123 self.negations.push(self.in_negated_mode);
124
125 self.in_negated_mode = false;
126
127 self
128 }
129
130 pub fn failure(self, message: String) -> Self {
131 self.expecting(ExpectMatchFailure {
132 preset_message: message,
133 })
134 }
135
136 pub fn soft(mut self) -> Self {
137 self.soft_mode = true;
138
139 self
140 }
141
142 pub fn conclude_result(self) -> Result<(), String> {
143 let location = self.expression.location;
144 let mut message = format!(
145 "{}:{}:{}\nwhen testing expression\n\n",
146 location.file, location.line, location.column
147 );
148 message.push_str(&format!(" {}\n\n", self.expression.tested_expression));
149
150 let mut had_failure = false;
151 for i in 0..self.expectations.len() {
152 let expectation = self.expectations.get(i).unwrap();
153 let is_negated = self.negations.get(i).unwrap();
154
155 if !(is_negated ^ expectation.test(self.expression.actual)) {
156 had_failure = true;
157
158 let failure_message =
159 expectation.message(self.expression.tested_expression, self.expression.actual);
160 let failure_message = if *is_negated {
161 indented(" ", &format!("NOT {}", failure_message))
162 } else {
163 indented(" ", &failure_message)
164 };
165
166 message.push_str(&failure_message);
167
168 if !self.soft_mode {
169 break;
170 }
171 }
172 }
173
174 if had_failure {
175 Err(message)
176 } else {
177 Ok(())
178 }
179 }
180
181 pub fn conclude_panic(self) {
182 if let Err(message) = self.conclude_result() {
183 eprintln!("{}", message);
184 panic!()
185 }
186 }
187}
188
189fn indented(indentation: &str, s: &str) -> String {
190 let result: Vec<String> = s
191 .split('\n')
192 .map(|s| format!("{}{}", indentation, s))
193 .collect();
194
195 format!("{}\n", result.join("\n"))
196}
197
198struct ExpectMatchFailure {
199 preset_message: String,
200}
201
202impl<T> Expectation<T> for ExpectMatchFailure {
203 fn test(&self, _actual: &T) -> bool {
204 false
205 }
206
207 fn message(&self, _expression: &str, _actual: &T) -> String {
208 self.preset_message.clone()
209 }
210}