Skip to content

Commit

Permalink
Automatically generate Debug impls for mock structs
Browse files Browse the repository at this point in the history
Fixes #287
  • Loading branch information
asomers committed Jun 20, 2021
1 parent 9c00a93 commit 59ddad5
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 2 deletions.
5 changes: 5 additions & 0 deletions 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 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));
}
3 changes: 3 additions & 0 deletions mockall_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,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" {
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
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

0 comments on commit 59ddad5

Please sign in to comment.