ffi_mock_macro/
lib.rs

1extern crate proc_macro;
2use proc_macro::TokenStream;
3use quote::{format_ident, quote};
4use syn::{
5    parse::Parse,
6    parse_macro_input, parse_str,
7    punctuated::Punctuated,
8    token::{Comma, Default},
9    Expr, FnArg, Ident, Signature, Token, Type, TypeParen,
10};
11
12fn get_names(in_sig: &Punctuated<FnArg, Comma>) -> Punctuated<Ident, Comma> {
13    let mut res = Punctuated::new();
14
15    in_sig
16        .iter()
17        .map(|f| match f {
18            FnArg::Typed(t) => match *t.pat.clone() {
19                syn::Pat::Ident(id) => id.ident,
20                _ => unimplemented!(),
21            },
22            FnArg::Receiver(_) => unimplemented!(),
23        })
24        .for_each(|f| res.push(f));
25
26    res
27}
28
29fn get_types(in_sig: &Punctuated<FnArg, Comma>) -> Punctuated<Type, Comma> {
30    let mut res = Punctuated::new();
31
32    in_sig
33        .iter()
34        .map(|f| match f {
35            FnArg::Typed(t) => *t.ty.clone(),
36            FnArg::Receiver(_) => unimplemented!(),
37        })
38        .for_each(|f| res.push(f));
39
40    res
41}
42
43#[proc_macro]
44pub fn mock(input: TokenStream) -> TokenStream {
45    let func = parse_macro_input!(input as Signature);
46    let static_mock_name = format_ident!(
47        "STATIC_MOCK_{}",
48        func.ident.to_string().to_ascii_uppercase()
49    );
50    let extern_name = func.ident;
51    let in_types = get_types(&func.inputs);
52    let in_names = get_names(&func.inputs);
53    let in_sig = func.inputs;
54    let out_sig: Type = match func.output {
55        syn::ReturnType::Default => parse_str("()").unwrap(),
56        syn::ReturnType::Type(_, t) => *t,
57    };
58
59    quote!(
60        {
61
62            ffi_mock::lazy_static! {
63                static ref #static_mock_name: std::sync::Mutex<ffi_mock::FunctionMockInner<(#in_types), #out_sig>> =
64                    std::sync::Mutex::new(ffi_mock::FunctionMockInner::new());
65            }
66
67            #[no_mangle]
68            extern "C" fn #extern_name(#in_sig) -> #out_sig {
69                let mut ffi_mock_mutex = #static_mock_name.lock().unwrap();
70                ffi_mock_mutex.call_history.push( (#in_names) );
71                ffi_mock_mutex.get_next_return()
72            }
73            ffi_mock::FunctionMock::new(&#static_mock_name)
74        }
75    )
76    .into()
77}