Skip to content

Commit

Permalink
Merge pull request #247 from asomers/sequence_messages
Browse files Browse the repository at this point in the history
Better error messages for Sequence violations
  • Loading branch information
asomers authored Jan 29, 2021
2 parents 7602b06 + 22e84c6 commit fb50ec5
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 32 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased] - ReleaseDate
### Added

- When a test fails because of a method sequence violation, the error message
will now show the method's arguments. This requires the `nightly` feature,
and requires that the arguments implement `Debug`.
([#247](https://github.com/asomers/mockall/pull/247))

- When a test fails because a mock object receives an unexpected call, the
error message will now show the method's arguments. This requires the
`nightly` feature, and requires that the arguments implement `Debug`.
Expand Down
8 changes: 4 additions & 4 deletions mockall/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1534,8 +1534,8 @@ impl SeqHandle {
}

/// Verify that this handle was called in the correct order
pub fn verify(&self) {
self.inner.verify(self.seq);
pub fn verify(&self, desc: &str) {
self.inner.verify(self.seq, desc);
}
}

Expand All @@ -1552,9 +1552,9 @@ impl SeqInner {
}

/// Verify that the call identified by `seq` was called in the correct order
fn verify(&self, seq: usize) {
fn verify(&self, seq: usize, desc: &str) {
assert_eq!(seq, self.satisfaction_level.load(Ordering::Relaxed),
"Method sequence violation")
"{}: Method sequence violation", desc)
}
}

Expand Down
48 changes: 48 additions & 0 deletions mockall/tests/mock_return_mutable_reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,51 @@ fn returning() {
let r = mock.foo(0);
assert_eq!(5, *r);
}

mod sequence {
use super::*;

#[test]
#[cfg_attr(feature = "nightly",
should_panic(expected = "MockFoo::foo(4): Method sequence violation"))]
#[cfg_attr(not(feature = "nightly"),
should_panic(expected = "MockFoo::foo(?): Method sequence violation"))]
fn fail() {
let mut seq = Sequence::new();
let mut mock = MockFoo::new();
mock.expect_foo()
.with(predicate::eq(3))
.times(1)
.return_var(0)
.in_sequence(&mut seq);

mock.expect_foo()
.with(predicate::eq(4))
.times(1)
.return_var(0)
.in_sequence(&mut seq);

mock.foo(4);
}

#[test]
fn ok() {
let mut seq = Sequence::new();
let mut mock = MockFoo::new();
mock.expect_foo()
.with(predicate::eq(3))
.times(1)
.return_var(0)
.in_sequence(&mut seq);

mock.expect_foo()
.with(predicate::eq(4))
.times(1)
.return_var(0)
.in_sequence(&mut seq);

mock.foo(3);
mock.foo(4);
}

}
17 changes: 10 additions & 7 deletions mockall/tests/mock_return_reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use mockall::*;

mock! {
Foo {
fn foo(&self) -> &u32;
fn foo(&self, x: i32) -> &u32;
fn bar(&self) -> &u32;
}
}
Expand Down Expand Up @@ -38,7 +38,7 @@ fn return_const() {
let mut mock = MockFoo::new();
mock.expect_foo()
.return_const(5u32);
assert_eq!(5, *mock.foo());
assert_eq!(5, *mock.foo(4));
}

#[test]
Expand All @@ -48,7 +48,7 @@ fn return_const() {
fn return_default() {
let mut mock = MockFoo::new();
mock.expect_foo();
let r = mock.foo();
let r = mock.foo(4);
assert_eq!(u32::default(), *r);
}

Expand All @@ -63,11 +63,14 @@ mod sequence {
mock.expect_foo()
.times(1..3)
.in_sequence(&mut seq);
mock.foo();
mock.foo(4);
}

#[test]
#[should_panic(expected = "Method sequence violation")]
#[cfg_attr(feature = "nightly",
should_panic(expected = "MockFoo::foo(4): Method sequence violation"))]
#[cfg_attr(not(feature = "nightly"),
should_panic(expected = "MockFoo::foo(?): Method sequence violation"))]
fn fail() {
let mut seq = Sequence::new();
let mut mock = MockFoo::new();
Expand All @@ -81,7 +84,7 @@ mod sequence {
.return_const(0)
.in_sequence(&mut seq);

mock.foo();
mock.foo(4);
mock.bar();
}

Expand All @@ -99,7 +102,7 @@ mod sequence {
.return_const(0)
.in_sequence(&mut seq);

mock.foo();
mock.foo(4);
mock.bar();
}
}
2 changes: 1 addition & 1 deletion mockall/tests/mock_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ mod sequence {
}

