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}