extern crate proc_macro; use std::path::Path; use glob::glob; use proc_macro::TokenStream; use quote::quote; use syn::ItemFn; fn get_test_name_for_root(root: &Path, path: &Path) -> String { let test_name = path .strip_prefix(root) .unwrap() .to_str() .unwrap() .replace("/", "_") .replace("\\", "_") .replace(".wat", ""); format!("winch_filetests_{}", test_name) } /// Generate a test case for every .wat file in the filetests directory. /// This should only be used from the filetests crate. #[proc_macro_attribute] pub fn generate_file_tests(_attr: TokenStream, input: TokenStream) -> TokenStream { // Parse the input as a function. let input = proc_macro2::TokenStream::from(input); let fn_ast: ItemFn = syn::parse(input.clone().into()).expect("Failed to parse tokens as function"); // Get the function's name and body. let name = &fn_ast.sig.ident; let filetests_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../filetests/filetests"); let test_file_entries = glob(format!("{}/**/*.wat", filetests_dir.to_str().unwrap()).as_str()) .expect("Failed to read glob pattern"); // Create a list of test cases by opening every .wat file in the directory. let test_cases = test_file_entries.map(|entry| { let path = entry.expect("Failed to read glob entry"); let full = path.to_str().expect("Path for file was empty"); let test_name = proc_macro2::Ident::new( &get_test_name_for_root(&filetests_dir, &path), proc_macro2::Span::call_site(), ); quote! { #[test] fn #test_name() { #name(#full); } } }); // Assemble the output by combining the function and test cases. let output = quote! { #input #(#test_cases)* }; output.into() } #[cfg(test)] mod tests { use super::*; #[test] fn test_get_test_name_for_root_unix() { let root = Path::new("/home/user/Documents/winch/filetests/filetests"); let path = Path::new("/home/user/Documents/winch/filetests/filetests/simd/simple.wat"); let test_name = get_test_name_for_root(root, path); assert_eq!(test_name, "winch_filetests_simd_simple"); } }