diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cca5e17..de7d9c70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/mockall/src/lib.rs b/mockall/src/lib.rs index 1b0e6624..a6055863 100644 --- a/mockall/src/lib.rs +++ b/mockall/src/lib.rs @@ -1146,7 +1146,7 @@ use downcast::*; use std::{ any, - fmt::{self, Debug, Formatter}, + fmt::Debug, marker::PhantomData, ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}, @@ -1462,28 +1462,38 @@ pub struct DefaultReturner(PhantomData); } } +// 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 where T: Debug { fn debug_string(&self) -> DebugPrint<'_, T>; } +impl<'a, T: Debug> ViaDebug 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 } } diff --git a/mockall/tests/automock_foreign_c.rs b/mockall/tests/automock_foreign_c.rs index 6dd33d05..f64b36c1 100644 --- a/mockall/tests/automock_foreign_c.rs +++ b/mockall/tests/automock_foreign_c.rs @@ -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() diff --git a/mockall/tests/automock_foreign_extern.rs b/mockall/tests/automock_foreign_extern.rs index c6ee6033..3bdd21ae 100644 --- a/mockall/tests/automock_foreign_extern.rs +++ b/mockall/tests/automock_foreign_extern.rs @@ -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() diff --git a/mockall/tests/automock_module.rs b/mockall/tests/automock_module.rs index a5182dbf..8f362b3f 100644 --- a/mockall/tests/automock_module.rs +++ b/mockall/tests/automock_module.rs @@ -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() diff --git a/mockall/tests/automock_nondebug.rs b/mockall/tests/automock_nondebug.rs index 26ff9b93..b5ac67c1 100644 --- a/mockall/tests/automock_nondebug.rs +++ b/mockall/tests/automock_nondebug.rs @@ -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)); diff --git a/mockall/tests/automock_slice_arguments.rs b/mockall/tests/automock_slice_arguments.rs index f05b5df8..37044ac2 100644 --- a/mockall/tests/automock_slice_arguments.rs +++ b/mockall/tests/automock_slice_arguments.rs @@ -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() diff --git a/mockall/tests/mock_generic_arguments.rs b/mockall/tests/mock_generic_arguments.rs index 3a00291e..aa3aaf4c 100644 --- a/mockall/tests/mock_generic_arguments.rs +++ b/mockall/tests/mock_generic_arguments.rs @@ -6,7 +6,7 @@ use mockall::*; mock! { Foo { - fn foo(&self, t: T) -> i32; + fn foo(&self, t: T) -> i32; fn bar(&self, t: T) -> i32; } } @@ -47,12 +47,7 @@ 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::() @@ -60,4 +55,14 @@ mod with { .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::() + .with(predicate::eq(4)) + .return_const(0); + mock.bar(4i32); + } } diff --git a/mockall/tests/mock_generic_struct_with_generic_static_method.rs b/mockall/tests/mock_generic_struct_with_generic_static_method.rs index b1e3aee7..bf6095ae 100644 --- a/mockall/tests/mock_generic_struct_with_generic_static_method.rs +++ b/mockall/tests/mock_generic_struct_with_generic_static_method.rs @@ -6,12 +6,12 @@ use mockall::*; use std::sync::Mutex; mock! { - Foo { - fn foo(t: T, q: Q) -> u64; + Foo { + fn foo(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(t: T, q: Q) -> u64; - fn foo3(t: T, q: Q) -> u64; + fn foo2(t: T, q: Q) -> u64; + fn foo3(t: T, q: Q) -> u64; } } @@ -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::::foo3_context(); diff --git a/mockall/tests/mock_return_mutable_reference.rs b/mockall/tests/mock_return_mutable_reference.rs index 9838fcfe..7bc0ed4f 100644 --- a/mockall/tests/mock_return_mutable_reference.rs +++ b/mockall/tests/mock_return_mutable_reference.rs @@ -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(); diff --git a/mockall/tests/mock_return_reference.rs b/mockall/tests/mock_return_reference.rs index e8c79696..d3fc71d8 100644 --- a/mockall/tests/mock_return_reference.rs +++ b/mockall/tests/mock_return_reference.rs @@ -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(); diff --git a/mockall/tests/mock_struct.rs b/mockall/tests/mock_struct.rs index bff87414..030c4fdc 100644 --- a/mockall/tests/mock_struct.rs +++ b/mockall/tests/mock_struct.rs @@ -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() @@ -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() @@ -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() diff --git a/mockall/tests/mock_struct_with_static_method.rs b/mockall/tests/mock_struct_with_static_method.rs index 299be51d..7a507468 100644 --- a/mockall/tests/mock_struct_with_static_method.rs +++ b/mockall/tests/mock_struct_with_static_method.rs @@ -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(); diff --git a/mockall/tests/mock_unsized.rs b/mockall/tests/mock_unsized.rs index 4c829b10..5753510e 100644 --- a/mockall/tests/mock_unsized.rs +++ b/mockall/tests/mock_unsized.rs @@ -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() diff --git a/mockall_derive/src/mock_function.rs b/mockall_derive/src/mock_function.rs index 29d22175..9d28ec39 100644 --- a/mockall_derive/src/mock_function.rs +++ b/mockall_derive/src/mock_function.rs @@ -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 @@ -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) @@ -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 @@ -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!( @@ -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()); @@ -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| {