//! This implements the x86test macro to run and also customize the execution //! of KVM based unit-tests. #![feature(proc_macro_diagnostic)] extern crate proc_macro; use proc_macro::TokenStream; use proc_macro2::Span; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::token::Comma; use syn::{AttributeArgs, Ident, ItemFn, Lit, Meta, MetaList, NestedMeta}; use quote::quote; /// Parse two integer literals from args (e.g., like (1, 2)). fn parse_two_ints(args: Punctuated) -> (u64, u64) { if args.len() != 2 { args.span() .unstable() .error("needs two numbers as parameters") .emit(); } let a = if let NestedMeta::Literal(Lit::Int(first)) = &args[0] { first.value() } else { args[0] .span() .unstable() .error("first parameter not an int literal") .emit(); 0 }; let b = if let NestedMeta::Literal(Lit::Int(second)) = &args[1] { second.value() } else { args[1] .span() .unstable() .error("second parameter not an int literal") .emit(); 0 }; (a, b) } /// Checks is ItemFn is annotated with #[should_panic] fn should_panic(fun: &ItemFn) -> bool { fun.attrs .iter() .find(|&attr| { attr.path .segments .iter() .find(|&path_segment| path_segment.ident == "should_panic") .is_some() }) .is_some() } /// The `x86test` macro adds and initializes a `X86TestFn` struct for /// every test function. That `X86TestFn` in turn is annotated with /// `#[test_case]` therefore all these structs are aggregated with /// by the custom test framework runner which is declared in `runner.rs`. /// /// # Example /// As an example, if we add x86test to a function, we do the following: /// /// ```no-run /// #[x86test(ram(0x10000, 0x11000), ioport(0x1, 0xfe), should_panic)] /// fn use_the_port() { /// unsafe { /// kassert!(x86::io::inw(0x1) == 0xff, "`inw` instruction didn't read correct value"); /// } /// } /// ``` /// /// Will expand to: /// /// ```no-run /// fn use_the_port() { /// unsafe { /// kassert!(x86::io::inw(0x1) == 0xff, "`inw` instruction didn't read correct value"); /// } /// } /// /// #[allow(non_upper_case_globals)] /// #[test_case] /// static use_the_port_genkvmtest: X86TestFn = X86TestFn { /// name: "use_the_port", /// ignore: false, /// identity_map: true, /// physical_memory: (0x10000, 0x11000), /// ioport_enable: (0x1, 0xfe), /// should_panic: true, /// testfn: x86test::StaticTestFn(|| { /// use_the_port() /// // Tell our "hypervisor" that we finished the test /// unsafe { x86::io::outw(0xf4, 0x00); } /// }) /// }; /// ``` #[proc_macro_attribute] pub fn x86test(args: TokenStream, input: TokenStream) -> TokenStream { let args: Vec = syn::parse_macro_input!(args as AttributeArgs); let input_fn = syn::parse_macro_input!(input as ItemFn); let mut physical_memory: (u64, u64) = (0, 0); let mut ioport_enable: (u64, u64) = (0, 0); let should_panic = should_panic(&input_fn); let mut should_halt = false; // Parse the arguments of x86test: // #[x86test(ram(0xdead, 12), ioport(0x1, 0xfe))] // will push (0xdead, 12) to physical_memory and (0x1, 0xfe) to ioport_enable: for arg in args { //println!("arg {:#?}", arg); if let NestedMeta::Meta(Meta::List(MetaList { ident, paren_token: _, nested, })) = arg { match ident.to_string().as_str() { "ram" => { physical_memory = parse_two_ints(nested); } "ioport" => { ioport_enable = parse_two_ints(nested); } x => unreachable!("unsupported attribute: {}", x), } } else if let NestedMeta::Meta(Meta::Word(ident)) = arg { match ident.to_string().as_str() { "should_halt" => should_halt = true, x => unreachable!("unsupported attribute: {}", x), } } } let physical_memory_tuple = { let (a, b) = physical_memory; quote! { (#a, #b) } }; let ioport_enable_tuple = { let (a, b) = ioport_enable; quote! { (#a as u16, #b as u32) } }; let struct_name = format!("{}_genkvmtest", input_fn.ident); let struct_ident = Ident::new(struct_name.as_str(), Span::call_site()); let test_name = format!("{}", input_fn.ident); let fn_ident = input_fn.ident.clone(); let ast = quote! { #[allow(non_upper_case_globals, unused_attributes)] #[test_case] static #struct_ident: X86TestFn = X86TestFn { name: #test_name, ignore: false, identity_map: true, physical_memory: #physical_memory_tuple, ioport_enable: #ioport_enable_tuple, should_panic: #should_panic, should_halt: #should_halt, testfn: x86test::StaticTestFn(|| { #fn_ident(); // Tell our "hypervisor" that we finished the test unsafe { x86test::outw(0xf4, 0x00); } }) }; // Suppress unused attribute #[should_panic] warning (XXX: there is probably a better way to do this) #[allow(unused_attributes)] #input_fn }; ast.into() }