extern crate clap; extern crate diff; extern crate bindgen; extern crate shlex; use bindgen::Builder; use std::fs; use std::io::{BufRead, BufReader, Error, ErrorKind, Read, Write}; use std::path::PathBuf; #[path="../src/options.rs"] mod options; use options::builder_from_flags; fn compare_generated_header(header: &PathBuf, builder: Builder) -> Result<(), Error> { let file_name = try!(header.file_name() .ok_or(Error::new(ErrorKind::Other, "spawn_bindgen expects a file"))); let mut expected = PathBuf::from(header); expected.pop(); expected.pop(); expected.push("expectations"); expected.push("tests"); expected.push(file_name); expected.set_extension("rs"); // We skip the generate() error here so we get a full diff below let output = match builder.generate() { Ok(bindings) => bindings.to_string(), Err(_) => "".to_string(), }; let mut buffer = String::new(); { if let Ok(expected_file) = fs::File::open(&expected) { try!(BufReader::new(expected_file).read_to_string(&mut buffer)); } } if output == buffer { if !output.is_empty() { return Ok(()); } return Err(Error::new(ErrorKind::Other, "Something's gone really wrong!")); } println!("diff expected generated"); println!("--- expected: {:?}", expected); println!("+++ generated from: {:?}", header); for diff in diff::lines(&buffer, &output) { match diff { diff::Result::Left(l) => println!("-{}", l), diff::Result::Both(l, _) => println!(" {}", l), diff::Result::Right(r) => println!("+{}", r), } } // Override the diff. { let mut expected_file = try!(fs::File::create(&expected)); try!(expected_file.write_all(output.as_bytes())); } Err(Error::new(ErrorKind::Other, "Header and binding differ!")) } fn create_bindgen_builder(header: &PathBuf) -> Result, Error> { let source = try!(fs::File::open(header)); let reader = BufReader::new(source); // Scoop up bindgen-flags from test header let mut flags = Vec::with_capacity(2); for line in reader.lines().take(3) { let line = try!(line); if line.contains("bindgen-flags: ") { let extra_flags = line.split("bindgen-flags: ") .last() .and_then(shlex::split) .unwrap(); flags.extend(extra_flags.into_iter()); } else if line.contains("bindgen-unstable") && cfg!(feature = "testing_only_llvm_stable") { return Ok(None); } else if line.contains("bindgen-osx-only") { let prepend_flags = ["--raw-line", "#![cfg(target_os=\"macos\")]"]; flags = prepend_flags.into_iter() .map(ToString::to_string) .chain(flags) .collect(); } } // Fool builder_from_flags() into believing it has real env::args_os... // - add "bindgen" as executable name 0th element // - add header filename as 1st element // - prepend raw lines so they're in the right order for expected output // - append the test header's bindgen flags let header_str = try!(header.to_str() .ok_or(Error::new(ErrorKind::Other, "Invalid header file name"))); let prepend = ["bindgen", "--with-derive-default", header_str, "--raw-line", "", "--raw-line", "#![allow(non_snake_case)]", "--raw-line", ""]; let args = prepend.into_iter() .map(ToString::to_string) .chain(flags.into_iter()); builder_from_flags(args) .map(|(builder, _, _)| Some(builder.no_unstable_rust())) } macro_rules! test_header { ($function:ident, $header:expr) => ( #[test] fn $function() { let header = PathBuf::from($header); let result = create_bindgen_builder(&header) .and_then(|builder| { if let Some(builder) = builder { compare_generated_header(&header, builder) } else { Ok(()) } }); if let Err(err) = result { panic!("{}", err); } } ) } // This file is generated by build.rs include!(concat!(env!("OUT_DIR"), "/tests.rs"));