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}