Skip to main content

galvanic_mock_lib/
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//! The support library for **[galvanic-mock](https://www.github.com/mindsbackyard/galvanic-mock)**.
17//!
18//! The crate provides common traits for all mocks generated by **galvanic-mock** as well as data structures for handling the state of mock objects.
19
20#[cfg(feature = "galvanic_assert_integration")] extern crate galvanic_assert;
21
22use std::collections::HashMap;
23use std::cell::RefCell;
24
25/// A trait for controlling the behaviour of a mock.
26///
27/// All mocks generated by `galvanic-mock` implement this trait.
28/// The generated mocks use a `MockState` object internally to handle the state of the mock.
29/// The mock's implementation of the `MockControl` trait acts as a proxy to the `MockState` object.
30pub trait MockControl {
31    /// Passing `true` enables verification of expected behaviours when the mock object is dropped.
32    ///
33    /// See `verify()`.
34    fn should_verify_on_drop(&mut self, flag: bool);
35
36    /// For *internal* use only.
37    ///
38    /// Enables a behaviour defined in a `given!`-block for a trait's method.
39    ///
40    /// # Arguments
41    /// * `requested_trait` - the trait's name
42    /// * `method` - the trait's method's name
43    /// * `behaviour` - the behaviour to be activated
44    fn add_given_behaviour(&self,
45                           requested_trait: &'static str,
46                           method: &'static str,
47                           behaviour: GivenBehaviour);
48
49    /// Deactivates all behaviours activated by a `given!`-block before.
50    fn reset_given_behaviours(&mut self);
51
52    /// For *internal* use only.
53    ///
54    /// Enables a behaviour defined in a `expect_interactions!`-block for a trait's method.
55    ///
56    /// # Arguments
57    /// * `requested_trait` - the trait's name
58    /// * `method` - the trait's method's name
59    /// * `behaviour` - the behaviour to be activated
60    fn add_expect_behaviour(&self,
61                            requested_trait: &'static str,
62                            method: &'static str,
63                            behaviour: ExpectBehaviour);
64
65    /// Deactivates all behaviours activated by a `expect_interactions!`-block before.
66    fn reset_expected_behaviours(&mut self);
67
68    /// Returns `true` iff all expected interactions with the mock have occurred.
69    fn are_expected_behaviours_satisfied(&self) -> bool;
70
71    /// Panics if some expected interaction with the mock has not occurred.
72    ///
73    /// An expected interaction is defined by a behaviour added to the mock in an `expect_interactions!`-block.
74    /// A behaviour is said to match if the method asocciated with the behaviour is called with values satisfying the behaviour's argument pattern.
75    /// A behaviour is satisfied if its expected repetitions are fulfilled.
76    ///
77    /// Verification should be skipped if the current thread is already panicking.
78    /// This method should also be executed once the implementor is drpped if verication on drop is enabled.
79    fn verify(&self);
80}
81
82
83/// Stores the state of a mock.
84///
85/// The state of a mock object is compromised by its enabled *given* and *expected* behaviours.
86/// As well as its verification policies.
87pub struct MockState {
88    /// The enabled *given* behaviours addressed by a tuple of the names of the mocked *trait* and *method*.
89    pub given_behaviours: RefCell<HashMap<(&'static str, &'static str), Vec<GivenBehaviour>>>,
90    /// The enabled *expected* behaviours addressed by a tuple of the names of the mocked *trait* and *method*.
91    pub expect_behaviours: RefCell<HashMap<(&'static str, &'static str), Vec<ExpectBehaviour>>>,
92    /// Whether the *expected behaviours should be verfied on drop.
93    verify_on_drop: bool,
94}
95
96impl MockState {
97    pub fn new() -> Self {
98        Self {
99            given_behaviours: RefCell::new(HashMap::new()),
100            expect_behaviours: RefCell::new(HashMap::new()),
101            verify_on_drop: true,
102        }
103    }
104}
105
106impl MockControl for MockState {
107    fn should_verify_on_drop(&mut self, flag: bool) {
108        self.verify_on_drop = flag;
109    }
110
111    fn add_given_behaviour(&self,
112                           requested_trait: &'static str,
113                           method: &'static str,
114                           behaviour: GivenBehaviour) {
115        self.given_behaviours
116            .borrow_mut()
117            .entry((requested_trait, method))
118            .or_insert_with(|| Vec::new())
119            .push(behaviour);
120    }
121
122    fn reset_given_behaviours(&mut self) {
123        self.given_behaviours.borrow_mut().clear();
124    }
125
126    fn add_expect_behaviour(&self,
127                            requested_trait: &'static str,
128                            method: &'static str,
129                            behaviour: ExpectBehaviour) {
130        self.expect_behaviours
131            .borrow_mut()
132            .entry((requested_trait, method))
133            .or_insert_with(|| Vec::new())
134            .push(behaviour);
135    }
136
137    fn reset_expected_behaviours(&mut self) {
138        self.expect_behaviours.borrow_mut().clear();
139    }
140
141    fn are_expected_behaviours_satisfied(&self) -> bool {
142        let mut unsatisfied_messages: Vec<String> = Vec::new();
143        for behaviour in self.expect_behaviours.borrow().values().flat_map(|vs| vs) {
144            if !behaviour.is_saturated() {
145                unsatisfied_messages
146                    .push(format!("Behaviour unsatisfied with {} matching invocations: {}",
147                                  behaviour.num_matches.get(),
148                                  behaviour.describe()));
149            }
150        }
151        if !unsatisfied_messages.is_empty() {
152            for message in unsatisfied_messages {
153                eprintln!("{}", message);
154            }
155            false
156        } else {
157            true
158        }
159    }
160
161    fn verify(&self) {
162        if !std::thread::panicking() && !self.are_expected_behaviours_satisfied() {
163            panic!("There are unsatisfied expected behaviours for mocked traits.");
164        }
165    }
166}
167
168impl std::ops::Drop for MockState {
169    /// Verfies the *expected interactions* on the mock if the policy is enabled.
170    ///
171    /// # Panics
172    /// iff the verification fails.
173    fn drop(&mut self) {
174        if self.verify_on_drop {
175            self.verify();
176        }
177    }
178}
179
180
181/// Defines a matcher for the arguments of a mocked method.
182///
183/// A matcher may check a single argument of a method or all arguments at once.
184/// If all arguments are to be checked then they should be passed in a curried form, e.g., as a tuple.
185pub trait ArgMatcher<'a, T: 'a> {
186    // Returns `true` iff the `actual` arguments satisfy the matcher.
187    fn match_args(&self, actual: &'a T) -> bool;
188}
189
190/// Any function accepting an argument and returning a `bool` can be used as `ArgMatcher`.
191impl<'a, T: 'a, F> ArgMatcher<'a, T> for F
192    where F: Fn(&'a T) -> bool
193{
194    fn match_args(&self, actual: &'a T) -> bool {
195        self(actual)
196    }
197}
198
199/// All matchers of the **galvanic-assert** crate can be used as `ArgMatcher`.
200///
201/// The crate's matchers can either be used to inspect a single argument or all of them (in curried form).
202#[cfg(feature = "galvanic_assert_integration")]
203impl<'a, T: 'a> ArgMatcher<'a, T> for Box<::galvanic_assert::Matcher<'a, T> + 'a> {
204    fn match_args(&self, actual: &'a T) -> bool {
205        self.check(actual).into()
206    }
207}
208
209/// Stores the state of a *given* behaviour.
210pub struct GivenBehaviour {
211    /// The unique id of the behaviour within the mocked method to which it belongs.
212    pub stmt_id: usize,
213    /// How often the behaviour has been matched.
214    num_matches: std::cell::Cell<usize>,
215    /// How often the behaviour should be matched before it is exhausted, `None` if never.
216    expected_matches: Option<usize>,
217    /// The bound variables available to the behaviour's `ArgMatcher`.
218    pub bound: std::rc::Rc<std::any::Any>,
219    /// A string representation of the behaviour's definition.
220    stmt_repr: String,
221}
222
223impl GivenBehaviour {
224    /// Creates a new behaviour which is never exhausted.
225    pub fn with(stmt_id: usize, bound: std::rc::Rc<std::any::Any>, stmt_repr: &str) -> Self {
226        Self {
227            stmt_id: stmt_id,
228            num_matches: std::cell::Cell::new(0),
229            expected_matches: None,
230            bound: bound,
231            stmt_repr: stmt_repr.to_string(),
232        }
233    }
234
235    /// Creates a new behaviour which is never exhausted after being matched `times`.
236    pub fn with_times(times: usize,
237                      stmt_id: usize,
238                      bound: std::rc::Rc<std::any::Any>,
239                      stmt_repr: &str)
240                      -> Self {
241        Self {
242            stmt_id: stmt_id,
243            num_matches: std::cell::Cell::new(0),
244            expected_matches: Some(times),
245            bound: bound,
246            stmt_repr: stmt_repr.to_string(),
247        }
248    }
249
250    /// Notifies the behaviour that it has been matched.
251    pub fn matched(&self) {
252        self.num_matches.set(self.num_matches.get() + 1);
253    }
254
255    /// Returns `true` iff the behaviour is exhausted.
256    pub fn is_saturated(&self) -> bool {
257        match self.expected_matches {
258            Some(limit) => self.num_matches.get() >= limit,
259            None => false,
260        }
261    }
262
263    /// Returns a description of the behaviour.
264    pub fn describe(&self) -> &str {
265        &self.stmt_repr
266    }
267}
268
269
270/// Stores the state of a *expected* behaviour.
271pub struct ExpectBehaviour {
272    /// The unique id of the behaviour within the mocked method to which it belongs.
273    pub stmt_id: usize,
274    /// How often the behaviour has been matched.
275    num_matches: std::cell::Cell<usize>,
276    /// The expected minimum number of matches for the behaviour to be satisfied
277    expected_min_matches: Option<usize>,
278    /// The expected maximum number of matches for the behaviour to be satisfied
279    expected_max_matches: Option<usize>,
280    #[allow(dead_code)] in_order: Option<bool>,
281    /// The bound variables available to the behaviour's `ArgMatcher`.
282    pub bound: std::rc::Rc<std::any::Any>,
283    /// A string representation of the behaviour's definition.
284    stmt_repr: String,
285}
286
287
288impl ExpectBehaviour {
289    /// Creates a new behaviour which is satisfied if matched `times`.
290    pub fn with_times(times: usize,
291                      stmt_id: usize,
292                      bound: std::rc::Rc<std::any::Any>,
293                      stmt_repr: &str)
294                      -> Self {
295        Self {
296            stmt_id: stmt_id,
297            num_matches: std::cell::Cell::new(0),
298            expected_min_matches: Some(times),
299            expected_max_matches: Some(times),
300            in_order: None,
301            bound: bound,
302            stmt_repr: stmt_repr.to_string(),
303        }
304    }
305
306    /// Creates a new behaviour which is satisfied if matched `at_least_times`.
307    pub fn with_at_least(at_least_times: usize,
308                         stmt_id: usize,
309                         bound: std::rc::Rc<std::any::Any>,
310                         stmt_repr: &str)
311                         -> Self {
312        Self {
313            stmt_id: stmt_id,
314            num_matches: std::cell::Cell::new(0),
315            expected_min_matches: Some(at_least_times),
316            expected_max_matches: None,
317            in_order: None,
318            bound: bound,
319            stmt_repr: stmt_repr.to_string(),
320        }
321    }
322
323    /// Creates a new behaviour which is satisfied if matched `at_most_times`.
324    pub fn with_at_most(at_most_times: usize,
325                        stmt_id: usize,
326                        bound: std::rc::Rc<std::any::Any>,
327                        stmt_repr: &str)
328                        -> Self {
329        Self {
330            stmt_id: stmt_id,
331            num_matches: std::cell::Cell::new(0),
332            expected_min_matches: None,
333            expected_max_matches: Some(at_most_times),
334            in_order: None,
335            bound: bound,
336            stmt_repr: stmt_repr.to_string(),
337        }
338    }
339
340    /// Creates a new behaviour which is satisfied if matched between `[at_least_times, at_most_times]` (inclusive endpoints).
341    pub fn with_between(at_least_times: usize,
342                        at_most_times: usize,
343                        stmt_id: usize,
344                        bound: std::rc::Rc<std::any::Any>,
345                        stmt_repr: &str)
346                        -> Self {
347        Self {
348            stmt_id: stmt_id,
349            num_matches: std::cell::Cell::new(0),
350            expected_min_matches: Some(at_least_times),
351            expected_max_matches: Some(at_most_times),
352            in_order: None,
353            bound: bound,
354            stmt_repr: stmt_repr.to_string(),
355        }
356    }
357
358    /// Notifies the behaviour that it has been matched.
359    pub fn matched(&self) {
360        self.num_matches.set(self.num_matches.get() + 1);
361    }
362
363    /// Returns `true` iff current number of matches would satify the behaviours expected repetitions.
364    pub fn is_saturated(&self) -> bool {
365        self.expected_min_matches.unwrap_or(0) <= self.num_matches.get() &&
366        self.num_matches.get() <= self.expected_max_matches.unwrap_or(std::usize::MAX)
367    }
368
369    /// Returns a description of the behaviour.
370    pub fn describe(&self) -> &str {
371        &self.stmt_repr
372    }
373}