|
1 |
| -// We add this `extern crate` here to ensure that bindgen is up-to-date and |
2 |
| -// rebuilt, even though we aren't using any of its types or functions here, only |
3 |
| -// indirectly calling the executable. |
4 |
| -#[allow(dead_code)] |
5 |
| -extern crate bindgen; |
| 1 | +extern crate libbindgen; |
| 2 | +extern crate tempdir; |
| 3 | +extern crate diff; |
6 | 4 |
|
7 | 5 | use std::env;
|
8 | 6 | use std::fs;
|
9 |
| -use std::io::Read; |
| 7 | +use std::io::{BufReader, BufRead, Read, Write}; |
10 | 8 | use std::path::{Path, PathBuf};
|
11 |
| -use std::process; |
12 |
| - |
13 |
| -const TEST_BATCH_DEFAULT_SIZE: usize = 16; |
14 |
| - |
15 |
| -fn spawn_run_bindgen<P, Q, R>(run_bindgen: P, |
16 |
| - bindgen: Q, |
17 |
| - header: R) |
18 |
| - -> process::Child |
19 |
| - where P: AsRef<Path>, |
20 |
| - Q: AsRef<Path>, |
21 |
| - R: AsRef<Path>, |
22 |
| -{ |
23 |
| - let run_bindgen = run_bindgen.as_ref(); |
24 |
| - let bindgen = bindgen.as_ref(); |
25 |
| - let header = header.as_ref(); |
26 |
| - |
27 |
| - // Convert from "tests/headers/foo.hpp" to "tests/expectations/foo.rs" by |
28 |
| - // saving the filename, popping off "headers/foo.hpp", pushing |
29 |
| - // "expectations", pushing the saved filename, and finally modifying the |
30 |
| - // extension. |
| 9 | +use std::process::{Command, ExitStatus}; |
| 10 | +use tempdir::TempDir; |
| 11 | + |
| 12 | +fn test_generated_bindings(header: &PathBuf, output: &str) -> Result<ExitStatus, std::io::Error> { |
| 13 | + let temp_dir = TempDir::new("bindgen-tests") |
| 14 | + .expect("Couldn't create temp dir"); |
| 15 | + |
| 16 | + let file_name = header.file_name() |
| 17 | + .expect("test_generated_bindings expects a file"); |
| 18 | + |
| 19 | + let mut source = temp_dir.path().to_owned(); |
| 20 | + source.push(file_name); |
| 21 | + source.set_extension("rs"); |
| 22 | + |
| 23 | + let mut binary = temp_dir.path().to_owned(); |
| 24 | + binary.push(file_name); |
| 25 | + binary.set_extension("bin"); |
| 26 | + |
| 27 | + let mut file = fs::File::create(&source).expect("Couldn't create output source file"); |
| 28 | + try!(file.write_all(output.as_bytes())); |
| 29 | + |
| 30 | + Command::new("rustc") |
| 31 | + .arg("--test") |
| 32 | + .arg(source) |
| 33 | + .arg("-o") |
| 34 | + .arg(binary) |
| 35 | + .status() |
| 36 | +} |
| 37 | + |
| 38 | +fn spawn_bindgen(header: &PathBuf, builder: libbindgen::Builder) -> Result<(), ()> { |
| 39 | + let file_name = header.file_name() |
| 40 | + .expect("spawn_bindgen expects a file"); |
31 | 41 |
|
32 | 42 | let mut expected = PathBuf::from(header);
|
33 |
| - let file_name = expected.file_name() |
34 |
| - .expect("Should have filename") |
35 |
| - .to_os_string(); |
36 | 43 | expected.pop();
|
37 | 44 | expected.pop();
|
38 | 45 | expected.push("expectations");
|
39 | 46 | expected.push(file_name);
|
40 | 47 | expected.set_extension("rs");
|
41 | 48 |
|
42 |
| - // And the same style conversion as above, but for the dummy uses. We assume |
43 |
| - // that .hpp means we should generate a .cpp uses file, and .h means we |
44 |
| - // should generate a .c file. |
45 |
| - |
46 |
| - let mut dummy_uses = PathBuf::from(header); |
47 |
| - let file_name = dummy_uses.file_name() |
48 |
| - .expect("Should still have filename") |
49 |
| - .to_os_string(); |
50 |
| - dummy_uses.pop(); |
51 |
| - dummy_uses.pop(); |
52 |
| - dummy_uses.push("uses"); |
53 |
| - dummy_uses.push(file_name); |
54 |
| - dummy_uses.set_extension(if header.extension().and_then(|s| s.to_str()) == |
55 |
| - Some("hpp") { |
56 |
| - "cpp" |
57 |
| - } else { |
58 |
| - "c" |
59 |
| - }); |
60 |
| - |
61 |
| - process::Command::new(run_bindgen) |
62 |
| - .stdout(process::Stdio::piped()) |
63 |
| - .stderr(process::Stdio::piped()) |
64 |
| - .arg(bindgen) |
65 |
| - .arg(header) |
66 |
| - .arg(expected) |
67 |
| - .arg("--dummy-uses") |
68 |
| - .arg(dummy_uses) |
69 |
| - .spawn() |
70 |
| - .expect("Should be able to spawn run-bindgen.py child process") |
71 |
| -} |
| 49 | + let output = match builder.generate() { |
| 50 | + Ok(bindings) => bindings.to_string(), |
| 51 | + Err(_) => "".to_string(), |
| 52 | + }; |
72 | 53 |
|
73 |
| -#[test] |
74 |
| -fn run_bindgen_tests() { |
75 |
| - let crate_root = env::var("CARGO_MANIFEST_DIR") |
76 |
| - .expect("should have CARGO_MANIFEST_DIR environment variable"); |
77 |
| - |
78 |
| - let mut run_bindgen = PathBuf::from(&crate_root); |
79 |
| - run_bindgen.push("tests"); |
80 |
| - run_bindgen.push("tools"); |
81 |
| - run_bindgen.push("run-bindgen.py"); |
82 |
| - |
83 |
| - let mut bindgen = PathBuf::from(&crate_root); |
84 |
| - bindgen.push("target"); |
85 |
| - if cfg!(debug_assertions) { |
86 |
| - bindgen.push("debug"); |
| 54 | + let mut buffer = String::new(); |
| 55 | + let _ = fs::File::open(&expected) |
| 56 | + .expect("Couldn't read from expected test output") |
| 57 | + .read_to_string(&mut buffer) |
| 58 | + .expect("Couldn't read from expected test output"); |
| 59 | + |
| 60 | + if output == buffer { |
| 61 | + test_generated_bindings(&header, &output).and(Ok(())).or(Err(())) |
87 | 62 | } else {
|
88 |
| - bindgen.push("release"); |
| 63 | + println!("diff expected generated\n--- expected: {:?}\n+++ generated from: {:?}", |
| 64 | + expected, header); |
| 65 | + for diff in diff::lines(&buffer, &output) { |
| 66 | + match diff { |
| 67 | + diff::Result::Left(l) => println!("-{}", l), |
| 68 | + diff::Result::Both(l, _) => println!(" {}", l), |
| 69 | + diff::Result::Right(r) => println!("+{}", r), |
| 70 | + } |
| 71 | + } |
| 72 | + Err(()) |
89 | 73 | }
|
90 |
| - bindgen.push("bindgen"); |
91 |
| - if !bindgen.is_file() { |
92 |
| - panic!("{} is not a file! Build bindgen before running tests.", |
93 |
| - bindgen.display()); |
| 74 | +} |
| 75 | + |
| 76 | +fn create_bindgen_builder(header: &PathBuf) -> libbindgen::Builder { |
| 77 | + let mut builder = libbindgen::builder() |
| 78 | + .header(header.to_str().unwrap()) |
| 79 | + .raw_line("") |
| 80 | + .raw_line("#![allow(non_snake_case)]") |
| 81 | + .raw_line(""); |
| 82 | + |
| 83 | + let source = fs::File::open(header).unwrap(); |
| 84 | + let reader = BufReader::new(source); |
| 85 | + |
| 86 | + let head: Result<Vec<_>, _> = reader.lines().take(5).collect(); |
| 87 | + let flagline = head.unwrap().into_iter() |
| 88 | + .filter(|o| o.contains("bindgen-flags:")).nth(0); |
| 89 | + |
| 90 | + if let Some(flagline) = flagline { |
| 91 | + // FIXME: split flags again on = |
| 92 | + let flags: Vec<_> = flagline.split("bindgen-flags:").last().unwrap() |
| 93 | + .trim().split_whitespace().collect(); |
| 94 | + let mut it = flags.into_iter(); |
| 95 | + while let Some(flag) = it.next() { |
| 96 | + builder = match flag { |
| 97 | + "--enable-cxx-namespaces" => builder.enable_cxx_namespaces(), |
| 98 | + "--no-unstable-rust" => builder.no_unstable_rust(), |
| 99 | + "--whitelist-type" => { |
| 100 | + if let Some(param) = it.next() { |
| 101 | + builder.whitelisted_type(param) |
| 102 | + } else { |
| 103 | + builder |
| 104 | + } |
| 105 | + }, |
| 106 | + "--whitelist-var" => { |
| 107 | + if let Some(param) = it.next() { |
| 108 | + builder.whitelisted_var(param) |
| 109 | + } else { |
| 110 | + builder |
| 111 | + } |
| 112 | + }, |
| 113 | + "--blacklist-type" => { |
| 114 | + if let Some(param) = it.next() { |
| 115 | + builder.hide_type(param) |
| 116 | + } else { |
| 117 | + builder |
| 118 | + } |
| 119 | + }, |
| 120 | + "--" => { |
| 121 | + let clang: Vec<_> = it.collect(); |
| 122 | + for arg in clang { |
| 123 | + builder = builder.clang_arg(arg); |
| 124 | + } |
| 125 | + break; |
| 126 | + }, |
| 127 | + e => panic!("What the hell is '{}'?", e), |
| 128 | + } |
| 129 | + } |
94 | 130 | }
|
95 | 131 |
|
96 |
| - let mut headers_dir = PathBuf::from(&crate_root); |
97 |
| - headers_dir.push("tests"); |
98 |
| - headers_dir.push("headers"); |
| 132 | + builder |
| 133 | +} |
| 134 | + |
| 135 | +#[test] |
| 136 | +fn run_bindgen_tests() { |
| 137 | + let manifest_env = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set!"); |
| 138 | + let manifest_dir = Path::new(&manifest_env); |
| 139 | + let headers_dir = manifest_dir.join("tests").join("headers"); |
99 | 140 |
|
100 | 141 | let entries = fs::read_dir(&headers_dir)
|
101 |
| - .expect("Should read directory") |
102 |
| - .map(|result| result.expect("Should read directory entry")); |
| 142 | + .expect("Couldn't read headers dir") |
| 143 | + .map(|result| result.expect("Couldn't read header file")); |
103 | 144 |
|
104 |
| - let tests = entries.filter(|entry| { |
| 145 | + let tests = entries.filter_map(|entry| { |
105 | 146 | match entry.path().extension().and_then(|s| s.to_str()) {
|
106 |
| - Some("h") | Some("hpp") => true, |
107 |
| - _ => false, |
| 147 | + Some("h") | Some("hpp") => Some(entry.path()), |
| 148 | + _ => None, |
108 | 149 | }
|
109 | 150 | })
|
110 | 151 | .collect::<Vec<_>>();
|
111 | 152 |
|
112 |
| - let batch_size = env::var("BINDGEN_TEST_BATCH_SIZE") |
113 |
| - .ok() |
114 |
| - .and_then(|x| x.parse::<usize>().ok()) |
115 |
| - .unwrap_or(TEST_BATCH_DEFAULT_SIZE); |
116 |
| - |
117 |
| - // Spawn `batch_size` children to run in parallel and wait on all of them |
118 |
| - // before processing the next batch. This puts a limit on the resources |
119 |
| - // consumed when testing, so that we don't overload the system. |
120 |
| - |
121 |
| - let children = tests.chunks(batch_size).map(|x| { |
122 |
| - x.iter() |
123 |
| - .map(|entry| { |
124 |
| - let child = spawn_run_bindgen(run_bindgen.clone(), |
125 |
| - bindgen.clone(), |
126 |
| - entry.path()); |
127 |
| - (entry.path(), child) |
128 |
| - }) |
129 |
| - .collect::<Vec<_>>() |
130 |
| - }); |
131 |
| - |
132 |
| - let failures: Vec<_> = children.flat_map(|x| { |
133 |
| - x.into_iter().filter_map(|(path, mut child)| { |
134 |
| - let passed = child.wait() |
135 |
| - .expect("Should wait on child process") |
136 |
| - .success(); |
137 |
| - |
138 |
| - if passed { None } else { Some((path, child)) } |
139 |
| - }) |
140 |
| - }) |
141 |
| - .collect(); |
| 153 | + let failures: Vec<_> = tests.iter().filter_map(|header| { |
| 154 | + let builder = create_bindgen_builder(header); |
| 155 | + spawn_bindgen(header, builder).err() |
| 156 | + }).collect(); |
142 | 157 |
|
143 | 158 | let num_failures = failures.len();
|
144 | 159 |
|
145 |
| - for (path, child) in failures { |
146 |
| - println!("FAIL: {}", path.display()); |
147 |
| - |
148 |
| - let mut buf = String::new(); |
149 |
| - |
150 |
| - child.stdout |
151 |
| - .expect("should have stdout piped") |
152 |
| - .read_to_string(&mut buf) |
153 |
| - .expect("should read child's stdout"); |
154 |
| - for line in buf.lines() { |
155 |
| - println!("child stdout> {}", line); |
156 |
| - } |
157 |
| - |
158 |
| - child.stderr |
159 |
| - .expect("should have stderr piped") |
160 |
| - .read_to_string(&mut buf) |
161 |
| - .expect("should read child's stderr"); |
162 |
| - for line in buf.lines() { |
163 |
| - println!("child stderr> {}", line); |
164 |
| - } |
165 |
| - } |
166 |
| - |
167 | 160 | if num_failures > 0 {
|
168 | 161 | panic!("{} test failures!", num_failures);
|
169 | 162 | }
|
|
0 commit comments