1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use std::num::NonZeroUsize;
/// Marks a function to be run as a test in a checkers test suite.
///
/// # Attributes
///
/// The `test` macro has the following attributes:
/// * `capacity` - Reserve capacity for the specified number of events
/// beforehand. Checkers will otherwise grow it as necessary using the system
/// allocator directly.
/// * `verify` - Use a custom verification function (see below).
///
/// # Examples
///
/// ```rust
/// #[global_allocator]
/// static ALLOCATOR: checkers::Allocator = checkers::Allocator::system();
///
/// #[checkers::test]
/// fn test_leaky_box() {
/// let _ = Box::into_raw(Box::new(42));
/// }
/// ```
///
/// Reserve capacity for the specified number of events up front:
///
/// ```rust
/// #[global_allocator]
/// static ALLOCATOR: checkers::Allocator = checkers::Allocator::system();
///
/// #[checkers::test(capacity = 10_000)]
/// fn test_custom_verify() {
/// for i in 0..1000 {
/// let v = Box::into_raw(vec![1, 2, 3, 4, 5].into_boxed_slice());
/// let _ = unsafe { Box::from_raw(v) };
/// }
/// }
/// ```
///
/// Using a custom verifier:
///
/// ```rust
/// #[global_allocator]
/// static ALLOCATOR: checkers::Allocator = checkers::Allocator::system();
///
/// fn verify_test_custom_verify(state: &mut checkers::State) {
/// let mut violations = Vec::new();
/// state.validate(&mut violations);
/// assert_eq!(1, violations.len());
/// assert!(violations[0].is_leaked_with(|region| region.size == 20 && region.align == 4));
/// }
///
/// #[checkers::test(verify = "verify_test_custom_verify")]
/// fn test_custom_verify() {
/// let _ = Box::into_raw(vec![1, 2, 3, 4, 5].into_boxed_slice());
/// }
/// ```
#[proc_macro_attribute]
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as syn::ItemFn);
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
let ret = &input.sig.output;
let name = &input.sig.ident;
let body = &input.block;
let attrs = &input.attrs;
let vis = input.vis;
for attr in attrs {
if attr.path.is_ident("test") {
let msg = "second test attribute is supplied";
return syn::Error::new_spanned(&attr, msg)
.to_compile_error()
.into();
}
}
if !input.sig.inputs.is_empty() {
let msg = "the test function cannot accept arguments";
return syn::Error::new_spanned(&input.sig.inputs, msg)
.to_compile_error()
.into();
}
let mut capacity = NonZeroUsize::new(1024).unwrap();
let mut verify = None::<syn::Ident>;
for arg in args {
if let syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) = arg {
let ident = namevalue.path.get_ident();
if ident.is_none() {
let msg = "Must have specified ident";
return syn::Error::new_spanned(namevalue.path, msg)
.to_compile_error()
.into();
}
match ident.unwrap().to_string().to_lowercase().as_str() {
"capacity" => match &namevalue.lit {
syn::Lit::Int(expr) => {
capacity = match expr.base10_parse::<NonZeroUsize>() {
Ok(n) => n,
_ => {
return syn::Error::new_spanned(
expr,
"capacity argument is not valid",
)
.to_compile_error()
.into();
}
}
}
_ => {
return syn::Error::new_spanned(
namevalue,
"capacity argument must be an int",
)
.to_compile_error()
.into();
}
},
"verify" => match &namevalue.lit {
syn::Lit::Str(expr) => {
verify = Some(match expr.parse::<syn::Ident>() {
Ok(ident) => ident,
Err(..) => {
return syn::Error::new_spanned(
expr,
"verify argument is not valid",
)
.to_compile_error()
.into();
}
});
}
_ => {
return syn::Error::new_spanned(
namevalue,
"verify argument must be a string",
)
.to_compile_error()
.into();
}
},
name => {
let msg = format!("Unknown attribute {} is specified", name);
return syn::Error::new_spanned(namevalue.path, msg)
.to_compile_error()
.into();
}
}
}
}
let capacity = capacity.get();
let verify = match verify {
Some(verify) => {
quote! {
#verify(state);
}
}
None => quote! {
checkers::verify!(state);
},
};
let result = quote! {
#[test]
#(#attrs)*
#vis fn #name() #ret {
checkers::with_state(|s| {
{
let mut s = s.borrow_mut();
s.clear();
s.reserve(#capacity);
}
checkers::with_unmuted(|| #body);
let state = &mut *s.borrow_mut();
#verify
});
}
};
result.into()
}