Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Print better expectation messages on stable #425

Merged
merged 13 commits into from
Feb 9, 2023
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).

- Raised MSRV to 1.60.0 because predicates-tree did.
([#430](https://github.com/asomers/mockall/pull/430))
- Better "No matching expectation found" messages on stable.
([#425](https://github.com/asomers/mockall/pull/425))

### Fixed

Expand Down
54 changes: 32 additions & 22 deletions mockall/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1146,7 +1146,7 @@
use downcast::*;
use std::{
any,
fmt::{self, Debug, Formatter},
fmt::Debug,
marker::PhantomData,
ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo,
RangeToInclusive},
Expand Down Expand Up @@ -1462,28 +1462,38 @@ pub struct DefaultReturner<O>(PhantomData<O>);
}
}

// Wrapper type to allow for better expectation messages for any type.
// Will first try Debug, otherwise will print '?'
#[doc(hidden)]
pub struct MaybeDebugger<'a, T>(pub &'a T);
::cfg_if::cfg_if! {
if #[cfg(feature = "nightly")] {
impl<'a, T> Debug for MaybeDebugger<'a, T> {
default fn fmt(&self, f: &mut Formatter<'_>)
-> Result<(), fmt::Error>
{
write!(f, "?")
}
}
impl<'a, T: Debug> Debug for MaybeDebugger<'a, T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
self.0.fmt(f)
}
}
} else {
impl<'a, T> Debug for MaybeDebugger<'a, T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "?")
}
}
pub struct ArgPrinter<'a, T>(pub &'a T);

#[doc(hidden)]
pub struct DebugPrint<'a, T: Debug>(pub &'a T);
impl<'a, T> Debug for DebugPrint<'a, T> where T: Debug {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Debug::fmt(self.0, f)
}
}
#[doc(hidden)]
pub trait ViaDebug<T> where T: Debug { fn debug_string(&self) -> DebugPrint<'_, T>; }
impl<'a, T: Debug> ViaDebug<T> for &ArgPrinter<'a, T> {
fn debug_string(&self) -> DebugPrint<'a, T> {
DebugPrint(self.0)
}
}

#[doc(hidden)]
pub struct NothingPrint;
impl Debug for NothingPrint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "?")
}
}
#[doc(hidden)]
pub trait ViaNothing { fn debug_string(&self) -> NothingPrint; }
impl<'a, T> ViaNothing for ArgPrinter<'a, T> {
fn debug_string(&self) -> NothingPrint {
NothingPrint
}
}

Expand Down
7 changes: 1 addition & 6 deletions mockall/tests/automock_foreign_c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,7 @@ pub fn normal_usage() {
}

#[test]
#[cfg_attr(feature = "nightly", should_panic(
expected = "mock_ffi::foo1(5): No matching expectation found"
))]
#[cfg_attr(not(feature = "nightly"), should_panic(
expected = "mock_ffi::foo1(?): No matching expectation found"
))]
#[should_panic(expected = "mock_ffi::foo1(5): No matching expectation found")]
fn with_no_matches() {
let ctx = mock_ffi::foo1_context();
ctx.expect()
Expand Down
7 changes: 1 addition & 6 deletions mockall/tests/automock_foreign_extern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,7 @@ extern "C" {
}

#[test]
#[cfg_attr(feature = "nightly", should_panic(
expected = "mock_ffi::foo1(5): No matching expectation found"
))]
#[cfg_attr(not(feature = "nightly"), should_panic(
expected = "mock_ffi::foo1(?): No matching expectation found"
))]
#[should_panic(expected = "mock_ffi::foo1(5): No matching expectation found")]
fn with_no_matches() {
let ctx = mock_ffi::foo1_context();
ctx.expect()
Expand Down
7 changes: 1 addition & 6 deletions mockall/tests/automock_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@ pub mod m {
}

#[test]
#[cfg_attr(feature = "nightly", should_panic(
expected = "mock_foo::bar1(5): No matching expectation found"
))]
#[cfg_attr(not(feature = "nightly"), should_panic(
expected = "mock_foo::bar1(?): No matching expectation found"
))]
#[should_panic(expected = "mock_foo::bar1(5): No matching expectation found")]
fn with_no_matches() {
let ctx = mock_foo::bar1_context();
ctx.expect()
Expand Down
7 changes: 1 addition & 6 deletions mockall/tests/automock_nondebug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,7 @@ pub trait Foo {
}

#[test]
#[cfg_attr(feature = "nightly", should_panic(
expected = "MockFoo::foo(?): No matching expectation found"
))]
#[cfg_attr(not(feature = "nightly"), should_panic(
expected = "MockFoo::foo(?): No matching expectation found"
))]
#[should_panic(expected = "MockFoo::foo(?): No matching expectation found")]
fn with_no_matches() {
let mock = MockFoo::new();
mock.foo(NonDebug(5));
Expand Down
7 changes: 1 addition & 6 deletions mockall/tests/automock_slice_arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,7 @@ mod withf {
use super::*;

#[test]
#[cfg_attr(feature = "nightly", should_panic(
expected = "MockFoo::foo([1, 2, 3, 4]): No matching expectation found"
))]
#[cfg_attr(not(feature = "nightly"), should_panic(
expected = "MockFoo::foo(?): No matching expectation found"
))]
#[should_panic(expected = "MockFoo::foo([1, 2, 3, 4]): No matching expectation found")]
fn fail() {
let mut mock = MockFoo::new();
mock.expect_foo()
Expand Down
19 changes: 12 additions & 7 deletions mockall/tests/mock_generic_arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use mockall::*;

mock! {
Foo {
fn foo<T: 'static>(&self, t: T) -> i32;
fn foo<T: 'static + std::fmt::Debug>(&self, t: T) -> i32;
jefftt marked this conversation as resolved.
Show resolved Hide resolved
fn bar<T: 'static>(&self, t: T) -> i32;
}
}
Expand Down Expand Up @@ -47,17 +47,22 @@ mod with {
}

#[test]
#[cfg_attr(feature = "nightly", should_panic(
expected = "MockFoo::foo(4): No matching expectation found"
))]
#[cfg_attr(not(feature = "nightly"), should_panic(
expected = "MockFoo::foo(?): No matching expectation found"
))]
#[should_panic(expected = "MockFoo::foo(4): No matching expectation found")]
fn wrong_generic_type() {
let mut mock = MockFoo::new();
mock.expect_foo::<i16>()
.with(predicate::eq(4))
.return_const(0);
mock.foo(4i32);
}

