extern crate glob;
use glob::glob;
use std::env;
use std::fs;
use std::io::{self, BufRead, Write};
use std::path;
fn get_crate_dir() -> io::Result<path::PathBuf> {
Ok(path::PathBuf::from(try!(env::var("CARGO_MANIFEST_DIR")
.map_err(|_| io::Error::new(io::ErrorKind::Other, "no CARGO_MANIFEST_DIR")))))
}
fn get_test_path(file_name: &str) -> io::Result<path::PathBuf> {
let mut test_path = try!(get_crate_dir());
test_path.push("tests");
assert!(test_path.is_dir());
test_path.push(file_name);
Ok(test_path)
}
fn generate_sanity_tests_from_afl_seeds() -> io::Result<()> {
for entry in glob("./in/*").expect("should read glob pattern") {
if let Ok(path) = entry {
println!("cargo:rerun-if-changed={}", path.display());
}
}
println!("cargo:rerun-if-changed=tests/afl_seeds.rs");
let test_path = try!(get_test_path("afl_seeds.rs"));
let mut test_file = try!(fs::File::create(test_path));
try!(writeln!(&mut test_file,
"
extern crate cpp_demangle;
use std::fs;
use std::io::Read;
"));
let mut in_dir = try!(get_crate_dir());
in_dir.push("in");
assert!(in_dir.is_dir());
let entries = try!(fs::read_dir(in_dir));
for entry in entries {
let entry = try!(entry);
let path = entry.path();
let file_name = try!(path.file_name()
.ok_or(io::Error::new(io::ErrorKind::Other,
"no file name for AFL.rs seed test case")));
try!(writeln!(&mut test_file,
r#"
#[test]
fn test_afl_seed_{}() {{
let mut file = fs::File::open("{}").unwrap();
let mut contents = Vec::new();
file.read_to_end(&mut contents).unwrap();
let _ = cpp_demangle::Symbol::new(contents);
assert!(true, "did not panic when parsing");
}}
"#,
file_name.to_string_lossy(),
path.to_string_lossy()));
}
Ok(())
}
const LIBIBERTY_TEST_THRESHOLD: usize = 60;
fn generate_compatibility_tests_from_libiberty() -> io::Result<()> {
println!("cargo:rerun-if-changed=tests/libiberty-demangle-expected");
let test_path = try!(get_test_path("libiberty.rs"));
let _ = fs::remove_file(&test_path);
let mut test_file = try!(fs::File::create(test_path));
try!(writeln!(&mut test_file, "
extern crate cpp_demangle;
extern crate diff;
use std::fmt::Write;
"));
let libiberty_tests = try!(get_test_path("libiberty-demangle-expected"));
let libiberty_tests = try!(fs::File::open(libiberty_tests));
let libiberty_tests = io::BufReader::new(libiberty_tests);
let mut lines = libiberty_tests.lines()
.filter(|line| {
line.as_ref()
.map(|l| !l.starts_with('#'))
.unwrap_or(true)
});
let mut n = 0;
loop {
let options = match lines.next() {
None => break,
Some(Ok(line)) => line,
Some(Err(e)) => return Err(e),
};
let mangled = match lines.next() {
Some(Ok(line)) => line,
None => {
return Err(io::Error::new(io::ErrorKind::Other,
"expected a line with a mangled symbol"))
}
Some(Err(e)) => return Err(e),
};
let demangled = match lines.next() {
Some(Ok(line)) => line,
None => {
return Err(io::Error::new(io::ErrorKind::Other,
"expected a line with the demangled symbol"))
}
Some(Err(e)) => return Err(e),
};
if options.find("--no-params").is_some() {
match lines.next() {
Some(Ok(_)) => {}
None => {
return Err(io::Error::new(io::ErrorKind::Other,
"expected a line with the demangled symbol without parameters"))
}
Some(Err(e)) => return Err(e),
}
}
if options.find("--format=gnu-v3").is_none() ||
options.find("--is-v3-ctor").is_some() ||
options.find("--is-v3-dtor").is_some() ||
options.find("--ret-postfix").is_some() {
continue;
}
let cfg = if n <= LIBIBERTY_TEST_THRESHOLD {
""
} else {
r###"#[cfg(feature = "run_libiberty_tests")]"###
};
try!(writeln!(test_file,
r###"
{}
#[test]
fn test_libiberty_demangle_{}_() {{
let mangled = br#"{}"#;
println!("Parsing mangled symbol: {{}}", String::from_utf8_lossy(mangled));
let sym = cpp_demangle::Symbol::new(&mangled[..])
.expect("should parse mangled symbol");
let expected = r#"{}"#;
let mut actual = String::new();
if let Err(e) = write!(&mut actual, "{{}}", sym) {{
panic!("Error while demangling '{{}}': {{}}",
String::from_utf8_lossy(mangled),
e);
}}
println!(" Expect demangled symbol: {{}}", expected);
println!("Actually demangled symbol as: {{}}", actual);
if expected != actual {{
println!("");
println!("Diff:");
println!("--- expected");
print!("+++ actual");
let mut last = None;
for cmp in diff::chars(expected, &actual) {{
match (last, cmp.clone()) {{
(Some(diff::Result::Left(_)), diff::Result::Left(_)) |
(Some(diff::Result::Both(..)), diff::Result::Both(..)) |
(Some(diff::Result::Right(_)), diff::Result::Right(_)) => {{}}
(_, diff::Result::Left(_)) => print!("\n-"),
(_, diff::Result::Both(..)) => print!("\n "),
(_, diff::Result::Right(_)) => print!("\n+"),
}};
match cmp.clone() {{
diff::Result::Left(c) |
diff::Result::Both(c, _) |
diff::Result::Right(c) => print!("{{}}", c),
}}
last = Some(cmp);
}}
println!("");
}}
assert_eq!(expected, actual);
}}
"###,
cfg,
n,
mangled.trim(),
demangled.trim()));
n += 1;
}
Ok(())
}
fn main() {
println!("cargo:rerun-if-changed=build.rs");
generate_sanity_tests_from_afl_seeds()
.expect("should generate sanity tests from AFL.rs seed test cases");
generate_compatibility_tests_from_libiberty()
.expect("should generate compatibility tests from tests/libiberty-demangle-expected");
}