From 143556db65c7651e94065ad47e44ff291ba1dfe6 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Mon, 5 Aug 2019 15:06:49 -0600 Subject: [PATCH] The times method now accepts range arguments times_any and times_range are now deprecated, because their functionality has been merged with times. This should be easier for users to remember. This syntax is inspired by Mockiato. --- CHANGELOG.md | 11 +++ mockall/src/lib.rs | 98 +++++++++++++++---- mockall/tests/mock_reference_arguments.rs | 21 ++-- mockall/tests/mock_return_reference.rs | 2 +- mockall/tests/mock_struct.rs | 93 ++++++++++++++---- .../tests/mock_struct_with_static_method.rs | 2 +- mockall_derive/src/expectation.rs | 70 +++++++------ 7 files changed, 217 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a177fa40..33a0c698 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] - ReleaseDate +### Added +### Changed + +- The `times` method now accepts ranges as arguments. `types_any` and + `times_range` are deprecated. + ([#14](https://github.com/asomers/mockall/pull/14)) + +### Fixed +### Removed + ## [0.2.1] - 3 August 2019 ### Fixed diff --git a/mockall/src/lib.rs b/mockall/src/lib.rs index 253e9b72..c4c4872c 100644 --- a/mockall/src/lib.rs +++ b/mockall/src/lib.rs @@ -292,11 +292,8 @@ //! ``` //! //! See also -//! [`never`](https://docs.rs/mockall_examples/latest/mockall_examples/__mock_Foo_Foo/foo/struct.Expectation.html#method.never), -//! [`times`](https://docs.rs/mockall_examples/latest/mockall_examples/__mock_Foo_Foo/foo/struct.Expectation.html#method.times), -//! [`times_any`](https://docs.rs/mockall_examples/latest/mockall_examples/__mock_Foo_Foo/foo/struct.Expectation.html#method.times_any), -//! and -//! [`times_range`](https://docs.rs/mockall_examples/latest/mockall_examples/__mock_Foo_Foo/foo/struct.Expectation.html#method.times_range). +//! [`never`](https://docs.rs/mockall_examples/latest/mockall_examples/__mock_Foo_Foo/foo/struct.Expectation.html#method.never) and +//! [`times`](https://docs.rs/mockall_examples/latest/mockall_examples/__mock_Foo_Foo/foo/struct.Expectation.html#method.times). //! //! ## Sequences //! @@ -1020,7 +1017,8 @@ use downcast::*; use std::{ any, marker::PhantomData, - ops::Range, + ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, + RangeToInclusive}, sync::{ Arc, atomic::{AtomicUsize, Ordering} @@ -1073,63 +1071,122 @@ pub struct DefaultReturner(PhantomData); } } +// Though it's not entirely correct, we treat usize::max_value() as +// approximately infinity. +#[derive(Debug)] +#[doc(hidden)] +pub struct TimesRange(Range); + +impl Default for TimesRange { + fn default() -> TimesRange { + TimesRange(0..usize::max_value()) + } +} + +impl From for TimesRange { + fn from(n: usize) -> TimesRange { + TimesRange(n..(n+1)) + } +} + +impl From> for TimesRange { + fn from(r: Range) -> TimesRange { + assert!(r.end > r.start, "Backwards range"); + TimesRange(r) + } +} + +impl From> for TimesRange { + fn from(r: RangeFrom) -> TimesRange { + TimesRange(r.start..usize::max_value()) + } +} + +impl From for TimesRange { + fn from(_: RangeFull) -> TimesRange { + TimesRange(0..usize::max_value()) + } +} + +impl From> for TimesRange { + fn from(r: RangeInclusive) -> TimesRange { + assert!(r.end() >= r.start(), "Backwards range"); + TimesRange(*r.start()..*r.end() + 1) + } +} + +impl From> for TimesRange { + fn from(r: RangeTo) -> TimesRange { + TimesRange(0..r.end) + } +} + +impl From> for TimesRange { + fn from(r: RangeToInclusive) -> TimesRange { + TimesRange(0..r.end + 1) + } +} #[derive(Debug)] #[doc(hidden)] pub struct Times{ /// How many times has the expectation already been called? count: AtomicUsize, - range: Range + range: TimesRange } impl Times { pub fn call(&self) { let count = self.count.fetch_add(1, Ordering::Relaxed) + 1; - if count >= self.range.end { - if self.range.end == 1 { + if count >= self.range.0.end { + if self.range.0.end == 1 { panic!("Expectation should not have been called"); } else { - let lim = self.range.end - 1; + let lim = self.range.0.end - 1; panic!("Expectation called more than {} times", lim); } } } pub fn any(&mut self) { - self.range = 0..usize::max_value(); + self.range.0 = 0..usize::max_value(); } /// Has this expectation already been called the maximum allowed number of /// times? pub fn is_done(&self) -> bool { - self.count.load(Ordering::Relaxed) >= self.range.end - 1 + self.count.load(Ordering::Relaxed) >= self.range.0.end - 1 } /// Is it required that this expectation be called an exact number of times, /// or may it be satisfied by a range of call counts? pub fn is_exact(&self) -> bool { - (self.range.end - self.range.start) == 1 + (self.range.0.end - self.range.0.start) == 1 } /// Has this expectation already been called the minimum required number of /// times? pub fn is_satisfied(&self) -> bool { - self.count.load(Ordering::Relaxed) >= self.range.start + self.count.load(Ordering::Relaxed) >= self.range.0.start } // https://github.com/rust-lang/rust-clippy/issues/3307 #[allow(clippy::range_plus_one)] pub fn n(&mut self, n: usize) { - self.range = n..(n+1); + self.range.0 = n..(n+1); } pub fn never(&mut self) { - self.range = 0..1; + self.range.0 = 0..1; } pub fn range(&mut self, range: Range) { assert!(range.end > range.start, "Backwards range"); - self.range = range; + self.range.0 = range; + } + + pub fn times>(&mut self, t: T) { + self.range = t.into(); } } @@ -1137,7 +1194,7 @@ impl Default for Times { fn default() -> Self { // By default, allow any number of calls let count = AtomicUsize::default(); - let range = 0..usize::max_value(); + let range = TimesRange::default(); Times{count, range} } } @@ -1145,8 +1202,9 @@ impl Default for Times { impl Drop for Times { fn drop(&mut self) { let count = self.count.load(Ordering::Relaxed); - if !thread::panicking() && (count < self.range.start) { - panic!("Expectation called fewer than {} times", self.range.start); + if !thread::panicking() && (count < self.range.0.start) { + panic!("Expectation called fewer than {} times", + self.range.0.start); } } } diff --git a/mockall/tests/mock_reference_arguments.rs b/mockall/tests/mock_reference_arguments.rs index cc878c16..ce8c2b27 100644 --- a/mockall/tests/mock_reference_arguments.rs +++ b/mockall/tests/mock_reference_arguments.rs @@ -83,39 +83,34 @@ mod times { // Verify that we panic quickly and don't reach code below this point. panic!("Shouldn't get here!"); } -} - -mod times_range { - use super::*; - const X: u32 = 42; #[test] - fn ok() { + fn range_ok() { let mut mock = MockFoo::new(); mock.expect_foo() .returning(|_| 0) - .times_range(2..4); + .times(2..4); mock.foo(&X); mock.foo(&X); } #[test] #[should_panic(expected = "Expectation called fewer than 2 times")] - fn too_few() { + fn range_too_few() { let mut mock = MockFoo::new(); mock.expect_foo() .returning(|_| 0) - .times_range(2..4); + .times(2..4); mock.foo(&X); } #[test] #[should_panic(expected = "Expectation called more than 3 times")] - fn too_many() { + fn range_too_many() { let mut mock = MockFoo::new(); mock.expect_foo() .returning(|_| 0) - .times_range(2..4); + .times(2..4); mock.foo(&X); mock.foo(&X); mock.foo(&X); @@ -126,13 +121,13 @@ mod times_range { } #[test] -fn times_any() { +fn times_full() { const X: u32 = 42; let mut mock = MockFoo::new(); mock.expect_foo() .returning(|_| 0) .times(1) - .times_any(); + .times(..); mock.foo(&X); mock.foo(&X); } diff --git a/mockall/tests/mock_return_reference.rs b/mockall/tests/mock_return_reference.rs index 5f47b458..fbb4a1cc 100644 --- a/mockall/tests/mock_return_reference.rs +++ b/mockall/tests/mock_return_reference.rs @@ -48,7 +48,7 @@ mod sequence { let mut seq = Sequence::new(); let mut mock = MockFoo::new(); mock.expect_foo() - .times_range(1..3) + .times(1..3) .in_sequence(&mut seq); mock.foo(); } diff --git a/mockall/tests/mock_struct.rs b/mockall/tests/mock_struct.rs index ad8a4baf..284627ac 100644 --- a/mockall/tests/mock_struct.rs +++ b/mockall/tests/mock_struct.rs @@ -30,7 +30,7 @@ mod checkpoint { let mut mock = MockFoo::new(); mock.expect_foo() .returning(|_| 5) - .times_range(1..3); + .times(1..3); mock.foo(0); mock.checkpoint(); @@ -56,7 +56,7 @@ mod checkpoint { let mut mock = MockFoo::new(); mock.expect_foo() .returning(|_| 5) - .times_range(1..3); + .times(1..3); mock.foo(0); mock.checkpoint(); } @@ -67,7 +67,7 @@ mod checkpoint { let mut mock = MockFoo::new(); mock.expect_foo() .returning(|_| 42) - .times_range(1..3); + .times(1..3); mock.foo(0); mock.checkpoint(); mock.foo(0); @@ -210,7 +210,7 @@ mod sequence { let mut seq = Sequence::new(); let mut mock = MockFoo::new(); mock.expect_baz() - .times_range(1..3) + .times(1..3) .in_sequence(&mut seq); mock.baz(); } @@ -315,23 +315,19 @@ mod times { // Verify that we panic quickly and don't reach code below this point. panic!("Shouldn't get here!"); } -} - -mod times_range { - use super::*; #[test] - fn ok() { + fn range_ok() { let mut mock = MockFoo::new(); mock.expect_baz() .returning(|| ()) - .times_range(2..4); + .times(2..4); mock.baz(); mock.baz(); mock.expect_bar() .returning(|_| ()) - .times_range(2..4); + .times(2..4); mock.bar(0); mock.bar(0); mock.bar(0); @@ -339,21 +335,21 @@ mod times_range { #[test] #[should_panic(expected = "Expectation called fewer than 2 times")] - fn too_few() { + fn range_too_few() { let mut mock = MockFoo::new(); mock.expect_baz() .returning(|| ()) - .times_range(2..4); + .times(2..4); mock.baz(); } #[test] #[should_panic(expected = "Expectation called more than 3 times")] - fn too_many() { + fn range_too_many() { let mut mock = MockFoo::new(); mock.expect_baz() .returning(|| ()) - .times_range(2..4); + .times(2..4); mock.baz(); mock.baz(); mock.baz(); @@ -361,15 +357,78 @@ mod times_range { // Verify that we panic quickly and don't reach code below this point. panic!("Shouldn't get here!"); } + + #[test] + fn rangeto_ok() { + let mut mock = MockFoo::new(); + mock.expect_bar() + .returning(|_| ()) + .times(..4); + mock.bar(0); + mock.bar(0); + mock.bar(0); + } + + #[test] + #[should_panic(expected = "Expectation called more than 3 times")] + fn rangeto_too_many() { + let mut mock = MockFoo::new(); + mock.expect_baz() + .returning(|| ()) + .times(..4); + mock.baz(); + mock.baz(); + mock.baz(); + mock.baz(); + } + + #[test] + fn rangeinclusive_ok() { + let mut mock = MockFoo::new(); + mock.expect_bar() + .returning(|_| ()) + .times(2..=4); + mock.bar(0); + mock.bar(0); + mock.bar(0); + mock.bar(0); + } + + #[test] + fn rangefrom_ok() { + let mut mock = MockFoo::new(); + mock.expect_baz() + .returning(|| ()) + .times(2..); + mock.baz(); + mock.baz(); + + mock.expect_bar() + .returning(|_| ()) + .times(2..); + mock.bar(0); + mock.bar(0); + mock.bar(0); + } + + #[test] + #[should_panic(expected = "Expectation called fewer than 2 times")] + fn rangefrom_too_few() { + let mut mock = MockFoo::new(); + mock.expect_baz() + .returning(|| ()) + .times(2..); + mock.baz(); + } } #[test] -fn times_any() { +fn times_full() { let mut mock = MockFoo::new(); mock.expect_baz() .returning(|| ()) .times(1) - .times_any(); + .times(..); mock.baz(); mock.baz(); } diff --git a/mockall/tests/mock_struct_with_static_method.rs b/mockall/tests/mock_struct_with_static_method.rs index f7276c7a..af3ae775 100644 --- a/mockall/tests/mock_struct_with_static_method.rs +++ b/mockall/tests/mock_struct_with_static_method.rs @@ -15,7 +15,7 @@ fn checkpoint() { let mut mock = MockFoo::new(); MockFoo::expect_bar() .returning(|_| 32) - .times_range(1..3); + .times(1..3); mock.checkpoint(); panic!("Shouldn't get here!"); } diff --git a/mockall_derive/src/expectation.rs b/mockall_derive/src/expectation.rs index 8304168d..8d275cd7 100644 --- a/mockall_derive/src/expectation.rs +++ b/mockall_derive/src/expectation.rs @@ -153,20 +153,11 @@ fn common_methods( } } - /// Expect this expectation to be called exactly `n` times. - fn times(&mut self, __mockall_n: usize) { - self.times.n(__mockall_n); - } - - /// Allow this expectation to be called any number of times - fn times_any(&mut self) { - self.times.any(); - } - - /// Allow this expectation to be called any number of times within a - /// given range - fn times_range(&mut self, __mockall_range: Range) { - self.times.range(__mockall_range); + /// Expect this expectation to be called any number of times + /// contained with the given range. + fn times>(&mut self, __mockall_r: R) + { + self.times.times(__mockall_r) } fn with<#with_generics>(&mut self, #with_args) @@ -284,9 +275,21 @@ fn expectation_methods(v: &Visibility, self.times(1) } - /// Expect this expectation to be called exactly `n` times. - #v fn times(&mut self, __mockall_n: usize) -> &mut Self { - self.common.times(__mockall_n); + /// Restrict the number of times that that this method may be called. + /// + /// The argument may be: + /// * A fixed number: `.times(4)` + /// * Various types of range: + /// - `.times(5..10)` + /// - `.times(..10)` + /// - `.times(5..)` + /// - `.times(5..=10)` + /// - `.times(..=10)` + /// * The wildcard: `.times(..)` + #v fn times(&mut self, __mockall_r: R) -> &mut Self + where R: Into<::mockall::TimesRange> + { + self.common.times(__mockall_r); self } @@ -294,17 +297,19 @@ fn expectation_methods(v: &Visibility, /// /// This behavior is the default, but the method is provided in case the /// default behavior changes. + #[deprecated(since = "0.3.0", note = "Use times instead")] #v fn times_any(&mut self) -> &mut Self { - self.common.times_any(); + self.common.times(..); self } /// Allow this expectation to be called any number of times within a /// given range + #[deprecated(since = "0.3.0", note = "Use times instead")] #v fn times_range(&mut self, __mockall_range: Range) -> &mut Self { - self.common.times_range(__mockall_range); + self.common.times(__mockall_range); self } @@ -1143,23 +1148,27 @@ pub(crate) fn expectation(attrs: &TokenStream, vis: &Visibility, /// Just like /// [`Expectation::times`](struct.Expectation.html#method.times) - #vis fn times(&mut self, __mockall_n: usize) - -> &mut Expectation #egenerics_tg { - self.guard.0[self.i].times(__mockall_n) + #vis fn times(&mut self, __mockall_r: R) + -> &mut Expectation #egenerics_tg + where R: Into<::mockall::TimesRange> + { + self.guard.0[self.i].times(__mockall_r) } /// Just like /// [`Expectation::times_any`](struct.Expectation.html#method.times_any) + #[deprecated(since = "0.3.0", note = "Use times instead")] #vis fn times_any(&mut self) -> &mut Expectation #egenerics_tg { - self.guard.0[self.i].times_any() + self.guard.0[self.i].times(..) } /// Just like /// [`Expectation::times_range`](struct.Expectation.html#method.times_range) + #[deprecated(since = "0.3.0", note = "Use times instead")] #vis fn times_range(&mut self, __mockall_range: Range) -> &mut Expectation #egenerics_tg { - self.guard.0[self.i].times_range(__mockall_range) + self.guard.0[self.i].times(__mockall_range) } /// Just like @@ -1288,18 +1297,22 @@ pub(crate) fn expectation(attrs: &TokenStream, vis: &Visibility, /// Just like /// [`Expectation::times`](struct.Expectation.html#method.times) - #vis fn times(&mut self, __mockall_n: usize) -> &mut Expectation #egenerics_tg { + #vis fn times(&mut self, __mockall_r: R) + -> &mut Expectation #egenerics_tg + where R: Into<::mockall::TimesRange> + { self.guard.store.get_mut( &::mockall::Key::new::<(#argty_tp)>() ).unwrap() .downcast_mut::() .unwrap() .0[self.i] - .times(__mockall_n) + .times(__mockall_r) } /// Just like /// [`Expectation::times_any`](struct.Expectation.html#method.times_any) + #[deprecated(since = "0.3.0", note = "Use times instead")] #vis fn times_any(&mut self) -> &mut Expectation #egenerics_tg { self.guard.store.get_mut( &::mockall::Key::new::<(#argty_tp)>() @@ -1307,11 +1320,12 @@ pub(crate) fn expectation(attrs: &TokenStream, vis: &Visibility, .downcast_mut::() .unwrap() .0[self.i] - .times_any() + .times(..) } /// Just like /// [`Expectation::times_range`](struct.Expectation.html#method.times_range) + #[deprecated(since = "0.3.0", note = "Use times instead")] #vis fn times_range(&mut self, __mockall_range: Range) -> &mut Expectation #egenerics_tg { @@ -1321,7 +1335,7 @@ pub(crate) fn expectation(attrs: &TokenStream, vis: &Visibility, .downcast_mut::() .unwrap() .0[self.i] - .times_range(__mockall_range) + .times(__mockall_range) } /// Just like