diff --git a/CHANGELOG.md b/CHANGELOG.md index 32506727..472bed73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added +- automock will now automatically generate Debug implementations for traits and + structs. mock! will to, if you put `#[derive(Debug)]` above the struct's + name. + ([#289](https://github.com/asomers/mockall/pull/289)) + - Added support for specific impls. A specific impl is an implementation of a trait on a generic struct with specified generic parameters. For example, `impl Foo for Bar` as opposed to `impl Foo for Bar`. Mockall does @@ -15,7 +20,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- Mockall is picker now about how you mock a trait on a generic struct. +- Mockall is pickier now about how you mock a trait on a generic struct. Previously you could usually omit the generics. Now, they're required. i.e., ```rust diff --git a/mockall/src/lib.rs b/mockall/src/lib.rs index aa540c8f..ed1aff3b 100644 --- a/mockall/src/lib.rs +++ b/mockall/src/lib.rs @@ -45,6 +45,7 @@ //! * [`Static methods`](#static-methods) //! * [`Modules`](#modules) //! * [`Foreign functions`](#foreign-functions) +//! * [`Debug`](#debug) //! * [`Async Traits`](#async-traits) //! * [`Crate features`](#crate-features) //! * [`Examples`](#examples) @@ -1032,6 +1033,22 @@ //! # fn main() {} //! ``` //! +//! ## Debug +//! +//! `#[automock]` will automatically generate `Debug` impls when mocking traits +//! and struct impls. `mock!` will too, if you add a `#[derive(Debug)]`, like +//! this: +//! ```no_run +//! # use mockall::*; +//! mock! { +//! #[derive(Debug)] +//! pub Foo {} +//! } +//! # fn main() { +//! # format!("{:?}", &MockFoo::default()); +//! # } +//! ``` +//! //! ## Async Traits //! //! Async traits aren't yet (as of 1.47.0) a part of the Rust language. But diff --git a/mockall/tests/automock_debug.rs b/mockall/tests/automock_debug.rs new file mode 100644 index 00000000..69867756 --- /dev/null +++ b/mockall/tests/automock_debug.rs @@ -0,0 +1,35 @@ +// vim: tw=80 +//! A mocked struct should implement Debug +#![deny(warnings)] + +use mockall::*; + +#[automock] +pub trait Foo {} + +pub trait Bar {} +pub struct Baz {} +#[automock] +impl Bar for Baz{} + +pub struct Bean {} +#[automock] +impl Bean{} + +#[test] +fn automock_trait() { + let foo = MockFoo::new(); + assert_eq!("MockFoo", format!("{:?}", foo)); +} + +#[test] +fn automock_struct_impl() { + let bean = MockBean::new(); + assert_eq!("MockBean", format!("{:?}", bean)); +} + +#[test] +fn automock_trait_impl() { + let baz = MockBaz::new(); + assert_eq!("MockBaz", format!("{:?}", baz)); +} diff --git a/mockall/tests/mock_debug.rs b/mockall/tests/mock_debug.rs new file mode 100644 index 00000000..da7e1312 --- /dev/null +++ b/mockall/tests/mock_debug.rs @@ -0,0 +1,44 @@ +// vim: tw=80 +//! A mocked struct should implement Debug +#![deny(warnings)] + +use mockall::*; +use std::fmt::{self, Debug, Formatter}; + +// using derive(Debug) tells mockall to generate the Debug impl automatically +mock!{ + #[derive(Debug)] + pub Bar { + fn foo(&self) -> u32; + } + impl Clone for Bar { + fn clone(&self) -> Self; + } +} + +// With no derive(Debug), mockall won't genetate the debug impl automatically +mock!{ + pub Baz { + fn foo(&self) -> u32; + } + impl Clone for Baz { + fn clone(&self) -> Self; + } +} +impl Debug for MockBaz { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_struct("XXX").finish() + } +} + +#[test] +fn automatic() { + let bar = MockBar::new(); + assert_eq!("MockBar", format!("{:?}", bar)); +} + +#[test] +fn manual() { + let baz = MockBaz::new(); + assert_eq!("XXX", format!("{:?}", baz)); +} diff --git a/mockall_derive/src/lib.rs b/mockall_derive/src/lib.rs index cac1d005..2721a851 100644 --- a/mockall_derive/src/lib.rs +++ b/mockall_derive/src/lib.rs @@ -565,6 +565,7 @@ impl<'a> AttrFormatter<'a> { // XXX This logic requires that attributes are imported with their // standard names. #[allow(clippy::needless_bool)] + #[allow(clippy::if_same_then_else)] fn format(&mut self) -> Vec { self.attrs.iter() .cloned() @@ -572,6 +573,9 @@ impl<'a> AttrFormatter<'a> { let i = attr.path.get_ident(); if i.is_none() { false + } else if *i.as_ref().unwrap() == "derive" { + // We can't usefully derive any traits. Ignore them + false } else if *i.as_ref().unwrap() == "doc" { self.doc } else if *i.as_ref().unwrap() == "async_trait" { diff --git a/mockall_derive/src/mock_item_struct.rs b/mockall_derive/src/mock_item_struct.rs index 2dc0cc50..ddba36b7 100644 --- a/mockall_derive/src/mock_item_struct.rs +++ b/mockall_derive/src/mock_item_struct.rs @@ -116,6 +116,8 @@ pub(crate) struct MockItemStruct { attrs: Vec, consts: Vec, generics: Generics, + /// Should Mockall generate a Debug implementation? + auto_debug: bool, /// Does the original struct have a `new` method? has_new: bool, /// Inherent methods of the mock struct @@ -129,6 +131,25 @@ pub(crate) struct MockItemStruct { } impl MockItemStruct { + fn debug_impl(&self) -> impl ToTokens { + if self.auto_debug { + let (ig, tg, wc) = self.generics.split_for_impl(); + let struct_name = &self.name; + let struct_name_str = format!("{}", self.name); + quote!( + impl #ig ::std::fmt::Debug for #struct_name #tg #wc { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) + -> ::std::result::Result<(), std::fmt::Error> + { + f.debug_struct(#struct_name_str).finish() + } + } + ) + } else { + quote!() + } + } + fn new_method(&self) -> impl ToTokens { if self.has_new { TokenStream::new() @@ -159,6 +180,7 @@ impl MockItemStruct { impl From for MockItemStruct { fn from(mockable: MockableStruct) -> MockItemStruct { + let auto_debug = mockable.derives_debug(); let modname = gen_mod_ident(&mockable.name, None); let generics = mockable.generics.clone(); let struct_name = &mockable.name; @@ -192,6 +214,7 @@ impl From for MockItemStruct { MockItemStruct { attrs: mockable.attrs, + auto_debug, consts: mockable.consts, generics, has_new, @@ -210,6 +233,7 @@ impl ToTokens for MockItemStruct { .async_trait(false) .format(); let consts = &self.consts; + let debug_impl = self.debug_impl(); let struct_name = &self.name; let (ig, tg, wc) = self.generics.split_for_impl(); let modname = &self.modname; @@ -283,6 +307,7 @@ impl ToTokens for MockItemStruct { { #(#field_definitions),* } + #debug_impl impl #ig ::std::default::Default for #struct_name #tg #wc { #[allow(clippy::default_trait_access)] fn default() -> Self { diff --git a/mockall_derive/src/mockable_item.rs b/mockall_derive/src/mockable_item.rs index 124d7461..df6085eb 100644 --- a/mockall_derive/src/mockable_item.rs +++ b/mockall_derive/src/mockable_item.rs @@ -70,7 +70,7 @@ impl From<(Attrs, ItemForeignMod)> for MockableModule { pub_token: ::default() }); let attrs = quote!( - #[deprecated(since = "0.9.0", note = "Using automock directly on an extern block is deprecated. Instead, wrap the extern block in a module, and automock that, like #[automoock] mod ffi { extern \"C\" { fn foo ... } }")] + #[deprecated(since = "0.9.0", note = "Using automock directly on an extern block is deprecated. Instead, wrap the extern block in a module, and automock that, like #[automock] mod ffi { extern \"C\" { fn foo ... } }")] ); let mut content = vec![ // When mocking extern blocks, we pretend that they're modules, so diff --git a/mockall_derive/src/mockable_struct.rs b/mockall_derive/src/mockable_struct.rs index 99e83f8a..993b55d6 100644 --- a/mockall_derive/src/mockable_struct.rs +++ b/mockall_derive/src/mockable_struct.rs @@ -92,6 +92,17 @@ fn add_lifetime_parameters(sig: &mut Signature) { } } +/// Generate a #[derive(Debug)] Attribute +fn derive_debug() -> Attribute { + Attribute { + pound_token: ::default(), + style: AttrStyle::Outer, + bracket_token: token::Bracket::default(), + path: Path::from(format_ident!("derive")), + tokens: quote!((Debug)) + } +} + /// Add "Mock" to the front of the named type fn mock_ident_in_type(ty: &mut Type) { match ty { @@ -291,10 +302,38 @@ pub(crate) struct MockableStruct { pub impls: Vec } +impl MockableStruct { + /// Does this struct derive Debug? + pub fn derives_debug(&self) -> bool { + self.attrs.iter() + .any(|attr| { + if let Ok(Meta::List(ml)) = attr.parse_meta() { + let i = ml.path.get_ident(); + if i.map_or(false, |i| *i == "derive") { + ml.nested.iter() + .any(|nm| { + if let NestedMeta::Meta(m) = nm { + let i = m.path().get_ident(); + i.map_or(false, |i| *i == "Debug") + } else { + false + } + }) + } else { + false + } + } else { + false + } + }) + } +} + impl From<(Attrs, ItemTrait)> for MockableStruct { fn from((attrs, item_trait): (Attrs, ItemTrait)) -> MockableStruct { let trait_ = attrs.substitute_trait(&item_trait); - let attrs = trait_.attrs.clone(); + let mut attrs = trait_.attrs.clone(); + attrs.push(derive_debug()); let vis = trait_.vis.clone(); let name = gen_mock_ident(&trait_.ident); let generics = trait_.generics.clone(); @@ -324,7 +363,8 @@ impl From for MockableStruct { Ident::new("", Span::call_site()) } }; - let attrs = item_impl.attrs.clone(); + let mut attrs = item_impl.attrs.clone(); + attrs.push(derive_debug()); let mut consts = Vec::new(); let generics = item_impl.generics.clone(); let mut methods = Vec::new();