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