#![deny(missing_docs)]
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as Tokens;
use quote::quote;
use syn::parse_macro_input;
use syn::parse_quote;
use syn::punctuated::Punctuated;
use syn::AttributeArgs;
use syn::FnArg;
use syn::ItemFn;
use syn::Meta;
use syn::NestedMeta;
use syn::ReturnType;
use syn::Token;
#[proc_macro_attribute]
pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as AttributeArgs);
let input = parse_macro_input!(item as ItemFn);
let inner_test = match args.as_slice() {
[] => parse_quote! { ::core::prelude::v1::test },
[NestedMeta::Meta(Meta::Path(path))] => quote! { #path },
[NestedMeta::Meta(Meta::List(list))] => quote! { #list },
_ => panic!("unsupported attributes supplied: {:?}", args),
};
expand_wrapper(&inner_test, &input)
}
fn expand_logging_init() -> Tokens {
#[cfg(feature = "log")]
quote! {
{
let _ = ::env_logger::builder().is_test(true).try_init();
}
}
#[cfg(not(feature = "log"))]
quote! {}
}
fn expand_tracing_init() -> Tokens {
#[cfg(feature = "trace")]
quote! {
{
let __internal_event_filter = {
use ::tracing_subscriber::fmt::format::FmtSpan;
match ::std::env::var("RUST_LOG_SPAN_EVENTS") {
Ok(value) => {
value
.to_ascii_lowercase()
.split(",")
.map(|filter| match filter.trim() {
"new" => FmtSpan::NEW,
"enter" => FmtSpan::ENTER,
"exit" => FmtSpan::EXIT,
"close" => FmtSpan::CLOSE,
"active" => FmtSpan::ACTIVE,
"full" => FmtSpan::FULL,
_ => panic!("test-log: RUST_LOG_SPAN_EVENTS must contain filters separated by `,`.\n\t\
For example: `active` or `new,close`\n\t\
Supported filters: new, enter, exit, close, active, full\n\t\
Got: {}", value),
})
.fold(FmtSpan::NONE, |acc, filter| filter | acc)
},
Err(::std::env::VarError::NotUnicode(_)) =>
panic!("test-log: RUST_LOG_SPAN_EVENTS must contain a valid UTF-8 string"),
Err(::std::env::VarError::NotPresent) => FmtSpan::NONE,
}
};
let subscriber = ::tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(::tracing_subscriber::EnvFilter::from_default_env())
.with_span_events(__internal_event_filter)
.with_test_writer()
.finish();
let _ = ::tracing::subscriber::set_global_default(subscriber);
}
}
#[cfg(not(feature = "trace"))]
quote! {}
}
fn extract_args(inputs: &Punctuated<FnArg, Token![,]>) -> Punctuated<Tokens, Token![,]> {
inputs
.iter()
.map(|arg| match arg {
FnArg::Receiver(receiver) => {
let slf = receiver.self_token;
quote! { #slf }
},
FnArg::Typed(typed) => {
let pat = &typed.pat;
quote! { #pat }
},
})
.collect()
}
fn expand_wrapper(inner_test: &Tokens, wrappee: &ItemFn) -> TokenStream {
let attrs = &wrappee.attrs;
let async_ = &wrappee.sig.asyncness;
let await_ = if async_.is_some() {
quote! {.await}
} else {
quote! {}
};
let body = &wrappee.block;
let test_name = &wrappee.sig.ident;
let inputs = &wrappee.sig.inputs;
let args = extract_args(inputs);
let ret = match &wrappee.sig.output {
ReturnType::Default => quote! {},
ReturnType::Type(_, type_) => quote! {-> #type_},
};
let logging_init = expand_logging_init();
let tracing_init = expand_tracing_init();
let result = quote! {
#[#inner_test]
#(#attrs)*
#async_ fn #test_name(#inputs) #ret {
#async_ fn test_impl(#inputs) #ret {
#body
}
mod init {
pub fn init() {
#logging_init
#tracing_init
}
}
init::init();
test_impl(#args)#await_
}
};
result.into()
}