#[test]
#[should_panic(expected = "MockFoo::bar(?): No matching expectation found")]
fn no_debug_trait_bound() {
let mut mock = MockFoo::new();
mock.expect_bar::<i16>()
.with(predicate::eq(4))
.return_const(0);
mock.bar(4i32);
}
}
15 changes: 5 additions & 10 deletions mockall/tests/mock_generic_struct_with_generic_static_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ use mockall::*;
use std::sync::Mutex;

mock! {
Foo<T: 'static> {
fn foo<Q: 'static>(t: T, q: Q) -> u64;
Foo<T: 'static + std::fmt::Debug> {
asomers marked this conversation as resolved.
Show resolved Hide resolved
fn foo<Q: 'static + std::fmt::Debug>(t: T, q: Q) -> u64;
// We must use a different method for every should_panic test, so the
// shared mutex doesn't get poisoned.
fn foo2<Q: 'static>(t: T, q: Q) -> u64;
fn foo3<Q: 'static>(t: T, q: Q) -> u64;
fn foo2<Q: 'static + std::fmt::Debug>(t: T, q: Q) -> u64;
fn foo3<Q: 'static + std::fmt::Debug>(t: T, q: Q) -> u64;
}
}

Expand Down Expand Up @@ -48,12 +48,7 @@ fn ctx_checkpoint() {

// Expectations should be cleared when a context object drops
#[test]
#[cfg_attr(feature = "nightly", should_panic(
expected = "MockFoo::foo3(42, 69): No matching expectation found"
))]
#[cfg_attr(not(feature = "nightly"), should_panic(
expected = "MockFoo::foo3(?, ?): No matching expectation found"
))]
#[should_panic(expected = "MockFoo::foo3(42, 69): No matching expectation found")]
fn ctx_hygiene() {
{
let ctx0 = MockFoo::<u32>::foo3_context();
Expand Down
5 changes: 1 addition & 4 deletions mockall/tests/mock_return_mutable_reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,7 @@ 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"))]
#[should_panic(expected = "MockFoo::foo(4): Method sequence violation")]
fn fail() {
let mut seq = Sequence::new();
let mut mock = MockFoo::new();
Expand Down
5 changes: 1 addition & 4 deletions mockall/tests/mock_return_reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,7 @@ mod sequence {
}

#[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"))]
#[should_panic(expected = "MockFoo::foo(4): Method sequence violation")]
fn fail() {
let mut seq = Sequence::new();
let mut mock = MockFoo::new();
Expand Down
21 changes: 3 additions & 18 deletions mockall/tests/mock_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,7 @@ mod checkpoint {
}

#[test]
#[cfg_attr(feature = "nightly", should_panic(
expected = "MockFoo::foo(0): No matching expectation found"
))]
#[cfg_attr(not(feature = "nightly"), should_panic(
expected = "MockFoo::foo(?): No matching expectation found"
))]
#[should_panic(expected = "MockFoo::foo(0): No matching expectation found")]
fn removes_old_expectations() {
let mut mock = MockFoo::new();
mock.expect_foo()
Expand Down Expand Up @@ -123,12 +118,7 @@ mod r#match {
}

