Skip to content

Commit

Permalink
Merge pull request #297 from asomers/cfg
Browse files Browse the repository at this point in the history
Allow #[cfg()] attributes on trait impls in mock!
  • Loading branch information
asomers committed Jun 27, 2021
2 parents 8076cd4 + 7ff1e03 commit 50e95a3
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 11 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

- `mock!` will now allow both methods and trait impls to be gated with
`#[cfg()]]` attributes. The attributes will be forwarded to all generated
code. This allows for example only mocking certain traits on certain OSes.
([#297](https://github.com/asomers/mockall/pull/297))

- automock will now automatically generate Debug implementations for traits and
structs. mock! will to, if you put `#[derive(Debug)]` above the struct's
name.
Expand Down
65 changes: 65 additions & 0 deletions mockall/tests/mock_cfg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// vim: tw=80
//! mock's methods and trait impls can be conditionally compiled
#![deny(warnings)]

use mockall::*;

// For this test, use the "nightly" feature as the cfg gate, because it's tested
// both ways in CI.
#[cfg(feature = "nightly")]
trait Beez {
fn beez(&self);
}
#[cfg(not(feature = "nightly"))]
trait Beez {
fn beez(&self, x: i32) -> i32;
}

mock! {
pub Foo {
#[cfg(feature = "nightly")]
fn foo(&self);
#[cfg(not(feature = "nightly"))]
fn foo(&self, x: i32) -> i32;
}
#[cfg(feature = "nightly")]
impl Beez for Foo {
fn beez(&self);
}
#[cfg(not(feature = "nightly"))]
impl Beez for Foo {
fn beez(&self, x: i32) -> i32;
}
}

#[test]
#[cfg(feature = "nightly")]
fn test_nightly_method() {
let mut mock = MockFoo::new();
mock.expect_foo()
.returning(|| ());
}

#[test]
#[cfg(feature = "nightly")]
fn test_nightly_trait() {
let mut mock = MockFoo::new();
mock.expect_beez()
.returning(|| ());
}

#[test]
#[cfg(not(feature = "nightly"))]
fn test_not_nightly_method() {
let mut mock = MockFoo::new();
mock.expect_foo()
.returning(|x| x + 1);
}

#[test]
#[cfg(not(feature = "nightly"))]
fn test_not_nightly_trait() {
let mut mock = MockFoo::new();
mock.expect_beez()
.returning(|x| x + 1);
}
41 changes: 32 additions & 9 deletions mockall_derive/src/mock_item_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,22 @@ fn phantom_fields(generics: &Generics) -> Vec<TokenStream> {
}

/// Filter out multiple copies of the same trait, even if they're implemented on
/// different types.
/// different types. But allow them if they have different attributes, which
/// probably indicates that they aren't meant to be compiled together.
fn unique_trait_iter<'a, I: Iterator<Item = &'a MockTrait>>(i: I)
-> impl Iterator<Item = &'a MockTrait>
{
let mut hs = HashSet::<Path>::default();
let mut hs = HashSet::<(Path, Vec<Attribute>)>::default();
i.filter(move |mt| {
if hs.contains(&mt.trait_path) {
let impl_attrs = AttrFormatter::new(&mt.attrs)
.async_trait(false)
.doc(false)
.format();
let key = (mt.trait_path.clone(), impl_attrs);
if hs.contains(&key) {
false
} else {
hs.insert(mt.trait_path.clone());
hs.insert(key);
true
}
})
Expand Down Expand Up @@ -266,22 +272,36 @@ impl ToTokens for MockItemStruct {
}).collect::<Vec<_>>();
let substruct_expectations = substructs.iter()
.filter(|ss| !ss.all_static())
.map(|ss| ss.fieldname.clone())
.collect::<Vec<_>>();
.map(|ss| {
let attrs = AttrFormatter::new(&ss.attrs)
.async_trait(false)
.doc(false)
.format();
let fieldname = &ss.fieldname;
quote!(#(#attrs)* self.#fieldname.checkpoint();)
}).collect::<Vec<_>>();
let mut field_definitions = substructs.iter()
.filter(|ss| !ss.all_static())
.map(|ss| {
let attrs = AttrFormatter::new(&ss.attrs)
.async_trait(false)
.doc(false)
.format();
let fieldname = &ss.fieldname;
let tyname = &ss.name;
quote!(#fieldname: #tyname #tg)
quote!(#(#attrs)* #fieldname: #tyname #tg)
}).collect::<Vec<_>>();
field_definitions.extend(self.methods.field_definitions(modname));
field_definitions.extend(self.phantom_fields());
let mut default_inits = substructs.iter()
.filter(|ss| !ss.all_static())
.map(|ss| {
let attrs = AttrFormatter::new(&ss.attrs)
.async_trait(false)
.doc(false)
.format();
let fieldname = &ss.fieldname;
quote!(#fieldname: Default::default())
quote!(#(#attrs)* #fieldname: Default::default())
}).collect::<Vec<_>>();
default_inits.extend(self.methods.default_inits());
default_inits.extend(self.phantom_default_inits());
Expand Down Expand Up @@ -325,7 +345,7 @@ impl ToTokens for MockItemStruct {
/// Validate that all current expectations for all methods have
/// been satisfied, and discard them.
pub fn checkpoint(&mut self) {
#(self.#substruct_expectations.checkpoint();)*
#(#substruct_expectations)*
#(#method_checkpoints)*
}
#new_method
Expand Down Expand Up @@ -380,6 +400,7 @@ impl ToTokens for MockItemTraitImpl {
quote!(
#[allow(non_snake_case)]
#[allow(missing_docs)]
#(#attrs)*
pub mod #modname {
use super::*;
#(#priv_mods)*
Expand All @@ -392,13 +413,15 @@ impl ToTokens for MockItemTraitImpl {
{
#(#field_definitions),*
}
#(#attrs)*
impl #ig ::std::default::Default for #struct_name #tg #wc {
fn default() -> Self {
Self {
#(#default_inits),*
}
}
}
#(#attrs)*
impl #ig #struct_name #tg #wc {
/// Validate that all current expectations for all methods have
/// been satisfied, and discard them.
Expand Down
10 changes: 8 additions & 2 deletions mockall_derive/src/mock_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use syn::{
};

use crate::{
AttrFormatter,
mock_function::{self, MockFunction},
compile_error
};
Expand Down Expand Up @@ -127,7 +128,11 @@ impl MockTrait {
// Supplying modname is an unfortunately hack. Ideally MockTrait
// wouldn't need to know that.
pub fn trait_impl(&self, modname: &Ident) -> impl ToTokens {
let attrs = &self.attrs;
let trait_impl_attrs = &self.attrs;
let impl_attrs = AttrFormatter::new(&self.attrs)
.async_trait(false)
.doc(false)
.format();
let (ig, _tg, wc) = self.generics.split_for_impl();
let consts = &self.consts;
let path_args = &self.self_path.arguments;
Expand All @@ -152,12 +157,13 @@ impl MockTrait {
let self_path = &self.self_path;
let types = &self.types;
quote!(
#(#attrs)*
#(#trait_impl_attrs)*
impl #ig #trait_path for #self_path #wc {
#(#consts)*
#(#types)*
#(#calls)*
}
#(#impl_attrs)*
impl #ig #self_path #wc {
#(#expects)*
#(#contexts)*
Expand Down

0 comments on commit 50e95a3

Please sign in to comment.