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

Fix some visibility issues with mocking modules and foreign functions: #133

Merged
merged 1 commit into from
May 24, 2020
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
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