• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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