use proc_macro::TokenStream;
use quote::quote;
use syn::{
parse::{Parse, ParseStream, Result},
parse_macro_input,
spanned::Spanned,
Block, Error, Expr, Ident, ItemFn, LitStr, Pat, Token,
};
#[proc_macro_attribute]
pub fn test_async(_: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemFn);
let attrs = input.attrs;
let vis = input.vis;
let mut sig = input.sig;
let block = input.block;
sig.asyncness
.take()
.expect("test_async macro can only be used with async functions");
let expanded = quote! {
#[test]
#(#attrs)*
#vis #sig {
futures::executor::block_on(async #block);
}
};
TokenStream::from(expanded)
}
#[proc_macro_attribute]
pub fn test_traced(attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemFn);
let log_level = if attr.is_empty() {
quote! { tracing::Level::DEBUG }
} else {
let level_str = parse_macro_input!(attr as LitStr);
let level_ident = level_str.value().to_uppercase();
match level_ident.as_str() {
"TRACE" => quote! { tracing::Level::TRACE },
"DEBUG" => quote! { tracing::Level::DEBUG },
"INFO" => quote! { tracing::Level::INFO },
"WARN" => quote! { tracing::Level::WARN },
"ERROR" => quote! { tracing::Level::ERROR },
_ => {
return Error::new_spanned(
level_str,
"Invalid log level. Expected one of: TRACE, DEBUG, INFO, WARN, ERROR.",
)
.to_compile_error()
.into();
}
}
};
let attrs = input.attrs;
let vis = input.vis;
let sig = input.sig;
let block = input.block;
let expanded = quote! {
#[test]
#(#attrs)*
#vis #sig {
let subscriber = tracing_subscriber::fmt()
.with_test_writer()
.with_max_level(#log_level)
.with_line_number(true)
.with_span_events(tracing_subscriber::fmt::format::FmtSpan::CLOSE)
.finish();
let dispatcher = tracing::Dispatch::new(subscriber);
tracing::dispatcher::with_default(&dispatcher, || {
#block
});
}
};
TokenStream::from(expanded)
}
struct SelectInput {
branches: Vec<Branch>,
}
struct Branch {
pattern: Pat,
future: Expr,
block: Block,
}
impl Parse for SelectInput {
fn parse(input: ParseStream) -> Result<Self> {
let mut branches = Vec::new();
while !input.is_empty() {
let pattern: Pat = input.parse()?;
input.parse::<Token![=]>()?;
let future: Expr = input.parse()?;
input.parse::<Token![=>]>()?;
let block: Block = input.parse()?;
branches.push(Branch {
pattern,
future,
block,
});
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
} else {
break;
}
}
Ok(SelectInput { branches })
}
}
#[proc_macro]
pub fn select(input: TokenStream) -> TokenStream {
let SelectInput { branches } = parse_macro_input!(input as SelectInput);
let mut stmts = Vec::new();
let mut select_branches = Vec::new();
for (
index,
Branch {
pattern,
future,
block,
},
) in branches.into_iter().enumerate()
{
let future_ident = Ident::new(&format!("__select_future_{}", index), pattern.span());
let stmt = quote! {
let #future_ident = (#future).fuse();
futures::pin_mut!(#future_ident);
};
stmts.push(stmt);
let branch_code = quote! {
#pattern = #future_ident => #block,
};
select_branches.push(branch_code);
}
quote! {
{
use futures::FutureExt as _;
#(#stmts)*
futures::select_biased! {
#(#select_branches)*
}
}
}
.into()
}