tests.rs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. extern crate cbindgen;
  2. use cbindgen::*;
  3. use std::collections::HashSet;
  4. use std::path::Path;
  5. use std::process::Command;
  6. use std::{env, fs, str};
  7. fn style_str(style: Style) -> &'static str {
  8. match style {
  9. Style::Both => "both",
  10. Style::Tag => "tag",
  11. Style::Type => "type",
  12. }
  13. }
  14. fn run_cbindgen(
  15. cbindgen_path: &'static str,
  16. path: &Path,
  17. output: &Path,
  18. language: Language,
  19. cpp_compat: bool,
  20. style: Option<Style>,
  21. ) -> Vec<u8> {
  22. let program = Path::new(cbindgen_path);
  23. let mut command = Command::new(&program);
  24. match language {
  25. Language::Cxx => {}
  26. Language::C => {
  27. command.arg("--lang").arg("c");
  28. if cpp_compat {
  29. command.arg("--cpp-compat");
  30. }
  31. }
  32. Language::Cython => {
  33. command.arg("--lang").arg("cython");
  34. }
  35. }
  36. if let Some(style) = style {
  37. command.arg("--style").arg(style_str(style));
  38. }
  39. let config = path.with_extension("toml");
  40. if config.exists() {
  41. command.arg("--config").arg(config);
  42. }
  43. command.arg(path);
  44. println!("Running: {:?}", command);
  45. let cbindgen_output = command.output().expect("failed to execute process");
  46. assert!(
  47. cbindgen_output.status.success(),
  48. "cbindgen failed: {:?} with error: {}",
  49. output,
  50. str::from_utf8(&cbindgen_output.stderr).unwrap_or_default()
  51. );
  52. cbindgen_output.stdout
  53. }
  54. fn compile(
  55. cbindgen_output: &Path,
  56. tests_path: &Path,
  57. tmp_dir: &Path,
  58. language: Language,
  59. style: Option<Style>,
  60. skip_warning_as_error: bool,
  61. ) {
  62. let cc = match language {
  63. Language::Cxx => env::var("CXX").unwrap_or_else(|_| "g++".to_owned()),
  64. Language::C => env::var("CC").unwrap_or_else(|_| "gcc".to_owned()),
  65. Language::Cython => env::var("CYTHON").unwrap_or_else(|_| "cython".to_owned()),
  66. };
  67. let file_name = cbindgen_output
  68. .file_name()
  69. .expect("cbindgen output should be a file");
  70. let mut object = tmp_dir.join(file_name);
  71. object.set_extension("o");
  72. let mut command = Command::new(cc);
  73. match language {
  74. Language::Cxx | Language::C => {
  75. command.arg("-D").arg("DEFINED");
  76. command.arg("-I").arg(tests_path);
  77. command.arg("-Wall");
  78. if !skip_warning_as_error {
  79. command.arg("-Werror");
  80. }
  81. // `swift_name` is not recognzied by gcc.
  82. command.arg("-Wno-attributes");
  83. // clang warns about unused const variables.
  84. command.arg("-Wno-unused-const-variable");
  85. // clang also warns about returning non-instantiated templates (they could
  86. // be specialized, but they're not so it's fine).
  87. command.arg("-Wno-return-type-c-linkage");
  88. if let Language::Cxx = language {
  89. // enum class is a c++11 extension which makes g++ on macos 10.14 error out
  90. // inline variables are are a c++17 extension
  91. command.arg("-std=c++17");
  92. // Prevents warnings when compiling .c files as c++.
  93. command.arg("-x").arg("c++");
  94. if let Ok(extra_flags) = env::var("CXXFLAGS") {
  95. command.args(extra_flags.split_whitespace());
  96. }
  97. } else if let Ok(extra_flags) = env::var("CFLAGS") {
  98. command.args(extra_flags.split_whitespace());
  99. }
  100. if let Some(style) = style {
  101. command.arg("-D");
  102. command.arg(format!(
  103. "CBINDGEN_STYLE_{}",
  104. style_str(style).to_uppercase()
  105. ));
  106. }
  107. command.arg("-o").arg(&object);
  108. command.arg("-c").arg(cbindgen_output);
  109. }
  110. Language::Cython => {
  111. command.arg("-Wextra");
  112. if !skip_warning_as_error {
  113. command.arg("-Werror");
  114. }
  115. command.arg("-3");
  116. command.arg("-o").arg(&object);
  117. command.arg(cbindgen_output);
  118. }
  119. }
  120. println!("Running: {:?}", command);
  121. let out = command.output().expect("failed to compile");
  122. assert!(out.status.success(), "Output failed to compile: {:?}", out);
  123. if object.exists() {
  124. fs::remove_file(object).unwrap();
  125. }
  126. }
  127. const SKIP_WARNING_AS_ERROR_SUFFIX: &str = ".skip_warning_as_error";
  128. #[allow(clippy::too_many_arguments)]
  129. fn run_compile_test(
  130. cbindgen_path: &'static str,
  131. name: &'static str,
  132. path: &Path,
  133. tmp_dir: &Path,
  134. language: Language,
  135. cpp_compat: bool,
  136. style: Option<Style>,
  137. cbindgen_outputs: &mut HashSet<Vec<u8>>,
  138. ) {
  139. let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
  140. let tests_path = Path::new(&crate_dir).join("tests");
  141. let mut generated_file = tests_path.join("expectations");
  142. fs::create_dir_all(&generated_file).unwrap();
  143. let style_ext = style
  144. .map(|style| match style {
  145. Style::Both => ".both",
  146. Style::Tag => ".tag",
  147. Style::Type => "",
  148. })
  149. .unwrap_or_default();
  150. let lang_ext = match language {
  151. Language::Cxx => ".cpp",
  152. Language::C if cpp_compat => ".compat.c",
  153. Language::C => ".c",
  154. // cbindgen is supposed to generate declaration files (`.pxd`), but `cython` compiler
  155. // is extension-sensitive and won't work on them, so we use implementation files (`.pyx`)
  156. // in the test suite.
  157. Language::Cython => ".pyx",
  158. };
  159. let skip_warning_as_error = name.rfind(SKIP_WARNING_AS_ERROR_SUFFIX).is_some();
  160. let source_file =
  161. format!("{}{}{}", name, style_ext, lang_ext).replace(SKIP_WARNING_AS_ERROR_SUFFIX, "");
  162. generated_file.push(source_file);
  163. let cbindgen_output = run_cbindgen(
  164. cbindgen_path,
  165. path,
  166. &generated_file,
  167. language,
  168. cpp_compat,
  169. style,
  170. );
  171. if cbindgen_outputs.contains(&cbindgen_output) {
  172. // We already generated an identical file previously.
  173. if env::var_os("CBINDGEN_TEST_VERIFY").is_some() {
  174. assert!(!generated_file.exists());
  175. } else if generated_file.exists() {
  176. fs::remove_file(&generated_file).unwrap();
  177. }
  178. } else {
  179. if env::var_os("CBINDGEN_TEST_VERIFY").is_some() {
  180. let prev_cbindgen_output = fs::read(&generated_file).unwrap();
  181. assert_eq!(cbindgen_output, prev_cbindgen_output);
  182. } else {
  183. fs::write(&generated_file, &cbindgen_output).unwrap();
  184. }
  185. cbindgen_outputs.insert(cbindgen_output);
  186. compile(
  187. &generated_file,
  188. &tests_path,
  189. tmp_dir,
  190. language,
  191. style,
  192. skip_warning_as_error,
  193. );
  194. if language == Language::C && cpp_compat {
  195. compile(
  196. &generated_file,
  197. &tests_path,
  198. tmp_dir,
  199. Language::Cxx,
  200. style,
  201. skip_warning_as_error,
  202. );
  203. }
  204. }
  205. }
  206. fn test_file(cbindgen_path: &'static str, name: &'static str, filename: &'static str) {
  207. let test = Path::new(filename);
  208. let tmp_dir = tempfile::Builder::new()
  209. .prefix("cbindgen-test-output")
  210. .tempdir()
  211. .expect("Creating tmp dir failed");
  212. let tmp_dir = tmp_dir.path();
  213. // Run tests in deduplication priority order. C++ compatibility tests are run first,
  214. // otherwise we would lose the C++ compiler run if they were deduplicated.
  215. let mut cbindgen_outputs = HashSet::new();
  216. for cpp_compat in &[true, false] {
  217. for style in &[Style::Type, Style::Tag, Style::Both] {
  218. run_compile_test(
  219. cbindgen_path,
  220. name,
  221. test,
  222. tmp_dir,
  223. Language::C,
  224. *cpp_compat,
  225. Some(*style),
  226. &mut cbindgen_outputs,
  227. );
  228. }
  229. }
  230. run_compile_test(
  231. cbindgen_path,
  232. name,
  233. test,
  234. tmp_dir,
  235. Language::Cxx,
  236. /* cpp_compat = */ false,
  237. None,
  238. &mut HashSet::new(),
  239. );
  240. // `Style::Both` should be identical to `Style::Tag` for Cython.
  241. let mut cbindgen_outputs = HashSet::new();
  242. for style in &[Style::Type, Style::Tag] {
  243. run_compile_test(
  244. cbindgen_path,
  245. name,
  246. test,
  247. tmp_dir,
  248. Language::Cython,
  249. /* cpp_compat = */ false,
  250. Some(*style),
  251. &mut cbindgen_outputs,
  252. );
  253. }
  254. }
  255. macro_rules! test_file {
  256. ($cbindgen_path:expr, $test_function_name:ident, $name:expr, $file:tt) => {
  257. #[test]
  258. fn $test_function_name() {
  259. test_file($cbindgen_path, $name, $file);
  260. }
  261. };
  262. }
  263. // This file is generated by build.rs
  264. include!(concat!(env!("OUT_DIR"), "/tests.rs"));