Skip to content

Commit ab33eca

Browse files
committed
Rustify test suite
1 parent 8abc2f0 commit ab33eca

File tree

2 files changed

+135
-138
lines changed

2 files changed

+135
-138
lines changed

libbindgen/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ readme = "README.md"
1414
repository = "https://github.com/servo/rust-bindgen"
1515
version = "0.17.0"
1616

17+
[dev-dependencies]
18+
tempdir = "0.3"
19+
diff = "0.1"
20+
1721
[build-dependencies]
1822
quasi_codegen = "0.20"
1923

libbindgen/tests/tests.rs

+131-138
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,162 @@
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;
64

75
use std::env;
86
use std::fs;
9-
use std::io::Read;
7+
use std::io::{BufReader, BufRead, Read, Write};
108
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");
3141

3242
let mut expected = PathBuf::from(header);
33-
let file_name = expected.file_name()
34-
.expect("Should have filename")
35-
.to_os_string();
3643
expected.pop();
3744
expected.pop();
3845
expected.push("expectations");
3946
expected.push(file_name);
4047
expected.set_extension("rs");
4148

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+
};
7253

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(()))
8762
} 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(())
8973
}
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+
}
94130
}
95131

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");
99140

100141
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"));
103144

104-
let tests = entries.filter(|entry| {
145+
let tests = entries.filter_map(|entry| {
105146
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,
108149
}
109150
})
110151
.collect::<Vec<_>>();
111152

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();
142157

143158
let num_failures = failures.len();
144159

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-
167160
if num_failures > 0 {
168161
panic!("{} test failures!", num_failures);
169162
}

0 commit comments

Comments
 (0)