extern crate proc_macro;
extern crate syn;
use proc_macro::TokenStream;
use quote::quote;
use syn::parse_macro_input;
#[proc_macro_attribute]
pub fn timeout(attr: TokenStream, item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as syn::ItemFn);
let time_ms = get_timeout(&parse_macro_input!(attr as syn::AttributeArgs));
let vis = &input.vis;
let sig = &input.sig;
let output = &sig.output;
let body = &input.block;
check_other_attributes(&input);
let result = quote! {
#vis #sig {
fn ntest_callback() #output
#body
let ntest_timeout_now = std::time::Instant::now();
let (sender, receiver) = std::sync::mpsc::channel();
std::thread::spawn(move || {
if let std::result::Result::Ok(()) = sender.send(ntest_callback()) {}
});
match receiver.recv_timeout(std::time::Duration::from_millis(#time_ms)) {
std::result::Result::Ok(t) => return t,
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => panic!("timeout: the function call took {} ms. Max time {} ms", ntest_timeout_now.elapsed().as_millis(), #time_ms),
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => panic!(),
}
}
};
result.into()
}
fn check_other_attributes(input: &syn::ItemFn) {
for attribute in &input.attrs {
let meta = attribute.parse_meta();
match meta {
std::result::Result::Ok(m) => match m {
syn::Meta::Path(p) => {
if p.segments.iter().any(|ps| ps.ident == "timeout") {
panic!("Timeout attribute is only allowed once");
}
}
syn::Meta::List(ml) => {
if ml.path.segments.iter().any(|ps| ps.ident == "timeout") {
panic!("Timeout attribute is only allowed once");
}
}
syn::Meta::NameValue(nv) => {
if nv.path.segments.iter().any(|ps| ps.ident == "timeout") {
panic!("Timeout attribute is only allowed once");
}
}
},
Err(e) => panic!("Could not determine meta data. Error {}.", e),
}
}
}
fn get_timeout(attribute_args: &syn::AttributeArgs) -> u64 {
if attribute_args.len() > 1 {
panic!("Only one integer expected. Example: #[timeout(10)]");
}
match &attribute_args[0] {
syn::NestedMeta::Meta(_) => {
panic!("Integer expected. Example: #[timeout(10)]");
}
syn::NestedMeta::Lit(lit) => match lit {
syn::Lit::Int(int) => int.base10_parse::<u64>().expect("Integer expected"),
_ => {
panic!("Integer as timeout in ms expected. Example: #[timeout(10)]");
}
},
}
}