diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1fb9ca8c3d..995894385b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,6 +21,7 @@ out to us in a GitHub issue, or stop by - [Test Expectations and `libclang` Versions](#test-expectations-and-libclang-versions) - [Integration Tests](#integration-tests) - [Fuzzing `bindgen` with `csmith`](#fuzzing-bindgen-with-csmith) + - [Property tests for `bindgen` with `quickchecking`](#property-tests-for-bindgen-with-quickchecking) - [Code Overview](#code-overview) - [Pull Requests and Code Reviews](#pull-requests-and-code-reviews) - [Generating Graphviz Dot Files](#generating-graphviz-dot-files) @@ -224,6 +225,14 @@ uncover hidden bugs is by running `csmith` to generate random headers to test See [./csmith-fuzzing/README.md](./csmith-fuzzing/README.md) for details. +### Property tests for `bindgen` with `quickchecking` + +The `tests/quickchecking` crate generates property tests for `bindgen`. +From the crate's directory you can run the tests with `cargo run`. For details +on additional configuration including how to preserve / inspect the generated +property tests, see +[./tests/quickchecking/README.md](./tests/quickchecking/README.md). + ## Code Overview `bindgen` takes C and C++ header files as input and generates corresponding Rust diff --git a/tests/quickchecking/Cargo.toml b/tests/quickchecking/Cargo.toml index eb5cdfcf58..ddefb33d69 100644 --- a/tests/quickchecking/Cargo.toml +++ b/tests/quickchecking/Cargo.toml @@ -18,3 +18,15 @@ lazy_static = "1.0" quickcheck = "0.4" rand = "0.3" tempdir = "0.3" + +[features] +# No features by default. +default = [] + +# Enable the generation of code that allows for zero sized arrays as struct +# fields. Until issues #684 and #1153 are resolved this can result in failing tests. +zero-sized-arrays = [] + +# Enable the generation of code that allows for long double types as struct +# fields. Until issue #550 is resolved this can result in failing tests. +long-doubles = [] diff --git a/tests/quickchecking/README.md b/tests/quickchecking/README.md new file mode 100644 index 0000000000..d3cfe17071 --- /dev/null +++ b/tests/quickchecking/README.md @@ -0,0 +1,39 @@ +# Property tests for `bindgen` with `quickchecking` + +`quickchecking` generates random C headers to test `bindgen` +using the [`quickcheck`][quickcheck] property testing crate. When testing +`bindgen` with `quickchecking`, the generated header files are passed to +`bindgen`'s `csmith-fuzzing/predicate.py` script. If that script fails, +`quickchecking` panics, and you can report an issue containing the test case! + + + + + +- [Prerequisites](#prerequisites) +- [Running](#running) + + + +## Prerequisites + +Requires `python3` to be in `$PATH`. + +Many systems have `python3` by default but if your OS doesn't, its package +manager may make it available: + +``` +$ sudo apt install python3 +$ brew install python3 +$ # Etc... +``` + +## Running + +Run `quickchecking` binary to generate and test fuzzed C headers with +`cargo run`. Additional configuration is exposed through the binary's CLI. + +``` +$ cargo run --bin=quickchecking -- -h +``` +[quickcheck]: https://github.com/BurntSushi/quickcheck diff --git a/tests/quickchecking/src/bin.rs b/tests/quickchecking/src/bin.rs index 9cf313cdc4..d2774eb0d9 100644 --- a/tests/quickchecking/src/bin.rs +++ b/tests/quickchecking/src/bin.rs @@ -1,5 +1,5 @@ -//! An application to run property tests for `bindgen` with _fuzzed_ C headers -//! using `quickcheck` +//! An application to run property tests for `bindgen` with _fuzzed_ C headers +//! using `quickcheck` //! //! ## Usage //! @@ -77,7 +77,7 @@ fn main() { "Sets the range quickcheck uses during generation. \ Corresponds to things like arbitrary usize and \ arbitrary vector length. This number doesn't have \ - to grow much for that execution time to increase \ + to grow much for execution time to increase \ significantly.", ) .takes_value(true) diff --git a/tests/quickchecking/src/fuzzers.rs b/tests/quickchecking/src/fuzzers.rs index 7549149f54..2876da8628 100644 --- a/tests/quickchecking/src/fuzzers.rs +++ b/tests/quickchecking/src/fuzzers.rs @@ -45,6 +45,8 @@ pub struct BasicTypeDeclarationC { pub type_qualifier: TypeQualifierC, /// The declaration's pointer level, i.e. `***`. pub pointer_level: PointerLevelC, + /// The declaration's array dimension, i.e. [][][]. + pub array_dimension: ArrayDimensionC, /// The declaration's identifier, i.e. ident_N. pub ident_id: String, } @@ -55,6 +57,8 @@ pub struct BasicTypeDeclarationC { pub struct StructDeclarationC { /// The declaration's fields. pub fields: DeclarationListC, + /// The declaration's array dimension, i.e. [][][]. + pub array_dimension: ArrayDimensionC, /// The declaration's identifier, i.e. struct_N. pub ident_id: String, } @@ -65,6 +69,8 @@ pub struct StructDeclarationC { pub struct UnionDeclarationC { /// The declaration's fields. pub fields: DeclarationListC, + /// The declaration's array dimension, i.e. [][][]. + pub array_dimension: ArrayDimensionC, /// The declaration's identifier, i.e. union_N. pub ident_id: String, } @@ -147,7 +153,7 @@ pub struct DeclarationListC { /// HeaderC is used in generation of C headers to represent a collection of /// declarations. -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct HeaderC { /// The header's declarations. pub def: DeclarationListC, @@ -256,7 +262,8 @@ impl Arbitrary for BaseTypeC { "unsigned long long int", "float", "double", - // "long double", + #[cfg(feature = "long-doubles")] + "long double", "void*", ]; BaseTypeC { @@ -315,10 +322,17 @@ impl Arbitrary for ArrayDimensionC { // Keep these small, clang complains when they get too big. let dimensions = g.gen_range(0, 5); let mut def = String::new(); - // Don't allow size 0 dimension until #684 and #1153 are closed. - // 16 is an arbitrary "not too big" number for capping array size. + + let lower_bound; + if cfg!(feature = "zero-sized-arrays") { + lower_bound = 0; + } else { + lower_bound = 1; + } + for _ in 1..dimensions { - def += &format!("[{}]", g.gen_range(1, 16)); + // 16 is an arbitrary "not too big" number for capping array size. + def += &format!("[{}]", g.gen_range(lower_bound, 16)); } ArrayDimensionC { def } } @@ -347,6 +361,7 @@ impl Arbitrary for BasicTypeDeclarationC { type_qualifier: Arbitrary::arbitrary(g), type_name: Arbitrary::arbitrary(g), pointer_level: Arbitrary::arbitrary(g), + array_dimension: Arbitrary::arbitrary(g), ident_id: format!("{}", usize::arbitrary(g)), } } @@ -357,11 +372,12 @@ impl fmt::Display for BasicTypeDeclarationC { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "{} {} {} ident_{};", + "{} {} {} ident_{}{};", self.type_qualifier, self.type_name, self.pointer_level, - self.ident_id + self.ident_id, + self.array_dimension ) } } @@ -398,6 +414,7 @@ impl Arbitrary for StructDeclarationC { StructDeclarationC { fields, ident_id: format!("{}", usize::arbitrary(g)), + array_dimension: Arbitrary::arbitrary(g), } } } @@ -405,7 +422,13 @@ impl Arbitrary for StructDeclarationC { /// Enables to string and format for StructDeclarationC types. impl fmt::Display for StructDeclarationC { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "struct {{ {} }} struct_{};", self.fields, self.ident_id) + write!( + f, + "struct {{ {} }} struct_{}{};", + self.fields, + self.ident_id, + self.array_dimension + ) } } @@ -441,6 +464,7 @@ impl Arbitrary for UnionDeclarationC { UnionDeclarationC { fields, ident_id: format!("{}", usize::arbitrary(g)), + array_dimension: Arbitrary::arbitrary(g), } } } @@ -448,7 +472,13 @@ impl Arbitrary for UnionDeclarationC { /// Enables to string and format for UnionDeclarationC types. impl fmt::Display for UnionDeclarationC { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "union {{ {} }} union_{};", self.fields, self.ident_id) + write!( + f, + "union {{ {} }} union_{}{};", + self.fields, + self.ident_id, + self.array_dimension + ) } } @@ -597,3 +627,11 @@ impl fmt::Display for HeaderC { write!(f, "{}", display) } } + +/// Use Display trait for Debug so that any failing property tests report +/// generated C code rather than the data structures that contain it. +impl fmt::Debug for HeaderC { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self) + } +}