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

Automatically generate Debug impls for mock structs #289

Merged
merged 3 commits into from
Jun 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<i32>` as opposed to `impl<T> Foo for Bar<T>`. Mockall does
Expand All @@ -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
Expand Down
17 changes: 17 additions & 0 deletions mockall/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions mockall/tests/automock_debug.rs
Original file line number Diff line number Diff line change
@@ -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));
}
44 changes: 44 additions & 0 deletions mockall/tests/mock_debug.rs
Original file line number Diff line number Diff line change
@@ -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));
}
4 changes: 4 additions & 0 deletions mockall_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,13 +565,17 @@ 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<Attribute> {
self.attrs.iter()
.cloned()
.filter(|attr| {
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" {
Expand Down
25 changes: 25 additions & 0 deletions mockall_derive/src/mock_item_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ pub(crate) struct MockItemStruct {
attrs: Vec<Attribute>,
consts: Vec<ImplItemConst>,
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
Expand All @@ -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()
Expand Down Expand Up @@ -159,6 +180,7 @@ impl MockItemStruct {

impl From<MockableStruct> 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;
Expand Down Expand Up @@ -192,6 +214,7 @@ impl From<MockableStruct> for MockItemStruct {

MockItemStruct {
attrs: mockable.attrs,
auto_debug,
consts: mockable.consts,
generics,
has_new,
Expand All @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion mockall_derive/src/mockable_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl From<(Attrs, ItemForeignMod)> for MockableModule {
pub_token: <Token![pub]>::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
Expand Down
44 changes: 42 additions & 2 deletions mockall_derive/src/mockable_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ fn add_lifetime_parameters(sig: &mut Signature) {
}
}

/// Generate a #[derive(Debug)] Attribute
fn derive_debug() -> Attribute {
Attribute {
pound_token: <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 {
Expand Down Expand Up @@ -291,10 +302,38 @@ pub(crate) struct MockableStruct {
pub impls: Vec<ItemImpl>
}

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();
Expand Down Expand Up @@ -324,7 +363,8 @@ impl From<ItemImpl> 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();
Expand Down