Skip to content

Commit a59205e

Browse files
author
bors-servo
authored
Auto merge of #1177 - snewt:feat/quickcheck-as-bin, r=fitzgen
Quickchecking crate CLI Prior to this commit the quickchecking crate used for generating proprty tests for bindgen was a [lib] target and had configurations that required commenting/uncommenting code to enable/disable. This meant it was inconvienent/prohibitive to configure the property tests on a per-run basis. This commit reorganizes the `quickchecking` crate to provide both [lib] and [[bin]] targets in order to expose those configurations through a CLI. The configurations that are exposed through the [[bin]] target's CLI are: * Count/number of tests to run. * Directory to provide fuzzed headers * Generation range corresponding to the range quickcheck uses to * generate arbitrary. __Usage from the__ `tests/quickchecking` __directory__ ```bash quickchecking 0.2.0 Bindgen property tests with quickcheck. Generate random valid C code and pass it to the csmith/predicate.py script USAGE: quickchecking [OPTIONS] FLAGS: -h, --help Prints help information -V, --version Prints version information OPTIONS: -c, --count <COUNT> Count / number of tests to run. Running a fuzzed header through the predicate.py script can take a long time, especially if the generation range is large. Increase this number if you're willing to wait a while. [default: 2] -p, --path <PATH> Optional. Preserve generated headers for inspection, provide directory path for header output. [default: None] -r, --range <RANGE> 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 significantly. [default: 32] ``` Because the actual work of running the property tests moved to the [[bin]] target, rather than duplicate that code in the `quickchecking` crate's tests directory, some actual (very basic) tests for the `quickchecking` crate were added. *Note: I'm not attached to any of the option flags, if there are better characters/words for any of the options I've exposed I'll be happy to revise! Also, I'm not sure how palatable the "global singleton" is for managing context (output path) across tests in the `lib.rs` file. Very open to suggestions on how to manage that if it's not an acceptable approach. Thanks for taking a look, looking forward to feedback! Closes #1168 r? @fitzgen
2 parents a48ca25 + 2c77652 commit a59205e

File tree

5 files changed

+305
-70
lines changed

5 files changed

+305
-70
lines changed

ci/script.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ case "$BINDGEN_JOB" in
4141
"quickchecking")
4242
cd ./tests/quickchecking
4343
# TODO: Actually run quickchecks once `bindgen` is reliable enough.
44-
cargo check
44+
cargo test
4545
;;
4646
*)
4747
echo "Error! Unknown \$BINDGEN_JOB: '$BINDGEN_JOB'"

tests/quickchecking/Cargo.toml

+12-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,19 @@
22
name = "quickchecking"
33
description = "Bindgen property tests with quickcheck. Generate random valid C code and pass it to the csmith/predicate.py script"
44
version = "0.1.0"
5-
authors = ["Shea Newton <[email protected]>"]
5+
authors = ["Shea Newton <[email protected]>"]
6+
7+
[lib]
8+
name = "quickchecking"
9+
path = "src/lib.rs"
10+
11+
[[bin]]
12+
name = "quickchecking"
13+
path = "src/bin.rs"
614

715
[dependencies]
16+
clap = "2.28"
17+
lazy_static = "1.0"
818
quickcheck = "0.4"
9-
tempdir = "0.3"
1019
rand = "0.3"
20+
tempdir = "0.3"

tests/quickchecking/src/bin.rs

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//! An application to run property tests for `bindgen` with _fuzzed_ C headers
2+
//! using `quickcheck`
3+
//!
4+
//! ## Usage
5+
//!
6+
//! Print help
7+
//! ```bash
8+
//! $ cargo run --bin=quickchecking -- -h
9+
//! ```
10+
//!
11+
//! Run with default values
12+
//! ```bash
13+
//! $ cargo run --bin=quickchecking
14+
//! ```
15+
//!
16+
#![deny(missing_docs)]
17+
extern crate clap;
18+
extern crate quickchecking;
19+
20+
use clap::{App, Arg};
21+
use std::path::Path;
22+
23+
// Validate CLI argument input for generation range.
24+
fn validate_generate_range(v: String) -> Result<(), String> {
25+
match v.parse::<usize>() {
26+
Ok(_) => Ok(()),
27+
Err(_) => Err(String::from(
28+
"Generate range could not be converted to a usize.",
29+
)),
30+
}
31+
}
32+
33+
// Validate CLI argument input for tests count.
34+
fn validate_tests_count(v: String) -> Result<(), String> {
35+
match v.parse::<usize>() {
36+
Ok(_) => Ok(()),
37+
Err(_) => Err(String::from(
38+
"Tests count could not be converted to a usize.",
39+
)),
40+
}
41+
}
42+
43+
// Validate CLI argument input for fuzzed headers output path.
44+
fn validate_path(v: String) -> Result<(), String> {
45+
match Path::new(&v).is_dir() {
46+
true => Ok(()),
47+
false => Err(String::from("Provided directory path does not exist.")),
48+
}
49+
}
50+
51+
fn main() {
52+
let matches = App::new("quickchecking")
53+
.version("0.2.0")
54+
.about(
55+
"Bindgen property tests with quickcheck. \
56+
Generate random valid C code and pass it to the \
57+
csmith/predicate.py script",
58+
)
59+
.arg(
60+
Arg::with_name("path")
61+
.short("p")
62+
.long("path")
63+
.value_name("PATH")
64+
.help(
65+
"Optional. Preserve generated headers for inspection, \
66+
provide directory path for header output. [default: None] ",
67+
)
68+
.takes_value(true)
69+
.validator(validate_path),
70+
)
71+
.arg(
72+
Arg::with_name("range")
73+
.short("r")
74+
.long("range")
75+
.value_name("RANGE")
76+
.help(
77+
"Sets the range quickcheck uses during generation. \
78+
Corresponds to things like arbitrary usize and \
79+
arbitrary vector length. This number doesn't have \
80+
to grow much for that execution time to increase \
81+
significantly.",
82+
)
83+
.takes_value(true)
84+
.default_value("32")
85+
.validator(validate_generate_range),
86+
)
87+
.arg(
88+
Arg::with_name("count")
89+
.short("c")
90+
.long("count")
91+
.value_name("COUNT")
92+
.help(
93+
"Count / number of tests to run. Running a fuzzed \
94+
header through the predicate.py script can take a \
95+
long time, especially if the generation range is \
96+
large. Increase this number if you're willing to \
97+
wait a while.",
98+
)
99+
.takes_value(true)
100+
.default_value("2")
101+
.validator(validate_tests_count),
102+
)
103+
.get_matches();
104+
105+
let output_path: Option<&str> = matches.value_of("path");
106+
let generate_range: usize = matches.value_of("range").unwrap().parse::<usize>().unwrap();
107+
let tests: usize = matches.value_of("count").unwrap().parse::<usize>().unwrap();
108+
109+
quickchecking::test_bindgen(generate_range, tests, output_path)
110+
}

