1 extern crate bindgen;
2 extern crate clap;
3 extern crate diff;
4 #[cfg(feature = "logging")]
5 extern crate env_logger;
6 extern crate shlex;
7
8 use bindgen::{clang_version, Builder};
9 use std::env;
10 use std::fs;
11 use std::io::{self, BufRead, BufReader, Error, ErrorKind, Read, Write};
12 use std::path::{Path, PathBuf};
13 use std::process;
14 use std::sync::Once;
15
16 use crate::options::builder_from_flags;
17
18 #[path = "../../bindgen-cli/options.rs"]
19 mod options;
20
21 mod parse_callbacks;
22
23 // Run `rustfmt` on the given source string and return a tuple of the formatted
24 // bindings, and rustfmt's stderr.
rustfmt(source: String) -> (String, String)25 fn rustfmt(source: String) -> (String, String) {
26 static CHECK_RUSTFMT: Once = Once::new();
27
28 CHECK_RUSTFMT.call_once(|| {
29 if env::var_os("RUSTFMT").is_some() {
30 return;
31 }
32
33 let mut rustfmt = {
34 let mut p = process::Command::new("rustup");
35 p.args(["run", "nightly", "rustfmt", "--version"]);
36 p
37 };
38
39 let have_working_rustfmt = rustfmt
40 .stdout(process::Stdio::null())
41 .stderr(process::Stdio::null())
42 .status()
43 .ok()
44 .map_or(false, |status| status.success());
45
46 if !have_working_rustfmt {
47 panic!(
48 "
49 The latest `rustfmt` is required to run the `bindgen` test suite. Install
50 `rustfmt` with:
51
52 $ rustup update nightly
53 $ rustup component add rustfmt --toolchain nightly
54 "
55 );
56 }
57 });
58
59 let mut child = match env::var_os("RUSTFMT") {
60 Some(r) => process::Command::new(r),
61 None => {
62 let mut p = process::Command::new("rustup");
63 p.args(["run", "nightly", "rustfmt"]);
64 p
65 }
66 };
67
68 let mut child = child
69 .args([
70 "--config-path",
71 concat!(env!("CARGO_MANIFEST_DIR"), "/tests/rustfmt.toml"),
72 ])
73 .stdin(process::Stdio::piped())
74 .stdout(process::Stdio::piped())
75 .stderr(process::Stdio::piped())
76 .spawn()
77 .expect("should spawn `rustup run nightly rustfmt`");
78
79 let mut stdin = child.stdin.take().unwrap();
80 let mut stdout = child.stdout.take().unwrap();
81 let mut stderr = child.stderr.take().unwrap();
82
83 // Write to stdin in a new thread, so that we can read from stdout on this
84 // thread. This keeps the child from blocking on writing to its stdout which
85 // might block us from writing to its stdin.
86 let stdin_handle =
87 ::std::thread::spawn(move || stdin.write_all(source.as_bytes()));
88
89 // Read stderr on a new thread for similar reasons.
90 let stderr_handle = ::std::thread::spawn(move || {
91 let mut output = vec![];
92 io::copy(&mut stderr, &mut output)
93 .map(|_| String::from_utf8_lossy(&output).to_string())
94 });
95
96 let mut output = vec![];
97 io::copy(&mut stdout, &mut output).expect("Should copy stdout into vec OK");
98
99 // Ignore actual rustfmt status because it is often non-zero for trivial
100 // things.
101 let _ = child.wait().expect("should wait on rustfmt child OK");
102
103 stdin_handle
104 .join()
105 .expect("writer thread should not have panicked")
106 .expect("should have written to child rustfmt's stdin OK");
107
108 let bindings = String::from_utf8(output)
109 .expect("rustfmt should only emit valid utf-8");
110
111 let stderr = stderr_handle
112 .join()
113 .expect("stderr reader thread should not have panicked")
114 .expect("should have read child rustfmt's stderr OK");
115
116 (bindings, stderr)
117 }
118
should_overwrite_expected() -> bool119 fn should_overwrite_expected() -> bool {
120 if let Some(var) = env::var_os("BINDGEN_OVERWRITE_EXPECTED") {
121 if var == "1" {
122 return true;
123 }
124 if var != "0" && var != "" {
125 panic!("Invalid value of BINDGEN_OVERWRITE_EXPECTED");
126 }
127 }
128 false
129 }
130
error_diff_mismatch( actual: &str, expected: &str, header: Option<&Path>, filename: &Path, ) -> Result<(), Error>131 fn error_diff_mismatch(
132 actual: &str,
133 expected: &str,
134 header: Option<&Path>,
135 filename: &Path,
136 ) -> Result<(), Error> {
137 println!("diff expected generated");
138 println!("--- expected: {:?}", filename);
139 if let Some(header) = header {
140 println!("+++ generated from: {:?}", header);
141 }
142
143 for diff in diff::lines(expected, actual) {
144 match diff {
145 diff::Result::Left(l) => println!("-{}", l),
146 diff::Result::Both(l, _) => println!(" {}", l),
147 diff::Result::Right(r) => println!("+{}", r),
148 }
149 }
150
151 if should_overwrite_expected() {
152 // Overwrite the expectation with actual output.
153 let mut expectation_file = fs::File::create(filename)?;
154 expectation_file.write_all(actual.as_bytes())?;
155 }
156
157 if let Some(var) = env::var_os("BINDGEN_TESTS_DIFFTOOL") {
158 //usecase: var = "meld" -> You can hand check differences
159 let name = match filename.components().last() {
160 Some(std::path::Component::Normal(name)) => name,
161 _ => panic!("Why is the header variable so weird?"),
162 };
163 let actual_result_path =
164 PathBuf::from(env::var("OUT_DIR").unwrap()).join(name);
165 let mut actual_result_file = fs::File::create(&actual_result_path)?;
166 actual_result_file.write_all(actual.as_bytes())?;
167 std::process::Command::new(var)
168 .args([filename, &actual_result_path])
169 .output()?;
170 }
171
172 Err(Error::new(ErrorKind::Other, "Header and binding differ! Run with BINDGEN_OVERWRITE_EXPECTED=1 in the environment to automatically overwrite the expectation or with BINDGEN_TESTS_DIFFTOOL=meld to do this manually."))
173 }
174
compare_generated_header( header: &Path, builder: BuilderState, check_roundtrip: bool, ) -> Result<(), Error>175 fn compare_generated_header(
176 header: &Path,
177 builder: BuilderState,
178 check_roundtrip: bool,
179 ) -> Result<(), Error> {
180 let file_name = header.file_name().ok_or_else(|| {
181 Error::new(ErrorKind::Other, "compare_generated_header expects a file")
182 })?;
183
184 let mut expectation = PathBuf::from(header);
185 expectation.pop();
186 expectation.pop();
187 expectation.push("expectations");
188 expectation.push("tests");
189
190 let mut looked_at = vec![];
191 let mut expectation_file;
192
193 // Try more specific expectations first.
194 {
195 let mut expectation = expectation.clone();
196
197 if cfg!(feature = "testing_only_libclang_9") {
198 expectation.push("libclang-9");
199 } else if cfg!(feature = "testing_only_libclang_5") {
200 expectation.push("libclang-5");
201 } else {
202 match clang_version().parsed {
203 None => expectation.push("libclang-9"),
204 Some(version) => {
205 let (maj, min) = version;
206 let version_str = if maj >= 9 {
207 "9".to_owned()
208 } else if maj >= 5 {
209 "5".to_owned()
210 } else if maj >= 4 {
211 "4".to_owned()
212 } else {
213 format!("{}.{}", maj, min)
214 };
215 expectation.push(format!("libclang-{}", version_str));
216 }
217 }
218 }
219
220 expectation.push(file_name);
221 expectation.set_extension("rs");
222 expectation_file = fs::File::open(&expectation).ok();
223 looked_at.push(expectation);
224 }
225
226 // Try the default path otherwise.
227 if expectation_file.is_none() {
228 expectation.push(file_name);
229 expectation.set_extension("rs");
230 expectation_file = fs::File::open(&expectation).ok();
231 looked_at.push(expectation.clone());
232 }
233
234 let mut expected = String::new();
235 match expectation_file {
236 Some(f) => {
237 BufReader::new(f).read_to_string(&mut expected)?;
238 }
239 None => panic!(
240 "missing test expectation file and/or 'testing_only_libclang_$VERSION' \
241 feature for header '{}'; looking for expectation file at '{:?}'",
242 header.display(),
243 looked_at,
244 ),
245 };
246
247 let (builder, roundtrip_builder) = builder.into_builder(check_roundtrip)?;
248
249 // We skip the generate() error here so we get a full diff below
250 let (actual, rustfmt_stderr) = match builder.generate() {
251 Ok(bindings) => {
252 let actual = bindings.to_string();
253 rustfmt(actual)
254 }
255 Err(_) => ("/* error generating bindings */\n".into(), "".to_string()),
256 };
257 println!("{}", rustfmt_stderr);
258
259 let (expected, rustfmt_stderr) = rustfmt(expected);
260 println!("{}", rustfmt_stderr);
261
262 if actual.is_empty() {
263 return Err(Error::new(
264 ErrorKind::Other,
265 "Something's gone really wrong!",
266 ));
267 }
268
269 if actual != expected {
270 println!("{}", rustfmt_stderr);
271 return error_diff_mismatch(
272 &actual,
273 &expected,
274 Some(header),
275 looked_at.last().unwrap(),
276 );
277 }
278
279 if let Some(roundtrip_builder) = roundtrip_builder {
280 if let Err(e) =
281 compare_generated_header(header, roundtrip_builder, false)
282 {
283 return Err(Error::new(ErrorKind::Other, format!("Checking CLI flags roundtrip errored! You probably need to fix Builder::command_line_flags. {}", e)));
284 }
285 }
286
287 Ok(())
288 }
289
builder() -> Builder290 fn builder() -> Builder {
291 #[cfg(feature = "logging")]
292 let _ = env_logger::try_init();
293
294 bindgen::builder().disable_header_comment()
295 }
296
297 struct BuilderState {
298 builder: Builder,
299 parse_callbacks: Option<String>,
300 }
301
302 impl BuilderState {
into_builder( self, with_roundtrip_builder: bool, ) -> Result<(Builder, Option<BuilderState>), Error>303 fn into_builder(
304 self,
305 with_roundtrip_builder: bool,
306 ) -> Result<(Builder, Option<BuilderState>), Error> {
307 let roundtrip_builder = if with_roundtrip_builder {
308 let mut flags = self.builder.command_line_flags();
309 flags.insert(0, "bindgen".into());
310 let mut builder = builder_from_flags(flags.into_iter())?.0;
311 if let Some(ref parse_cb) = self.parse_callbacks {
312 builder =
313 builder.parse_callbacks(parse_callbacks::lookup(parse_cb));
314 }
315 Some(BuilderState {
316 builder,
317 parse_callbacks: self.parse_callbacks,
318 })
319 } else {
320 None
321 };
322 Ok((self.builder, roundtrip_builder))
323 }
324 }
325
create_bindgen_builder(header: &Path) -> Result<BuilderState, Error>326 fn create_bindgen_builder(header: &Path) -> Result<BuilderState, Error> {
327 #[cfg(feature = "logging")]
328 let _ = env_logger::try_init();
329
330 let source = fs::File::open(header)?;
331 let reader = BufReader::new(source);
332
333 // Scoop up bindgen-flags from test header
334 let mut flags = Vec::with_capacity(2);
335 let mut parse_callbacks = None;
336
337 for line in reader.lines() {
338 let line = line?;
339 if !line.starts_with("// bindgen") {
340 continue;
341 }
342
343 if line.contains("bindgen-flags: ") {
344 let extra_flags = line
345 .split("bindgen-flags: ")
346 .last()
347 .and_then(shlex::split)
348 .unwrap();
349 flags.extend(extra_flags.into_iter());
350 } else if line.contains("bindgen-osx-only") {
351 let prepend_flags = ["--raw-line", "#![cfg(target_os=\"macos\")]"];
352 flags = prepend_flags
353 .iter()
354 .map(ToString::to_string)
355 .chain(flags)
356 .collect();
357 } else if line.contains("bindgen-parse-callbacks: ") {
358 let parse_cb =
359 line.split("bindgen-parse-callbacks: ").last().unwrap();
360 parse_callbacks = Some(parse_cb.to_owned());
361 }
362 }
363
364 // Different platforms have various different conventions like struct padding, mangling, etc.
365 // We make the default target as x86_64-unknown-linux
366 if flags.iter().all(|flag| !flag.starts_with("--target=")) {
367 if !flags.iter().any(|flag| flag == "--") {
368 flags.push("--".into());
369 }
370 flags.push("--target=x86_64-unknown-linux".into());
371 }
372
373 // Fool builder_from_flags() into believing it has real env::args_os...
374 // - add "bindgen" as executable name 0th element
375 // - add header filename as 1st element
376 // - prepend raw lines so they're in the right order for expected output
377 // - append the test header's bindgen flags
378 let header_str = header.to_str().ok_or_else(|| {
379 Error::new(ErrorKind::Other, "Invalid header file name")
380 })?;
381
382 let prepend = [
383 "bindgen",
384 // We format in `compare_generated_header` ourselves to have a little
385 // more control.
386 "--no-rustfmt-bindings",
387 "--with-derive-default",
388 "--disable-header-comment",
389 "--vtable-generation",
390 header_str,
391 "--raw-line",
392 "",
393 "--raw-line",
394 "#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]",
395 "--raw-line",
396 "",
397 ];
398
399 let args = prepend
400 .iter()
401 .map(ToString::to_string)
402 .chain(flags.into_iter());
403
404 let mut builder = builder_from_flags(args)?.0;
405 if let Some(ref parse_cb) = parse_callbacks {
406 builder = builder.parse_callbacks(parse_callbacks::lookup(parse_cb));
407 }
408 Ok(BuilderState {
409 builder,
410 parse_callbacks,
411 })
412 }
413
414 macro_rules! test_header {
415 ($function:ident, $header:expr) => {
416 #[test]
417 fn $function() {
418 let header = PathBuf::from($header);
419 let result = create_bindgen_builder(&header).and_then(|builder| {
420 let check_roundtrip =
421 env::var_os("BINDGEN_DISABLE_ROUNDTRIP_TEST").is_none();
422 compare_generated_header(&header, builder, check_roundtrip)
423 });
424
425 if let Err(err) = result {
426 panic!("{}", err);
427 }
428 }
429 };
430 }
431
432 // This file is generated by build.rs
433 include!(concat!(env!("OUT_DIR"), "/tests.rs"));
434
435 #[test]
436 #[cfg_attr(target_os = "windows", ignore)]
test_clang_env_args()437 fn test_clang_env_args() {
438 std::env::set_var(
439 "BINDGEN_EXTRA_CLANG_ARGS",
440 "-D_ENV_ONE=1 -D_ENV_TWO=\"2 -DNOT_THREE=1\"",
441 );
442 let actual = builder()
443 .disable_header_comment()
444 .header_contents(
445 "test.hpp",
446 "#ifdef _ENV_ONE\nextern const int x[] = { 42 };\n#endif\n\
447 #ifdef _ENV_TWO\nextern const int y[] = { 42 };\n#endif\n\
448 #ifdef NOT_THREE\nextern const int z[] = { 42 };\n#endif\n",
449 )
450 .generate()
451 .unwrap()
452 .to_string();
453
454 let (actual, stderr) = rustfmt(actual);
455 println!("{}", stderr);
456
457 let (expected, _) = rustfmt(
458 "extern \"C\" {
459 pub static x: [::std::os::raw::c_int; 1usize];
460 }
461 extern \"C\" {
462 pub static y: [::std::os::raw::c_int; 1usize];
463 }
464 "
465 .to_string(),
466 );
467
468 assert_eq!(expected, actual);
469 }
470
471 #[test]
test_header_contents()472 fn test_header_contents() {
473 let actual = builder()
474 .disable_header_comment()
475 .header_contents("test.h", "int foo(const char* a);")
476 .clang_arg("--target=x86_64-unknown-linux")
477 .generate()
478 .unwrap()
479 .to_string();
480
481 let (actual, stderr) = rustfmt(actual);
482 println!("{}", stderr);
483
484 let (expected, _) = rustfmt(
485 "extern \"C\" {
486 pub fn foo(a: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int;
487 }
488 "
489 .to_string(),
490 );
491
492 assert_eq!(expected, actual);
493 }
494
495 #[test]
test_multiple_header_calls_in_builder()496 fn test_multiple_header_calls_in_builder() {
497 let actual = builder()
498 .header(concat!(
499 env!("CARGO_MANIFEST_DIR"),
500 "/tests/headers/func_ptr.h"
501 ))
502 .header(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/headers/char.h"))
503 .clang_arg("--target=x86_64-unknown-linux")
504 .generate()
505 .unwrap()
506 .to_string();
507
508 let (actual, stderr) = rustfmt(actual);
509 println!("{}", stderr);
510
511 let expected_filename = concat!(
512 env!("CARGO_MANIFEST_DIR"),
513 "/tests/expectations/tests/test_multiple_header_calls_in_builder.rs"
514 );
515 let expected = include_str!(concat!(
516 env!("CARGO_MANIFEST_DIR"),
517 "/tests/expectations/tests/test_multiple_header_calls_in_builder.rs"
518 ));
519 let (expected, _) = rustfmt(expected.to_string());
520
521 if actual != expected {
522 println!("Generated bindings differ from expected!");
523 error_diff_mismatch(
524 &actual,
525 &expected,
526 None,
527 Path::new(expected_filename),
528 )
529 .unwrap();
530 }
531 }
532
533 #[test]
test_multiple_header_contents()534 fn test_multiple_header_contents() {
535 let actual = builder()
536 .header_contents("test.h", "int foo(const char* a);")
537 .header_contents("test2.h", "float foo2(const char* b);")
538 .clang_arg("--target=x86_64-unknown-linux")
539 .generate()
540 .unwrap()
541 .to_string();
542
543 let (actual, stderr) = rustfmt(actual);
544 println!("{}", stderr);
545
546 let (expected, _) = rustfmt(
547 "extern \"C\" {
548 pub fn foo2(b: *const ::std::os::raw::c_char) -> f32;
549 }
550 extern \"C\" {
551 pub fn foo(a: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int;
552 }
553 "
554 .to_string(),
555 );
556
557 assert_eq!(expected, actual);
558 }
559
560 #[test]
test_mixed_header_and_header_contents()561 fn test_mixed_header_and_header_contents() {
562 let actual = builder()
563 .header(concat!(
564 env!("CARGO_MANIFEST_DIR"),
565 "/tests/headers/func_ptr.h"
566 ))
567 .header(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/headers/char.h"))
568 .header_contents("test.h", "int bar(const char* a);")
569 .header_contents("test2.h", "float bar2(const char* b);")
570 .clang_arg("--target=x86_64-unknown-linux")
571 .generate()
572 .unwrap()
573 .to_string();
574
575 let (actual, stderr) = rustfmt(actual);
576 println!("{}", stderr);
577
578 let expected_filename = concat!(
579 env!("CARGO_MANIFEST_DIR"),
580 "/tests/expectations/tests/test_mixed_header_and_header_contents.rs"
581 );
582 let expected = include_str!(concat!(
583 env!("CARGO_MANIFEST_DIR"),
584 "/tests/expectations/tests/test_mixed_header_and_header_contents.rs"
585 ));
586 let (expected, _) = rustfmt(expected.to_string());
587 if expected != actual {
588 error_diff_mismatch(
589 &actual,
590 &expected,
591 None,
592 Path::new(expected_filename),
593 )
594 .unwrap();
595 }
596 }
597
598 #[test]
599 // Doesn't support executing sh file on Windows.
600 // We may want to implement it in Rust so that we support all systems.
601 #[cfg(not(target_os = "windows"))]
no_system_header_includes()602 fn no_system_header_includes() {
603 use std::process::Command;
604 assert!(Command::new("../ci/no-includes.sh")
605 .current_dir(env!("CARGO_MANIFEST_DIR"))
606 .spawn()
607 .expect("should spawn ../ci/no-includes.sh OK")
608 .wait()
609 .expect("should wait for ../ci/no-includes OK")
610 .success());
611 }
612
613 #[test]
emit_depfile()614 fn emit_depfile() {
615 let header = PathBuf::from("tests/headers/enum-default-rust.h");
616 let expected_depfile = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
617 .join("tests")
618 .join("expectations")
619 .join("tests")
620 .join("enum-default-rust.d");
621 let observed_depfile = tempfile::NamedTempFile::new().unwrap();
622 let mut builder = create_bindgen_builder(&header).unwrap();
623 builder.builder = builder.builder.depfile(
624 "tests/expectations/tests/enum-default-rust.rs",
625 observed_depfile.path(),
626 );
627
628 let check_roundtrip =
629 env::var_os("BINDGEN_DISABLE_ROUNDTRIP_TEST").is_none();
630 let (builder, _roundtrip_builder) =
631 builder.into_builder(check_roundtrip).unwrap();
632 let _bindings = builder.generate().unwrap();
633
634 let observed = std::fs::read_to_string(observed_depfile).unwrap();
635 let expected = std::fs::read_to_string(expected_depfile).unwrap();
636 assert_eq!(observed.trim(), expected.trim());
637 }
638
639 #[test]
dump_preprocessed_input()640 fn dump_preprocessed_input() {
641 let arg_keyword =
642 concat!(env!("CARGO_MANIFEST_DIR"), "/tests/headers/arg_keyword.hpp");
643 let empty_layout = concat!(
644 env!("CARGO_MANIFEST_DIR"),
645 "/tests/headers/cpp-empty-layout.hpp"
646 );
647
648 builder()
649 .header(arg_keyword)
650 .header(empty_layout)
651 .dump_preprocessed_input()
652 .expect("should dump preprocessed input");
653
654 fn slurp(p: &str) -> String {
655 let mut contents = String::new();
656 let mut file = fs::File::open(p).unwrap();
657 file.read_to_string(&mut contents).unwrap();
658 contents
659 }
660
661 let bindgen_ii = slurp("__bindgen.ii");
662 let arg_keyword = slurp(arg_keyword);
663 let empty_layout = slurp(empty_layout);
664
665 assert!(
666 bindgen_ii.contains(&arg_keyword),
667 "arg_keyword.hpp is in the preprocessed file"
668 );
669 assert!(
670 bindgen_ii.contains(&empty_layout),
671 "cpp-empty-layout.hpp is in the preprocessed file"
672 );
673 }
674
675 #[test]
allowlist_warnings()676 fn allowlist_warnings() {
677 let header = concat!(
678 env!("CARGO_MANIFEST_DIR"),
679 "/tests/headers/allowlist_warnings.h"
680 );
681
682 let bindings = builder()
683 .header(header)
684 .allowlist_function("doesnt_match_anything")
685 .generate()
686 .expect("unable to generate bindings");
687
688 assert_eq!(1, bindings.warnings().len());
689 }
690
build_flags_output_helper(builder: &bindgen::Builder)691 fn build_flags_output_helper(builder: &bindgen::Builder) {
692 let mut command_line_flags = builder.command_line_flags();
693 command_line_flags.insert(0, "bindgen".to_string());
694
695 let flags_quoted: Vec<String> = command_line_flags
696 .iter()
697 .map(|x| format!("{}", shlex::quote(x)))
698 .collect();
699 let flags_str = flags_quoted.join(" ");
700 println!("{}", flags_str);
701
702 let (builder, _output, _verbose) =
703 crate::options::builder_from_flags(command_line_flags.into_iter())
704 .unwrap();
705 builder.generate().expect("failed to generate bindings");
706 }
707
708 #[test]
commandline_multiple_headers()709 fn commandline_multiple_headers() {
710 let bindings = bindgen::Builder::default()
711 .header("tests/headers/char.h")
712 .header("tests/headers/func_ptr.h")
713 .header("tests/headers/16-byte-alignment.h");
714 build_flags_output_helper(&bindings);
715 }
716
717 #[test]
test_wrap_static_fns()718 fn test_wrap_static_fns() {
719 // This test is for testing diffs of the generated C source and header files
720 // TODO: If another such feature is added, convert this test into a more generic
721 // test that looks at `tests/headers/generated` directory.
722 let expect_path = PathBuf::from("tests/expectations/tests/generated")
723 .join("wrap_static_fns");
724 println!("In path is ::: {}", expect_path.display());
725
726 let generated_path =
727 PathBuf::from(env::var("OUT_DIR").unwrap()).join("wrap_static_fns");
728 println!("Out path is ::: {}", generated_path.display());
729
730 let _bindings = Builder::default()
731 .header("tests/headers/wrap-static-fns.h")
732 .wrap_static_fns(true)
733 .wrap_static_fns_path(generated_path.display().to_string())
734 .generate()
735 .expect("Failed to generate bindings");
736
737 let expected_c = fs::read_to_string(expect_path.with_extension("c"))
738 .expect("Could not read generated wrap_static_fns.c");
739
740 let actual_c = fs::read_to_string(generated_path.with_extension("c"))
741 .expect("Could not read actual wrap_static_fns.c");
742
743 if expected_c != actual_c {
744 error_diff_mismatch(
745 &actual_c,
746 &expected_c,
747 None,
748 &expect_path.with_extension("c"),
749 )
750 .unwrap();
751 }
752 }
753