Skip to content

Commit 22c0708

Browse files
committed
Generate an additional meta-data struct for every test.
Signed-off-by: Gerd Zellweger <[email protected]>
1 parent be162ed commit 22c0708

File tree

4 files changed

+90
-58
lines changed

4 files changed

+90
-58
lines changed

test_harness/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
#![feature(used, lang_items)]
22

3+
#[derive(Debug)]
4+
pub struct KvmTestMetaData {
5+
pub mbz: u64,
6+
pub meta: &'static str,
7+
}
8+
39
pub fn test_start(ntests: usize) {
410
println!("KVM testing: running {} tests", ntests)
511
}

test_macros/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ authors = ["Gerd Zellweger <[email protected]>"]
55

66
[dependencies]
77
quote = "0.3.*"
8+
test = { path = "../test_harness" }
89

910
[dependencies.syn]
10-
version = "0.11.*"
11-
features = ["full"]
11+
version = "0.*"
12+
features = ["full", "fold"]
1213

1314
[lib]
1415
proc-macro = true

test_macros/src/lib.rs

Lines changed: 80 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,99 @@
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)]
319

420
extern crate proc_macro;
521
extern crate syn;
622
#[macro_use]
723
extern crate quote;
824
extern crate syntax;
925

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;
2028

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() {
5052
log!("blabla");
5153
}
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);
5373
}
5474

55-
use std::string;
5675

5776
#[proc_macro_attribute]
5877
pub fn kvmattrs(args: TokenStream, input: TokenStream) -> TokenStream {
5978
let args = args.to_string();
6079
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);
6196

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()
7299
}

tests/kvm/bin.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![feature(linkage, naked_functions, asm, const_fn, test, proc_macro)]
1+
#![feature(linkage, naked_functions, asm, const_fn, proc_macro, used)]
22
// Execute using: RUSTFLAGS="-C relocation-model=dynamic-no-pic -C code-model=kernel" RUST_BACKTRACE=1 cargo test --verbose --test kvm -- --nocapture
33

44
extern crate kvm;
@@ -10,7 +10,6 @@ extern crate core;
1010
extern crate klogger;
1111

1212
extern crate test_macros;
13-
use test_macros::panics_note;
1413
use test_macros::kvmattrs;
1514

1615
use kvm::{Capability, Exit, IoDirection, Segment, System, Vcpu, VirtualMachine};
@@ -24,7 +23,6 @@ use x86::bits64::paging::*;
2423

2524
#[kvmattrs(identity_map)]
2625
fn use_the_port() {
27-
use_the_port_setup();
2826
log!("1");
2927
unsafe {
3028
asm!("inb $0, %al" :: "i"(0x01) :: "volatile");

0 commit comments

Comments
 (0)