Skip to content

Commit

Permalink
Fix some visibility issues with mocking modules and foreign functions:
Browse files Browse the repository at this point in the history
* When mocking foreign functions, the 'foreign "<ABI>"' block is not a
  module, and should not introduce an extra layer of visibility
  modifications.

* The visibility of the Expectation must always match the levels
  argument of Expectation::new().

* When foreign functions, the argument and return types of mocked
  functions must be superfied by one level.  Otherwise code like this
  wouldn't compile:
  ```rust
  struct SuperT;

  #[automock]
  extern "Rust" {
      fn foo(x: super::SuperT) -> super::SuperT;
  }
  ```
  • Loading branch information
asomers committed May 24, 2020
1 parent 5543ecb commit f59ddd9
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 24 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Fix the formatting of generated doc comments for context methods
([#132](https://github.com/asomers/mockall/pull/132))

- Fixed some visibility issues in the generated mocks for modules and extern
functions.
([#133](https://github.com/asomers/mockall/pull/133))

### Removed

## [0.7.1] - 3 May 2020
Expand Down
57 changes: 57 additions & 0 deletions mockall/tests/automock_foreign_nonpub.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// vim: tw=80
//! "extern" blocks aren't modules, and shouldn't introduce another layer of
//! "super"s.
//!
//! The tests really just check that the visibility of the functions and
//! expectations functions is correct.
#[allow(unused)]
mod outer {
struct SuperT();

pub mod inner {
use mockall::automock;

#[derive(Debug)]
pub(crate) struct PubCrateT();
#[derive(Debug)]
struct PrivT();

#[automock(mod mock_ffi;)]
extern "Rust" {
pub(crate) fn foo(x: PubCrateT) -> PubCrateT;
fn bar(x: PrivT) -> PrivT;
pub(in super) fn baz(x: super::SuperT) -> super::SuperT;
pub(in crate::outer) fn bang(x: crate::outer::SuperT)
-> crate::outer::SuperT;
}

#[test]
fn test_bar() {
let ctx = mock_ffi::bar_context();
ctx.expect().returning(|_| PrivT());
unsafe{mock_ffi::bar(PrivT())};
}


#[test]
fn test_baz() {
let ctx = mock_ffi::baz_context();
ctx.expect().returning(|_| super::SuperT());
unsafe{mock_ffi::baz(super::SuperT())};
}
}

#[test]
fn test_bang() {
let ctx = inner::mock_ffi::bang_context();
ctx.expect().returning(|_| SuperT());
unsafe{inner::mock_ffi::bang(SuperT())};
}
}

#[test]
fn foo() {
let ctx = outer::inner::mock_ffi::foo_context();
ctx.expect().returning(|_| outer::inner::PubCrateT());
unsafe{outer::inner::mock_ffi::foo(outer::inner::PubCrateT())};
}
16 changes: 12 additions & 4 deletions mockall/tests/automock_module_nonpub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,20 @@ cfg_if::cfg_if! {
mod m {
use super::*;

fn foo(x: PubCrateT) -> PubCrateT { unimplemented!() }
fn bar(x: PrivT) -> PrivT { unimplemented!() }
fn baz(x: super::super::SuperT) -> super::super::SuperT {
pub(crate) fn foo(x: PubCrateT) -> PubCrateT {
unimplemented!()
}
fn bang(x: crate::outer::SuperT) -> crate::outer::SuperT {
pub(super) fn bar(x: PrivT) -> PrivT {
unimplemented!()
}
pub(in super::super) fn baz(x: super::super::SuperT)
-> super::super::SuperT
{
unimplemented!()
}
pub(in crate::outer) fn bang(x: crate::outer::SuperT)
-> crate::outer::SuperT
{
unimplemented!()
}
}
Expand Down
49 changes: 31 additions & 18 deletions mockall_derive/src/automock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ fn mock_foreign(attrs: Attrs, foreign_mod: ItemForeignMod) -> TokenStream {
.collect::<Vec<_>>();
}
).to_tokens(&mut cp_body);
mock_foreign_function(&modname, f).to_tokens(&mut body);
mock_foreign_function(&modname, f, 1).to_tokens(&mut body);
},
ForeignItem::Static(s) => {
// Copy verbatim so a mock method can mutate it
Expand All @@ -362,22 +362,31 @@ fn mock_foreign(attrs: Attrs, foreign_mod: ItemForeignMod) -> TokenStream {
pub fn checkpoint() { #cp_body }).to_tokens(&mut body);
quote!(
#[allow(missing_docs)]
pub mod #modname { #body }
pub mod #modname {
use super::*;
#body
}
)
}

/// Mock a foreign function the same way we mock static trait methods: with a
/// global Expectations object
fn mock_foreign_function(modname: &Ident, f: ForeignItemFn) -> TokenStream {
fn mock_foreign_function(modname: &Ident, f: ForeignItemFn, levels: i32)
-> TokenStream
{
// Foreign functions are always unsafe. Mock foreign functions should be
// unsafe too, to prevent "warning: unused unsafe" messages.
let mut sig = f.sig.clone();
sig.unsafety = Some(Token![unsafe](f.sig.span()));
mock_function(modname, &f.attrs, &f.vis, &sig)
mock_function(modname, &f.attrs, &f.vis, &sig, levels)
}

/// # Arguments
///
/// * `call_levels`: Module levels that will be added above the mock function
/// call
fn mock_function(modname: &Ident, attrs: &[Attribute], vis: &Visibility,
sig: &Signature)
sig: &Signature, call_levels: i32)
-> TokenStream
{
let asyncness = &sig.asyncness;
Expand All @@ -388,14 +397,15 @@ fn mock_function(modname: &Ident, attrs: &[Attribute], vis: &Visibility,
let unsafety = &sig.unsafety;
let output = match &sig.output{
ReturnType::Default => quote!(-> ()),
_ => {
let decl_output = &sig.output;
quote!(#decl_output)
ReturnType::Type(_, ref ty) => {
let decl_output = supersuperfy(ty, call_levels);
quote!(-> #decl_output)
}
};
let attrs_nodocs = format_attrs(&attrs, false);
let attrs_docs = format_attrs(&attrs, true);
let mut args = Vec::new();
let mut argnames = Vec::new();
let mut argty = Vec::new();

if sig.variadic.is_some() {
compile_error(sig.variadic.span(),
Expand All @@ -412,24 +422,25 @@ fn mock_function(modname: &Ident, attrs: &[Attribute], vis: &Visibility,
for p in inputs.iter() {
match p {
FnArg::Typed(arg) => {
args.push(arg.pat.clone());
argnames.push(arg.pat.clone());
argty.push(supersuperfy(&arg.ty, call_levels));
},
_ => compile_error(p.span(),
"Should be unreachable for normal Rust code")
}
}

let meth_vis = expectation_visibility(&vis, 1);
let meth_vis = expectation_visibility(&vis, call_levels);
let context_ident = format_ident!("{}_context", &ident);
let expect_vis = expectation_visibility(&vis, 2);
let expect_vis = expectation_visibility(&vis, call_levels + 1);
let mut g = generics.clone();
let lt = Lifetime::new("'guard", Span::call_site());
let ltd = LifetimeDef::new(lt);
g.params.push(GenericParam::Lifetime(ltd));

let mut out = TokenStream::new();
Expectation::new(&attrs_nodocs, &inputs, None, generics,
&ident, &mod_ident, None, &sig.output, &expect_vis, 1)
&ident, &mod_ident, None, &sig.output, &expect_vis, call_levels + 1)
.to_tokens(&mut out);
let no_match_msg = format!("{}::{}: No matching expectation found",
modname, ident);
Expand All @@ -441,7 +452,7 @@ fn mock_function(modname: &Ident, attrs: &[Attribute], vis: &Visibility,
quote!(
#attrs_docs
#meth_vis #constness #unsafety #asyncness
#fn_token #ident #generics (#inputs) #output {
#fn_token #ident #generics ( #(#argnames: #argty),* ) #output {
{
let __mockall_guard = #mod_ident::EXPECTATIONS
.lock().unwrap();
Expand All @@ -451,7 +462,7 @@ fn mock_function(modname: &Ident, attrs: &[Attribute], vis: &Visibility,
* parameters with UnwindSafe
*/
/* std::panic::catch_unwind(|| */
__mockall_guard.call(#(#args),*)
__mockall_guard.call(#(#argnames),*)
/*)*/
}.expect(#no_match_msg)
}
Expand Down Expand Up @@ -593,7 +604,7 @@ fn mock_module(mod_: ItemMod) -> TokenStream {
.collect::<Vec<_>>();
}
).to_tokens(&mut cp_body);
mock_native_function(&modname, &f).to_tokens(&mut body);
mock_native_function(&modname, &f, 0).to_tokens(&mut body);
},
Item::Mod(_) | Item::ForeignMod(_)
| Item::Struct(_) | Item::Enum(_)
Expand Down Expand Up @@ -633,8 +644,10 @@ fn mock_module(mod_: ItemMod) -> TokenStream {

/// Mock a function the same way we mock static trait methods: with a
/// global Expectations object
fn mock_native_function(modname: &Ident, f: &ItemFn) -> TokenStream {
mock_function(modname, &f.attrs, &f.vis, &f.sig)
fn mock_native_function(modname: &Ident, f: &ItemFn, call_levels: i32)
-> TokenStream
{
mock_function(modname, &f.attrs, &f.vis, &f.sig, call_levels)
}

/// Generate a mock struct that implements a trait
Expand Down
7 changes: 5 additions & 2 deletions mockall_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -773,10 +773,13 @@ fn split_lifetimes(
/// # Arguments
/// - `vis`: Original visibility of the item
/// - `levels`: How many modules will the mock item be nested in?
fn expectation_visibility(vis: &Visibility, levels: u32)
fn expectation_visibility(vis: &Visibility, levels: i32)
-> Visibility
{
debug_assert!(levels > 0);
if levels == 0 {
return vis.clone();
}

let in_token = Token![in](vis.span());
let super_token = Token![super](vis.span());
match vis {
Expand Down

0 comments on commit f59ddd9

Please sign in to comment.