Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

macros: add test macro #64

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion bolero-engine/src/target_location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ pub struct TargetLocation {

/// The name of the test
pub test_name: Option<String>,

/// Set to true if the location is harnessed by libtest
pub is_harnessed: Option<bool>,
}

impl TargetLocation {
Expand Down Expand Up @@ -101,7 +104,8 @@ impl TargetLocation {
}

pub fn is_harnessed(&self) -> bool {
is_harnessed(self.item_path)
self.is_harnessed
.unwrap_or_else(|| is_harnessed(self.item_path))
}

fn fuzz_dir(&self) -> String {
Expand Down
19 changes: 19 additions & 0 deletions bolero-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "bolero-macros"
version = "0.6.2"
authors = ["Cameron Bytheway <[email protected]>"]
description = "macro support for bolero"
homepage = "https://github.com/camshaft/bolero"
repository = "https://github.com/camshaft/bolero"
keywords = ["testing", "quickcheck", "property", "fuzz", "fuzzing"]
license = "MIT"
edition = "2018"
readme = "../bolero/README.md"

[lib]
proc-macro = true

[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = "1.0"
118 changes: 118 additions & 0 deletions bolero-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use proc_macro::TokenStream;
use proc_macro2::{Ident, TokenStream as TokenStream2};
use quote::quote_spanned;
use syn::{
parse_macro_input, spanned::Spanned as _, token::Colon, AttributeArgs, ItemFn, Visibility,
};

#[proc_macro_attribute]
pub fn test(config: TokenStream, body: TokenStream) -> TokenStream {
let config = parse_macro_input!(config as AttributeArgs);
let body = parse_macro_input!(body as ItemFn);

compile(config, body).into()
}

fn compile(_config: AttributeArgs, mut body: ItemFn) -> TokenStream2 {
// TODO config

let inner = Ident::new("inner", body.sig.ident.span());
let vis = core::mem::replace(&mut body.vis, Visibility::Inherited);
let name = core::mem::replace(&mut body.sig.ident, inner.clone());
let test_name = name.to_string();
let span = body.span();
let mut attrs = core::mem::take(&mut body.attrs);

let is_harnessed = test_name != "main";
if is_harnessed {
attrs.push(syn::parse_quote!(#[cfg_attr(not(rmc), test)]));
}

let mut args = vec![];
let mut fields = vec![];

for (idx, input) in body.sig.inputs.iter_mut().enumerate() {
match input {
syn::FnArg::Typed(arg) => {
let span = arg.span();
fields.push(syn::Field {
attrs: core::mem::take(&mut arg.attrs),
vis: Visibility::Inherited,
ident: None,
colon_token: Some(Colon(span)),
ty: *arg.ty.clone(),
});
}
_ => continue,
}

args.push(Ident::new(&format!("input_{}", idx), span));
}

if args.is_empty() {
todo!("register test in linker");
}

quote_spanned! {span=>
#(#attrs)*
// when compiling with rmc, export the full path
#[cfg_attr(rmc, export_name = concat!(module_path!(), "::", #test_name))]
#vis fn #name() {
#[allow(unused_imports)]
use bolero::{TargetLocation, generator::*};

#body

#[allow(non_upper_case_globals)]
const __BOLERO__ITEM_PATH: &str = concat!(module_path!(), "::", #test_name);
#[allow(non_upper_case_globals)]
const __BOLERO__ITEM_PATH_REG_LEN: usize = __BOLERO__ITEM_PATH.len() + 4;

#[cfg(not(rmc))]
#[used]
#[cfg_attr(
any(target_os = "linux", target_os = "android"),
link_section = ".note.bolero"
)]
#[cfg_attr(target_os = "freebsd", link_section = ".note.bolero")]
#[cfg_attr(
any(target_os = "macos", target_os = "ios"),
link_section = "__DATA,__bolero"
)]
#[cfg_attr(windows, link_section = ".debug_bolero")]
#[allow(non_upper_case_globals)]
static __BOLERO__ITEM_PATH_REG: [u8; __BOLERO__ITEM_PATH_REG_LEN] = {
let mut bytes = [0u8; __BOLERO__ITEM_PATH_REG_LEN];
let len_bytes = (__BOLERO__ITEM_PATH.len() as u32).to_be_bytes();
bytes[0] = len_bytes[0];
bytes[1] = len_bytes[1];
bytes[2] = len_bytes[2];
bytes[3] = len_bytes[3];

let mut idx = 4;
while idx < __BOLERO__ITEM_PATH.len() {
bytes[idx] = __BOLERO__ITEM_PATH.as_bytes()[idx - 4];
idx += 1;
}

bytes
};

let location = TargetLocation {
package_name: env!("CARGO_PKG_NAME"),
manifest_dir: env!("CARGO_MANIFEST_DIR"),
module_path: module_path!(),
file: file!(),
line: line!(),
item_path: __BOLERO__ITEM_PATH,
test_name: Some(String::from(#test_name)),
is_harnessed: Some(#is_harnessed),
};

// TODO enable config
bolero::test(location).with_type().for_each(|(#(#args,)*)| {
#inner(#(#args,)*)
});
}
}
}
1 change: 1 addition & 0 deletions bolero/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ alloc = ["bolero-generator/alloc"]
[dependencies]
bolero-engine = { version = "0.6", path = "../bolero-engine" }
bolero-generator = { version = "0.6", path = "../bolero-generator", default-features = false }
bolero-macros = { version = "0.6", path = "../bolero-macros" }
cfg-if = "1"

[target.'cfg(fuzzing_afl)'.dependencies]
Expand Down
77 changes: 40 additions & 37 deletions bolero/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub mod generator {
pub use bolero_engine::{self, TargetLocation, __item_path__};

pub use bolero_engine::{rng::RngEngine, Driver, DriverMode, Engine, Test};
pub use bolero_macros::test;

/// Execute tests for a given target
///
Expand Down Expand Up @@ -115,6 +116,7 @@ macro_rules! check {
line: line!(),
item_path: $crate::__item_path__!(),
test_name: None,
is_harnessed: None,
};

if !location.should_run() {
Expand All @@ -141,6 +143,7 @@ macro_rules! check {
line: line!(),
item_path: $crate::__item_path__!(),
test_name: Some(format!("{}", $target_name)),
is_harnessed: None,
};

if !location.should_run() {
Expand Down Expand Up @@ -439,50 +442,50 @@ impl<E> TestTarget<ByteSliceGenerator, E, ClonedInput> {
}
}

#[test]
#[should_panic]
fn slice_generator_test() {
check!().for_each(|input| {
assert!(input.len() > 1000);
});
}
#[cfg(test)]
mod tests {
use super::check;

#[test]
#[should_panic]
fn type_generator_test() {
check!().with_type().for_each(|input: &u8| {
assert!(input < &128);
});
}
#[test]
#[should_panic]
fn slice_generator_test() {
check!().for_each(|input| {
assert!(input.len() > 1000);
});
}

#[test]
#[should_panic]
fn type_generator_cloned_test() {
check!().with_type().cloned().for_each(|input: u8| {
assert!(input < 128);
});
}
#[test]
#[should_panic]
fn type_generator_test() {
check!().with_type().for_each(|input: &u8| {
assert!(input < &128);
});
}

#[test]
fn range_generator_test() {
check!().with_generator(0..=5).for_each(|_input: &u8| {
// println!("{:?}", input);
});
}
#[test]
#[should_panic]
fn type_generator_cloned_test() {
check!().with_type().cloned().for_each(|input: u8| {
assert!(input < 128);
});
}

#[test]
fn range_generator_cloned_test() {
check!()
.with_generator(0..=5)
.cloned()
.for_each(|_input: u8| {
#[test]
fn range_generator_test() {
check!().with_generator(0..=5).for_each(|_input: &u8| {
// println!("{:?}", input);
});
}
}

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn range_generator_cloned_test() {
check!()
.with_generator(0..=5)
.cloned()
.for_each(|_input: u8| {
// println!("{:?}", input);
});
}

#[test]
fn nested_test() {
Expand Down
7 changes: 7 additions & 0 deletions examples/basic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ mod tests {
});
}

#[bolero::test]
fn add_macro_test(a: &u8, b: &u8) {
let value = a.saturating_add(*b);
assert!(value >= *a);
assert!(value >= *b);
}

#[test]
fn other_test() {
let should_panic = std::env::var("SHOULD_PANIC").is_ok();
Expand Down