tests/quickchecking/src/lib.rs

+98
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,107 @@
2020
//! ```
2121
//!
2222
#![deny(missing_docs)]
23+
#[macro_use]
24+
extern crate lazy_static;
2325
extern crate quickcheck;
2426
extern crate rand;
2527
extern crate tempdir;
2628

29+
use std::sync::Mutex;
30+
use quickcheck::{QuickCheck, StdGen, TestResult};
31+
use std::fs::File;
32+
use std::io::Write;
33+
use tempdir::TempDir;
34+
use std::process::{Command, Output};
35+
use std::path::PathBuf;
36+
use std::error::Error;
37+
use rand::thread_rng;
38+
2739
/// Contains definitions of and impls for types used to fuzz C declarations.
2840
pub mod fuzzers;
41+
42+
// Global singleton, manages context across tests. For now that context is
43+
// only the output_path for inspecting fuzzed headers (if specified).
44+
struct Context {
45+
output_path: Option<String>,
46+
}
47+
48+
// Initialize global context.
49+
lazy_static! {
50+
static ref CONTEXT: Mutex<Context> = Mutex::new(Context { output_path: None });
51+
}
52+
53+
// Passes fuzzed header to the `csmith-fuzzing/predicate.py` script, returns
54+
// output of the associated command.
55+
fn run_predicate_script(header: fuzzers::HeaderC) -> Result<Output, Box<Error>> {
56+
let dir = TempDir::new("bindgen_prop")?;
57+
let header_path = dir.path().join("prop_test.h");
58+
59+
let mut header_file = File::create(&header_path)?;
60+
header_file.write_all(header.to_string().as_bytes())?;
61+
header_file.sync_all()?;
62+
63+
let header_path_string;
64+
match header_path.into_os_string().into_string() {
65+
Ok(s) => header_path_string = s,
66+
Err(_) => return Err(From::from("error converting path into String")),
67+
}
68+
69+
let mut predicate_script_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
70+
predicate_script_path.push("../../csmith-fuzzing/predicate.py");
71+
72+
let predicate_script_path_string;
73+
match predicate_script_path.into_os_string().into_string() {
74+
Ok(s) => predicate_script_path_string = s,
75+
Err(_) => return Err(From::from("error converting path into String")),
76+
}
77+
78+
// Copy generated temp files to output_path directory for inspection.
79+
// If `None`, output path not specified, don't copy.
80+
match CONTEXT.lock().unwrap().output_path {
81+
Some(ref path) => {
82+
Command::new("cp")
83+
.arg("-a")
84+
.arg(&dir.path().to_str().unwrap())
85+
.arg(&path)
86+
.output()?;
87+
}
88+
None => {}
89+
}
90+
91+
Ok(Command::new(&predicate_script_path_string)
92+
.arg(&header_path_string)
93+
.output()?)
94+
}
95+
96+
// Generatable property. Pass generated headers off to run through the
97+
// `csmith-fuzzing/predicate.py` script. Success is measured by the success
98+
// status of that command.
99+
fn bindgen_prop(header: fuzzers::HeaderC) -> TestResult {
100+
match run_predicate_script(header) {
101+
Ok(o) => return TestResult::from_bool(o.status.success()),
102+
Err(e) => {
103+
println!("{:?}", e);
104+
return TestResult::from_bool(false);
105+
}
106+
}
107+
}
108+
109+
/// Instantiate a Quickcheck object and use it to run property tests using
110+
/// fuzzed C headers generated with types defined in the `fuzzers` module.
111+
/// Success/Failure is dictated by the result of passing the fuzzed headers
112+
/// to the `csmith-fuzzing/predicate.py` script.
113+
pub fn test_bindgen(generate_range: usize, tests: usize, output_path: Option<&str>) {
114+
match output_path {
115+
Some(path) => {
116+
CONTEXT.lock().unwrap().output_path =
117+
Some(String::from(PathBuf::from(path).to_str().unwrap()));
118+
}
119+
None => {} // Path not specified, don't provide output.
120+
}
121+
122+
QuickCheck::new()
123+
.tests(tests)
124+
.gen(StdGen::new(thread_rng(), generate_range))
125+
.quickcheck(bindgen_prop as fn(fuzzers::HeaderC) -> TestResult)
126+
}

0 commit comments

Comments
 (0)