|
1 |
| -#![feature(rustc_private)] |
2 |
| -#![feature(proc_macro)] |
| 1 | +///! This implements the kvmargs macro to customize the execution of KVM based tests. |
| 2 | +///! |
| 3 | +///! One problem we have to solve here is that we need to store additional data |
| 4 | +///! to customize the setup of the virtual CPU in the host, whereas the TokenStream |
| 5 | +///! of our macro only allows us to modify the test function itself that already runs inside |
| 6 | +///! the virtual machine (see also `test_harness/src/lib.rs`). |
| 7 | +///! |
| 8 | +///! Using procedural macros, we do the following: |
| 9 | +///! (a) Generate a struct to store additional meta-data for a test |
| 10 | +///! (b) Initialize that struct with values from kvmargs attributes |
| 11 | +///! (b) Store the meta-data in a special ELF section (.kvm) so we can find it later |
| 12 | +///! (c) Make sure the linker won't throw away the meta-data struct by inserting a dummy reference |
| 13 | +///! to it in the test function itself. |
| 14 | +///! |
| 15 | +///! Obviously, this is a bit of a mess right now, my hope is that such things become |
| 16 | +///! easier with better custom test harness support. |
| 17 | +///! |
| 18 | +#![feature(rustc_private, proc_macro)] |
3 | 19 |
|
4 | 20 | extern crate proc_macro;
|
5 | 21 | extern crate syn;
|
6 | 22 | #[macro_use]
|
7 | 23 | extern crate quote;
|
8 | 24 | extern crate syntax;
|
9 | 25 |
|
10 |
| -use proc_macro::TokenStream; |
11 |
| - |
12 |
| -#[proc_macro_attribute] |
13 |
| -pub fn panics_note(args: TokenStream, input: TokenStream) -> TokenStream { |
14 |
| - let args = args.to_string(); |
15 |
| - let mut input = input.to_string(); |
16 |
| - |
17 |
| - assert!(args.starts_with("= \""), |
18 |
| - "`#[panics_note]` requires an argument of the form `#[panics_note = \"panic note \ |
19 |
| - here\"]`"); |
| 26 | +extern crate test; |
| 27 | +use test::KvmTestMetaData; |
20 | 28 |
|
21 |
| - // Get just the bare note string |
22 |
| - let panics_note = args.trim_matches(&['=', ' ', '"'][..]); |
23 |
| - |
24 |
| - // The input will include all docstrings regardless of where the attribute is placed, |
25 |
| - // so we need to find the last index before the start of the item |
26 |
| - let insert_idx = idx_after_last_docstring(&input); |
27 |
| - |
28 |
| - // And insert our `### Panics` note there so it always appears at the end of an item's docs |
29 |
| - input.insert_str(insert_idx, &format!("/// # Panics \n/// {}\n", panics_note)); |
30 |
| - |
31 |
| - input.parse().unwrap() |
32 |
| -} |
33 |
| - |
34 |
| -// `proc-macro` crates can contain any kind of private item still |
35 |
| -fn idx_after_last_docstring(input: &str) -> usize { |
36 |
| - // Skip docstring lines to find the start of the item proper |
37 |
| - input.lines().skip_while(|line| line.trim_left().starts_with("///")).next() |
38 |
| - // Find the index of the first non-docstring line in the input |
39 |
| - // Note: assumes this exact line is unique in the input |
40 |
| - .and_then(|line_after| input.find(line_after)) |
41 |
| - // No docstrings in the input |
42 |
| - .unwrap_or(0) |
43 |
| -} |
44 |
| - |
45 |
| - |
46 |
| -fn generate_kvm_setup(ident: syn::Ident) -> quote::Tokens { |
47 |
| - quote! { |
48 |
| - // The generated impl |
49 |
| - fn #ident() { |
| 29 | +use syn::fold; |
| 30 | +use syn::parse::IResult; |
| 31 | +use std::string; |
| 32 | +use proc_macro::TokenStream; |
| 33 | +use quote::ToTokens; |
| 34 | + |
| 35 | +/// Add a additional meta-data and setup functions for a KVM based test. |
| 36 | +fn generate_kvmtest_meta_data(test_ident: &syn::Ident) -> (syn::Ident, quote::Tokens) { |
| 37 | + // Create some test meta-data |
| 38 | + let test_name = test_ident.as_ref(); |
| 39 | + let setup_fn_ident = syn::Ident::new(String::from(test_name) + "_setup"); |
| 40 | + let struct_ident = syn::Ident::new(String::from(test_name) + "_kvm_meta_data"); |
| 41 | + |
| 42 | + (struct_ident.clone(), |
| 43 | + quote! { |
| 44 | + extern crate test; |
| 45 | + use self::test::KvmTestMetaData; |
| 46 | + #[link_section = ".kvm"] |
| 47 | + #[used] |
| 48 | + static #struct_ident: KvmTestMetaData = KvmTestMetaData { mbz: 0, meta: "test" }; |
| 49 | + |
| 50 | + /// The generated impl |
| 51 | + fn #setup_fn_ident() { |
50 | 52 | log!("blabla");
|
51 | 53 | }
|
52 |
| - } |
| 54 | + }) |
| 55 | +} |
| 56 | + |
| 57 | +/// Inserts a reference to the corresponding KvmTestData struct of a test function |
| 58 | +/// i.e., the test function fn test { foo(); } is changed to |
| 59 | +/// fn test() { assert!(foo_kvm_meta_data.mbz == 0); foo(); } |
| 60 | +/// |
| 61 | +/// This makes sure that the linker won't throw away the symbol to the meta data struct. |
| 62 | +/// An alternative would be to make sure test code is linked with --whole-archive, |
| 63 | +/// but I'm not sure how to do that in rust... |
| 64 | +fn insert_meta_data_reference(struct_ident: &syn::Ident, test_block: &mut syn::Block) { |
| 65 | + let stmt_string = format!("assert!({}.mbz == 0);", struct_ident); |
| 66 | + |
| 67 | + let stmt = match syn::parse::stmt(stmt_string.as_str()) { |
| 68 | + IResult::Done(stmt_str, stmt) => stmt, |
| 69 | + IResult::Error => panic!("Unable to generate reference to meta data"), |
| 70 | + }; |
| 71 | + |
| 72 | + test_block.stmts.insert(0, stmt); |
53 | 73 | }
|
54 | 74 |
|
55 |
| -use std::string; |
56 | 75 |
|
57 | 76 | #[proc_macro_attribute]
|
58 | 77 | pub fn kvmattrs(args: TokenStream, input: TokenStream) -> TokenStream {
|
59 | 78 | let args = args.to_string();
|
60 | 79 | let mut input = input.to_string();
|
| 80 | + let mut ast = syn::parse_item(&input).unwrap(); |
| 81 | + let ident = ast.ident.clone(); |
| 82 | + let (meta_data_ident, new_code) = generate_kvmtest_meta_data(&ident); |
| 83 | + |
| 84 | + { |
| 85 | + match &mut ast.node { |
| 86 | + &mut syn::ItemKind::Fn(_, _, _, _, _, ref mut block) => { |
| 87 | + let mod_test_code = insert_meta_data_reference(&meta_data_ident, block); |
| 88 | + println!("{:#?}", block); |
| 89 | + } |
| 90 | + _ => panic!("Not a function!"), |
| 91 | + }; |
| 92 | + } |
| 93 | + let mut token = quote::Tokens::new(); |
| 94 | + ast.to_tokens(&mut token); |
| 95 | + token.append(new_code); |
61 | 96 |
|
62 |
| - let ast = syn::parse_item(&input).unwrap(); |
63 |
| - let new_fn_ident = syn::Ident::new(String::from(ast.ident.as_ref()) + "_setup"); |
64 |
| - println!("{:?}", new_fn_ident); |
65 |
| - |
66 |
| - // Get just the bare note string |
67 |
| - let panics_note = args.trim_matches(&['=', ' ', '"'][..]); |
68 |
| - let new_code: TokenStream = generate_kvm_setup(new_fn_ident).parse().unwrap(); |
69 |
| - |
70 |
| - input += new_code.to_string().as_str(); |
71 |
| - input.parse().unwrap() |
| 97 | + //input += new_code.to_string().as_str(); |
| 98 | + token.to_string().parse().unwrap() |
72 | 99 | }
|
0 commit comments