Hello!
I'm trying to develop a testing framework in Rust which should have few capabilities to reduce boilerplate:
- Has some necessary stuff pasted at the start of each function
- The user can execute more operations by defining a
fn set_up()
that also get pasted at the start of each function
This is what I wanted to start with, yet it's probably the most challenging one.
I learnt about proc-macro which seemed perfect for that use-case, but I'll explain why they don't work very well (at least with the tried implementation).
tldr; the aim is to be able to write:
fn set_up() {
let val = 2;
}
#[rustry_test]
fn test_works() {
// if annotated with `#[rustry_test]` and that there is a set_up function,
// the content of the `set_up` will be copy/pasted to each rustry_test.
assert_eq!(val + 2, 4);
}
#[rustry_test]
fn test_very_works(x: U256) {
// do more stuff
}
Latest try
I attempted to define a proc_macro_attribute
to seek for a function called set_up
and paste its content at the start of each annotated test function with #[rustry_test]
, but because it needs to call file()!
in order to determine in which file path the attribute has been invoked from, we need to conditionally generate tokens at runtime, which is not something that is supported by Rust. Here is a code so you get an idea:
#[proc_macro_attribute]
pub fn rustry_test(_args: TokenStream, input: TokenStream) -> TokenStream {
let fun = parse_macro_input!(input as ItemFn);
let fname = fun.sig.ident;
let block = fun.block;
let hash_symbol = proc_macro2::Punct::new('#', Spacing::Joint);
let set_up_block = {
quote! {
let filepath = std::path::PathBuf::from(file!());
let code = std::fs::read_to_string(filepath).unwrap();
let syntax = syn::parse_file(&code).unwrap();
let _set_up_block = if let Some(set_up_fn) = syntax.items.into_iter().find(|item| {
if let syn::Item::Fn(_fn) = item {
_fn.sig.ident == "set_up"
} else {
false
}
}) {
match set_up_fn {
syn::Item::Fn(syn::ItemFn { block, .. }) => {
let block: syn::Block = *block;
block.into_token_stream()
},
_ => unreachable!(),
}
} else {
proc_macro2::TokenStream::new()
};
#hash_symbol _set_up_block
}
};
quote! {
#[test]
pub fn #fname() {
#set_up_block
#block
}
}
.into()
}
When annotating a function with the attribute, there is an error
Diagnostics:
expected one of `!` or `[`, found `_set_up_block`
expected one of `!` or `[`
Which may or may not be useful in this context, but again there is no way to expand the macro at runtime.
Is there any way to hack this please ?
Thanks a lot, and take care !