#[test]
#[should_panic(expected = "Method sequence violation")]
#[should_panic(expected = "MockFoo::baz(): Method sequence violation")]
fn fail() {
let mut seq = Sequence::new();
let mut mock = MockFoo::new();
Expand Down
51 changes: 31 additions & 20 deletions mockall_derive/src/mock_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,17 +432,9 @@ impl MockFunction {
}.split_for_impl();
let tbf = tg.as_turbofish();
let name = self.name();
let argnames = &self.argnames;
let fields = vec!["{:?}"; argnames.len()].join(", ");
let fstr = if let Some(s) = &self.struct_ {
format!("{}::{}({}): No matching expectation found",
s, name, fields)
} else {
format!("{}::{}({}): No matching expectation found",
self.mod_ident, self.name(), fields)
};
let no_match_msg = quote!(format!(#fstr,
#(::mockall::MaybeDebugger(&#argnames)),*));
let desc = self.desc();
let no_match_msg = quote!(format!("{}: No matching expectation found",
#desc));
let sig = &self.sig;
let vis = if self.trait_.is_some() {
&Visibility::Inherited
Expand Down Expand Up @@ -546,6 +538,19 @@ impl MockFunction {
)
}

/// Generate a code fragment that will print a description of the invocation
fn desc(&self) -> impl ToTokens {
let argnames = &self.argnames;
let name = if let Some(s) = &self.struct_ {
format!("{}::{}", s, self.sig.ident)
} else {
format!("{}::{}", self.mod_ident, self.sig.ident)
};
let fields = vec!["{:?}"; argnames.len()].join(", ");
let fstr = format!("{}({})", name, fields);
quote!(format!(#fstr, #(::mockall::MaybeDebugger(&#argnames)),*))
}

/// Generate code for the expect_ method
///
/// # Arguments
Expand Down Expand Up @@ -816,15 +821,15 @@ impl<'a> ToTokens for Common<'a> {
}

impl #ig Common #tg #wc {
fn call(&self) {
fn call(&self, desc: &str) {
self.times.call()
.unwrap_or_else(|m| {
let desc = format!("{}",
self.matcher.lock().unwrap());
panic!("{}: Expectation({}) {}", #funcname, desc,
m);
});
self.verify_sequence();
self.verify_sequence(desc);
if self.times.is_satisfied() {
self.satisfy_sequence()
}
Expand Down Expand Up @@ -894,9 +899,9 @@ impl<'a> ToTokens for Common<'a> {
);
}

fn verify_sequence(&self) {
fn verify_sequence(&self, desc: &str) {
if let Some(__mockall_handle) = &self.seq_handle {
__mockall_handle.verify()
__mockall_handle.verify(desc)
}
}
}
Expand Down Expand Up @@ -1811,7 +1816,10 @@ struct RefExpectation<'a> {

impl<'a> ToTokens for RefExpectation<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let argnames = &self.f.argnames;
let argty = &self.f.argty;
let common_methods = CommonExpectationMethods{f: &self.f};
let desc = self.f.desc();
let funcname = self.f.funcname();
let (ig, tg, wc) = self.f.egenerics.split_for_impl();

Expand All @@ -1832,9 +1840,9 @@ impl<'a> ToTokens for RefExpectation<'a> {
#[allow(clippy::unused_unit)]
impl #ig Expectation #tg #wc {
/// Call this [`Expectation`] as if it were the real method.
#v fn call #lg (&self) -> #output
#v fn call #lg (&self, #(#argnames: #argty, )*) -> #output
{
self.common.call();
self.common.call(&#desc);
self.rfunc.call().unwrap_or_else(|m| {
let desc = format!("{}",
self.common.matcher.lock().unwrap());
Expand Down Expand Up @@ -1876,6 +1884,7 @@ impl<'a> ToTokens for RefMutExpectation<'a> {
let common_methods = CommonExpectationMethods{f: &self.f};
let argnames = &self.f.argnames;
let argty = &self.f.argty;
let desc = self.f.desc();
let funcname = self.f.funcname();
let (ig, tg, wc) = self.f.egenerics.split_for_impl();
let (_, common_tg, _) = self.f.cgenerics.split_for_impl();
Expand All @@ -1897,7 +1906,7 @@ impl<'a> ToTokens for RefMutExpectation<'a> {
#v fn call_mut #lg (&mut self, #(#argnames: #argty, )*)
-> &mut #owned_output
{
self.common.call();
self.common.call(&#desc);
let desc = format!("{}",
self.common.matcher.lock().unwrap());
self.rfunc.call_mut(#(#argnames, )*).unwrap_or_else(|m| {
Expand Down Expand Up @@ -1962,13 +1971,15 @@ impl<'a> ToTokens for StaticExpectation<'a> {
let common_methods = CommonExpectationMethods{f: &self.f};
let argnames = &self.f.argnames;
let argty = &self.f.argty;
let desc = self.f.desc();
let hrtb = self.f.hrtb();
let funcname = self.f.funcname();
let (ig, tg, wc) = self.f.egenerics.split_for_impl();
let (_, common_tg, _) = self.f.cgenerics.split_for_impl();
let lg = lifetimes_to_generics(&self.f.alifetimes);
let output = &self.f.output;
let v = &self.f.privmod_vis;

quote!(
/// Expectation type for methods that return a `'static` type.
/// This is the type returned by the `expect_*` methods.
Expand All @@ -1983,7 +1994,7 @@ impl<'a> ToTokens for StaticExpectation<'a> {
#[doc(hidden)]
#v fn call #lg (&self, #(#argnames: #argty, )* ) -> #output
{
self.common.call();
self.common.call(&#desc);
self.rfunc.lock().unwrap().call_mut(#(#argnames, )*)
.unwrap_or_else(|message| {
let desc = format!("{}",
Expand Down Expand Up @@ -2152,7 +2163,7 @@ impl<'a> ToTokens for RefExpectations<'a> {
__mockall_e.matches(#(#predexprs, )*) &&
(!__mockall_e.is_done() || self.0.len() == 1))
.map(move |__mockall_e|
__mockall_e.call()
__mockall_e.call(#(#argnames),*)
)
}

Expand Down

0 comments on commit fb50ec5

Please sign in to comment.