galvanic_assert/lib.rs
1/* Copyright 2017 Christopher Bacher
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16//! Galvanic-assert: Matcher-based assertions for easier testing
17//! ============================================================
18//! This crate provides a new assertion macros (`assert_that!`, `expect_that!`, `get_expectation_for!`) based on **matching predicates** (matchers) to
19//!
20//! * make **writing** asserts easier
21//! * make **reading** asserts comprehendable
22//! * easily **extend** the assertion framework
23//! * provide a large list **common matchers**
24//! * integrate with **galvanic-test** and **galvanic-mock** (both still in development ... stay tuned!)
25//! * be used with your favourite test framework
26//!
27//! The crate will be part of **galvanic**---a complete test framework for **Rust**.
28
29use std::fmt::{Debug, Display, Formatter, Result as FormatResult};
30
31/// States that the asserted value satisfies the required properties of the supplied `Matcher`.
32///
33/// The postulated assertion is verfied immediately and panics if it is not satisfied.
34/// The macro comes in three different forms:
35///
36/// 1. Assert that some expression is true, supplied with an optional error message.
37///
38/// ```rust,ignore
39/// assert_that!(EXPRESSION);
40/// assert_that!(EXPRESSION, otherwise "some error message");
41/// ```
42/// 2. Assert that some expression satifies the properties of some `Matcher`.
43/// Expressions used with `Matcher`s **must return a reference** to a value.
44/// The `Matcher` is either predefined, a user defined type with a `Matcher` implementation, or a closure returning a `MatchResult`.
45///
46/// ```rust,ignore
47/// assert_that!(&1, eq(1));
48/// assert_that!(&1, |&x| {
49/// let builder = MatchResultBuilder::for_("my_matcher");
50/// if x == 1 { builder.matched } else { builder.failed_because("some reason") }
51/// })
52/// ```
53/// 3. Assert that some expression is expected to panic/not panic.
54///
55/// ```rust,ignore
56/// assert_that!(panic!("panic"), panics);
57/// assert_that!(1+1, does not panic);
58/// ```
59#[macro_export]
60macro_rules! assert_that {
61 ( $actual: expr, panics ) => {{
62 let result = std::panic::catch_unwind(|| { $actual; });
63 if result.is_ok() {
64 panic!("\nFailed assertion; expected expression to panic")
65 }
66 }};
67 ( $actual: expr, does not panic ) => {
68 let result = std::panic::catch_unwind(|| { $actual; });
69 if result.is_err() {
70 panic!("\nFailed assertion; expression panicked unexpectantly")
71 }
72 };
73 ( $actual: expr) => {{
74 if !$actual {
75 panic!("\nFailed assertion; '{}' is not true", stringify!($actual));
76 }
77 }};
78 ( $actual: expr , otherwise $reason: expr ) => {{
79 if !$actual {
80 panic!("\nFailed assertion; expression '{}' is not true,\n Because: {}",
81 stringify!($actual), $reason
82 );
83 }
84 }};
85 ( $actual: expr, $matcher: expr ) => {{
86 #[allow(unused_imports)]
87 use galvanic_assert::{MatchResult, Matcher};
88 // store the actual value to borrow it
89 let value = $actual;
90 // store matcher so it's dropped before the actual value (reverse order of declaration)
91 let m = $matcher;
92 match m.check(value) {
93 MatchResult::Matched { .. } => { },
94 MatchResult::Failed { name, reason } => {
95 panic!("\nFailed assertion of matcher: {}\n{}", name, reason)
96 }
97 }
98 }};
99}
100
101/// States that the asserted values satisfies the required properties of the supplied `Matcher`
102/// and returns an `Expectation` object to inspect the results at a later time.
103///
104/// The postulated assertion is verfied immediately,
105/// but the returned `Expectation` defers a potential panic either until `Expectation::verify` is called
106/// or the `Expectation` object is dropped.
107/// It is safe for multiple expectations to fail the assertion code will prevent nested panics.
108///
109/// The macro comes in three different forms:
110///
111/// 1. Expect that some expression is true, supplied with an optional error message.
112///
113/// ```rust,ignore
114/// let e1 = get_expectation_for!(EXPRESSION);
115/// let e2 = get_expectation_for!(EXPRESSION, otherwise "some error message");
116/// ```
117/// 2. Expect that some expression satifies the properties of some `Matcher`.
118/// Expressions used with `Matcher`s **must return a reference** to a value.
119/// The `Matcher` is either predefined, a user defined type with a `Matcher` implementation, or a closure returning a `MatchResult`.
120///
121/// ```rust,ignore
122/// let e1 = get_expectation_for!(&1, eq(1));
123/// let e2 = get_expectation_for!(&1, |x| {
124/// let builder = MatchResultBuilder::for_("my_matcher");
125/// if x == 1 { builder.matched } else { builder.failed_because("some reason") }
126/// })
127/// ```
128/// 3. Expect that some expression is expected to panic/not panic.
129///
130/// ```rust,ignore
131/// let e1 = get_expectation_for!(panic!("panic"), panics);
132/// let e2 = get_expectation_for!(&1+1, does not panic);
133/// ```
134///
135/// An expectation can be verfied manually
136///
137/// ```rust,ignore
138/// let e1 = get_expectation_for!(&1+1, equal_to(0));
139/// let e2 = get_expectation_for!(&1+1, less_than(4)); // is executed
140/// e1.verify();
141/// let e3 = get_expectation_for!(&1+1, panics); // is never executed as e1 panics
142/// ```
143/// or is automatically verfied on drop.
144///
145/// ```rust,ignore
146/// {
147/// let e1 = get_expectation_for!(&1+1, equal_to(0));
148/// let e2 = get_expectation_for!(&1+1, less_than(4)); // is executed
149/// }
150/// let e3 = get_expectation_for!(1+1, panics); // is never executed as e1 panics
151/// ```
152#[macro_export]
153macro_rules! get_expectation_for {
154 ( $actual: expr, panics ) => {{
155 use galvanic_assert::Expectation;
156 let result = std::panic::catch_unwind(|| { $actual; });
157 if result.is_ok() {
158 let assertion = format!("'{}, panics'", stringify!($actual));
159 Expectation::failed(assertion, file!().to_string(), line!(),
160 "Expected expression to panic".to_string()
161 )
162 } else {
163 Expectation::satisfied()
164 }
165 }};
166 ( $actual: expr, does not panic ) => {{
167 use galvanic_assert::Expectation;
168 let result = std::panic::catch_unwind(|| { $actual; });
169 if result.is_err() {
170 let assertion = format!("'{}, does not panic'", stringify!($actual));
171 Expectation::failed(assertion, file!().to_string(), line!(),
172 "Expression panicked unexpectantly".to_string()
173 )
174 } else { Expectation::satisfied() }
175 }};
176 ( $actual: expr) => {{
177 use galvanic_assert::Expectation;
178 if !$actual {
179 let assertion = format!("'{}' is true", stringify!($actual));
180 Expectation::failed(assertion, file!().to_string(), line!(),
181 format!("'{}' is not true", stringify!($actual))
182 )
183 } else { Expectation::satisfied() }
184 }};
185 ( $actual: expr , otherwise $reason: expr ) => {{
186 use galvanic_assert::Expectation;
187 if !$actual {
188 let assertion = format!("'{}' is true", stringify!($actual));
189 Expectation::failed(assertion, file!().to_string(), line!(),
190 format!("'{}' is not true,\n\tBecause: {}",
191 stringify!($actual), $reason)
192 )
193 } else { Expectation::satisfied() }
194 }};
195 ( $actual: expr, $matcher: expr ) => {{
196 #[allow(unused_imports)]
197 use galvanic_assert::{Expectation, MatchResult, Matcher};
198 let value = $actual;
199 let m = $matcher;
200 match m.check(value) {
201 MatchResult::Matched { .. } => { Expectation::satisfied() },
202 MatchResult::Failed { name, reason } => {
203 let assertion = format!("'{}' matches '{}'", stringify!($actual), stringify!($matcher));
204 Expectation::failed(assertion, file!().to_string(), line!(),
205 format!("Failed assertion of matcher: {}\n{}", name, reason)
206 )
207 }
208 }
209 }};
210}
211
212/// States that the asserted values satisfies the required properties of the supplied `Matcher`
213/// but waits until the end of the block to inspect the results.
214///
215/// The postulated assertion is verfied immediately,
216/// but a potential panic is deferred until the end of the block wherein the expectation is stated.
217/// It is safe for multiple expectations to fail.
218/// The assertion code will prevent nested panics.
219///
220/// The macro comes in three different forms:
221///
222/// 1. Expect that some expression is true, supplied with an optional error message.
223///
224/// ```rust,ignore
225/// expect_that!(EXPRESSION);
226/// expect_that!(EXPRESSION, otherwise "some error message");
227/// ```
228/// 2. Expect that some expression satifies the properties of some `Matcher`.
229/// Expressions used with `Matcher`s **must return a reference** to a value.
230/// The `Matcher` is either predefined, a user defined type with a `Matcher` implementation, or a closure returning a `MatchResult
231///
232/// ```rust,ignore
233/// expect_that!(&1, eq(1));
234/// expect_that!(&1, |x| {
235/// let builder = MatchResultBuilder::for_("my_matcher");
236/// if x == 1 { builder.matched } else { builder.failed_because("some reason") }
237/// })
238/// ```
239/// 3. Expect that some expression is expected to panic/not panic.
240///
241/// ```rust,ignore
242/// expect_that!(panic!("panic"), panics);
243/// expect_that!(1+1, does not panic);
244/// ```
245///
246/// An expectation is verified at the end of the block it is stated in:
247///
248/// ```rust,ignore
249/// {
250/// expect_that!(&1+1, equal_to(0));
251/// expect_that!(&1+1, less_than(4)); // is executed
252/// }
253/// expect_that!(1+1, panics); // is never executed as e1 panics
254/// ```
255#[macro_export]
256macro_rules! expect_that {
257 ( $actual: expr, panics ) => { #[allow(unused_variables)] let expectation = get_expectation_for!($actual, panics); };
258 ( $actual: expr, does not panic ) => { #[allow(unused_variables)] let expectation = get_expectation_for!($actual, does not panic); };
259 ( $actual: expr) => { #[allow(unused_variables)] let expectation = get_expectation_for!($actual); };
260 ( $actual: expr , otherwise $reason: expr ) => { #[allow(unused_variables)] let expectation = get_expectation_for!($actual, otherwise $reason); };
261 ( $actual: expr, $matcher: expr ) => { #[allow(unused_variables)] let expectation = get_expectation_for!($actual, $matcher); };
262}
263
264/// The trait which has to be implemented by all matchers.
265pub trait Matcher<'a, T:'a> {
266 /// Checks the passed value if it satisfies the `Matcher`.
267 ///
268 /// Values are always taken as immutable reference as the actual value shouldn't be changed by the matcher.
269 fn check(&self, actual: &'a T) -> MatchResult;
270}
271
272/// A closure can be used as a `Matcher`.
273///
274/// The closure must be repeatably callable in case that the matcher is combined with another matcher.
275impl<'a, T:'a, F> Matcher<'a,T> for F
276where F: Fn(&'a T) -> MatchResult + ?Sized {
277 fn check(&self, actual: &'a T) -> MatchResult {
278 self(actual)
279 }
280}
281
282/// The return type of any `Machter`
283pub enum MatchResult {
284 /// Indicates that the `Matcher` matched the value under inspection.
285 Matched {
286 /// The `name` of the `Matcher`
287 name: String
288 },
289 /// Indicates that the `Matcher` failed to match the value under inspection.
290 Failed {
291 /// The `name` of the `Matcher`
292 name: String,
293 /// The `reason` why the `Matcher` failed
294 reason: String
295 }
296}
297
298impl std::convert::From<MatchResult> for bool {
299 fn from(result: MatchResult) -> bool {
300 match result {
301 MatchResult::Matched {..} => true,
302 MatchResult::Failed {..} => false
303 }
304 }
305}
306
307impl<'a> std::convert::From<&'a MatchResult> for bool {
308 fn from(result: &'a MatchResult) -> bool {
309 match result {
310 &MatchResult::Matched {..} => true,
311 &MatchResult::Failed {..} => false
312 }
313 }
314}
315
316/// A builder for creating `MatchResult`s.
317///
318/// Create a new builder with `new()` or `for_()`
319/// and finalize it either with `matched()`, `failed_because()`, or `failed_comparison()`.
320pub struct MatchResultBuilder {
321 matcher_name: String
322}
323
324impl MatchResultBuilder {
325 /// Creates a `MatchResultBuilder` for an anonymous `Matcher`.
326 pub fn new() -> MatchResultBuilder {
327 MatchResultBuilder {
328 matcher_name: "_unknown_".to_owned()
329 }
330 }
331
332 /// Creates `MatchResultBuilder` for a `Matcher` with the given `name`
333 pub fn for_(name: &str) -> MatchResultBuilder {
334 MatchResultBuilder {
335 matcher_name: name.to_owned()
336 }
337 }
338
339 /// Finalzes the builder indicating that the `Matcher` matched the inspected value.
340 pub fn matched(self) -> MatchResult {
341 MatchResult::Matched { name: self.matcher_name }
342 }
343
344 /// Finalzes the builder indicating that the `Matcher` failed to the inspected value.
345 ///
346 /// The `reason` should give a short indication why the matcher failed.
347 pub fn failed_because(self, reason: &str) -> MatchResult {
348 MatchResult::Failed {
349 name: self.matcher_name,
350 reason: format!(" Because: {}", reason)
351 }
352 }
353
354 /// Finalzes the builder indicating that the `Matcher` failed to the inspected value.
355 ///
356 /// The `actual` and `expected` value are used the generate a useful error message.
357 pub fn failed_comparison<T: Debug>(self, actual: &T, expected: &T) -> MatchResult {
358 MatchResult::Failed {
359 name: self.matcher_name,
360 reason: format!(" Expected: {:?}\n Got: {:?}", expected, actual)
361 }
362 }
363}
364
365/// The result of a deferred assertion.
366///
367///
368pub enum Expectation {
369 Failed {
370 /// A representation of the failed assertion.
371 assertion: String,
372 /// The file where the expection has been created.
373 file: String,
374 /// The source code line where the expectation has been created.
375 line: u32,
376 /// The reason why the expectation has not been met.
377 error_msg: String
378 },
379 Satisfied
380}
381
382impl Expectation {
383 /// Creates a failed `Expectation`
384 pub fn failed(assertion:String, file: String, line: u32, error_msg: String) -> Expectation {
385 Expectation::Failed {
386 assertion: assertion,
387 file: file,
388 line: line,
389 error_msg: error_msg
390 }
391 }
392
393 /// Create a satisfied `Expectation`
394 pub fn satisfied() -> Expectation {
395 Expectation::Satisfied
396 }
397
398 /// Verifies if the asseration given by the `Expectation` held.
399 ///
400 /// Panics if the verification fails.
401 pub fn verify(self) { /* drop self */ }
402}
403
404/// If the `Expectation` is dropped it is automatically verified.
405impl Drop for Expectation {
406 fn drop(&mut self) {
407 if let &mut Expectation::Failed { .. } = self {
408 eprintln!("{}", self);
409 if !std::thread::panicking() {
410 panic!("Some expectations failed.")
411 }
412 }
413 }
414}
415
416impl Display for Expectation {
417 fn fmt(&self, f: &mut Formatter) -> FormatResult {
418 match self {
419 &Expectation::Failed { ref assertion, ref file, ref line, ref error_msg } => {
420 write!(f, "Expectation '{}' failed, originating from {}:{}\n\t{}",
421 assertion, file, line, error_msg
422 )
423 },
424 _ => write!(f, "The expectation has been satisfied")
425 }
426 }
427}
428
429pub mod matchers;
430
431#[cfg(test)]
432mod test {
433 use super::*;
434
435 #[test]
436 fn should_bool() {
437 let matched = MatchResultBuilder::new().matched();
438 let failed = MatchResultBuilder::new().failed_because("");
439
440 let flag: bool = matched.into();
441 assert!(flag);
442 let flag: bool = failed.into();
443 assert!(!flag);
444 }
445}