#[test]
#[cfg_attr(feature = "nightly", should_panic(
expected = "MockFoo::bar(5): No matching expectation found"
))]
#[cfg_attr(not(feature = "nightly"), should_panic(
expected = "MockFoo::bar(?): No matching expectation found"
))]
#[should_panic(expected = "MockFoo::bar(5): No matching expectation found")]
fn with_no_matches() {
let mut mock = MockFoo::new();
mock.expect_bar()
Expand Down Expand Up @@ -156,12 +146,7 @@ mod r#match {
}

#[test]
#[cfg_attr(feature = "nightly", should_panic(
expected = "MockFoo::bar(5): No matching expectation found"
))]
#[cfg_attr(not(feature = "nightly"), should_panic(
expected = "MockFoo::bar(?): No matching expectation found"
))]
#[should_panic(expected = "MockFoo::bar(5): No matching expectation found")]
fn withf_no_matches() {
let mut mock = MockFoo::new();
mock.expect_bar()
Expand Down
7 changes: 1 addition & 6 deletions mockall/tests/mock_struct_with_static_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,7 @@ fn ctx_checkpoint() {

// Expectations should be cleared when a context object drops
#[test]
#[cfg_attr(feature = "nightly", should_panic(
expected = "MockFoo::bar3(42): No matching expectation found"
))]
#[cfg_attr(not(feature = "nightly"), should_panic(
expected = "MockFoo::bar3(?): No matching expectation found"
))]
#[should_panic(expected = "MockFoo::bar3(42): No matching expectation found")]
fn ctx_hygiene() {
{
let ctx0 = MockFoo::bar3_context();
Expand Down
7 changes: 1 addition & 6 deletions mockall/tests/mock_unsized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,7 @@ fn with_eq() {
}

#[test]
#[cfg_attr(feature = "nightly", should_panic(
expected = "MockFoo::foo(\"xxx\"): No matching expectation found"
))]
#[cfg_attr(not(feature = "nightly"), should_panic(
expected = "MockFoo::foo(?): No matching expectation found"
))]
#[should_panic(expected = "MockFoo::foo(\"xxx\"): No matching expectation found")]
fn with_never() {
let mut foo = MockFoo::new();
foo.expect_foo()
Expand Down
7 changes: 6 additions & 1 deletion mockall_derive/src/mock_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ impl MockFunction {
#(#attrs)*
#dead_code
#vis #sig {
use ::mockall::{ViaDebug, ViaNothing};
let no_match_msg = #no_match_msg;
#deref {
let __mockall_guard = #outer_mod_path::EXPECTATIONS
Expand All @@ -532,6 +533,7 @@ impl MockFunction {
#(#attrs)*
#dead_code
#vis #sig {
use ::mockall::{ViaDebug, ViaNothing};
let no_match_msg = #no_match_msg;
#deref self.#substruct_obj #name.#call #tbf(#(#call_exprs,)*)
.expect(&no_match_msg)
Expand Down Expand Up @@ -602,7 +604,7 @@ impl MockFunction {
};
let fields = vec!["{:?}"; argnames.len()].join(", ");
let fstr = format!("{name}({fields})");
quote!(std::format!(#fstr, #(::mockall::MaybeDebugger(&#argnames)),*))
quote!(std::format!(#fstr, #((&&::mockall::ArgPrinter(&#argnames)).debug_string()),*))
}

/// Generate code for the expect_ method
Expand Down Expand Up @@ -1952,6 +1954,7 @@ impl<'a> ToTokens for RefExpectation<'a> {
/// Call this [`Expectation`] as if it were the real method.
#v fn call #lg (&self, #(#argnames: #argty, )*) -> #output
{
use ::mockall::{ViaDebug, ViaNothing};
self.common.call(&#desc);
self.rfunc.call().unwrap_or_else(|m| {
let desc = std::format!(
Expand Down Expand Up @@ -2016,6 +2019,7 @@ impl<'a> ToTokens for RefMutExpectation<'a> {
#v fn call_mut #lg (&mut self, #(#argnames: #argty, )*)
-> &mut #owned_output
{
use ::mockall::{ViaDebug, ViaNothing};
self.common.call(&#desc);
let desc = std::format!(
"{}", self.common.matcher.lock().unwrap());
Expand Down Expand Up @@ -2104,6 +2108,7 @@ impl<'a> ToTokens for StaticExpectation<'a> {
#[doc(hidden)]
#v fn call #lg (&self, #(#argnames: #argty, )* ) -> #output
{
use ::mockall::{ViaDebug, ViaNothing};
self.common.call(&#desc);
self.rfunc.lock().unwrap().call_mut(#(#argnames, )*)
.unwrap_or_else(|message| {
Expand Down