Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 1 | // Copyright 2022 Google LLC |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 4 | // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 5 | // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your |
| 6 | // option. This file may not be copied, modified, or distributed |
| 7 | // except according to those terms. |
| 8 | |
| 9 | use std::{ |
| 10 | ffi::OsStr, |
| 11 | fs::File, |
| 12 | io::{Read, Write}, |
| 13 | panic::RefUnwindSafe, |
| 14 | path::{Path, PathBuf}, |
| 15 | sync::Mutex, |
| 16 | }; |
| 17 | |
| 18 | use autocxx_engine::{ |
| 19 | Builder, BuilderBuild, BuilderContext, BuilderError, RebuildDependencyRecorder, HEADER, |
| 20 | }; |
| 21 | use log::info; |
| 22 | use once_cell::sync::OnceCell; |
| 23 | use proc_macro2::{Span, TokenStream}; |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 24 | use quote::{format_ident, quote, TokenStreamExt}; |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 25 | use syn::Token; |
| 26 | use tempfile::{tempdir, TempDir}; |
| 27 | |
| 28 | const KEEP_TEMPDIRS: bool = false; |
| 29 | |
| 30 | /// API to run a documentation test. Panics if the test fails. |
| 31 | /// Guarantees not to emit anything to stdout and so can be run in an mdbook context. |
| 32 | pub fn doctest( |
| 33 | cxx_code: &str, |
| 34 | header_code: &str, |
| 35 | rust_code: TokenStream, |
| 36 | manifest_dir: &OsStr, |
| 37 | ) -> Result<(), TestError> { |
| 38 | std::env::set_var("CARGO_PKG_NAME", "autocxx-integration-tests"); |
| 39 | std::env::set_var("CARGO_MANIFEST_DIR", manifest_dir); |
| 40 | do_run_test_manual(cxx_code, header_code, rust_code, None, None) |
| 41 | } |
| 42 | |
| 43 | fn configure_builder(b: &mut BuilderBuild) -> &mut BuilderBuild { |
| 44 | let target = rust_info::get().target_triple.unwrap(); |
| 45 | b.host(&target) |
| 46 | .target(&target) |
| 47 | .opt_level(1) |
| 48 | .flag("-std=c++14") // For clang |
| 49 | .flag_if_supported("/GX") // Enable C++ exceptions for msvc |
| 50 | .flag_if_supported("-Wall") |
| 51 | .flag_if_supported("-Werror") |
| 52 | } |
| 53 | |
| 54 | /// What environment variables we should set in order to tell rustc how to find |
| 55 | /// the Rust code. |
| 56 | pub enum RsFindMode { |
| 57 | AutocxxRs, |
| 58 | AutocxxRsArchive, |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 59 | AutocxxRsFile, |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 60 | /// This just calls the callback instead of setting any environment variables. The callback |
| 61 | /// receives the path to the temporary directory. |
| 62 | Custom(Box<dyn FnOnce(&Path)>), |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 63 | } |
| 64 | |
| 65 | /// API to test building pre-generated files. |
| 66 | pub fn build_from_folder( |
| 67 | folder: &Path, |
| 68 | main_rs_file: &Path, |
| 69 | generated_rs_files: Vec<PathBuf>, |
| 70 | cpp_files: &[&str], |
| 71 | rs_find_mode: RsFindMode, |
| 72 | ) -> Result<(), TestError> { |
| 73 | let target_dir = folder.join("target"); |
| 74 | std::fs::create_dir(&target_dir).unwrap(); |
| 75 | let mut b = BuilderBuild::new(); |
| 76 | for cpp_file in cpp_files.iter() { |
| 77 | b.file(folder.join(cpp_file)); |
| 78 | } |
| 79 | configure_builder(&mut b) |
| 80 | .out_dir(&target_dir) |
| 81 | .include(folder) |
| 82 | .include(folder.join("demo")) |
| 83 | .try_compile("autocxx-demo") |
| 84 | .map_err(TestError::CppBuild)?; |
| 85 | // use the trybuild crate to build the Rust file. |
| 86 | let r = get_builder().lock().unwrap().build( |
| 87 | &target_dir, |
| 88 | "autocxx-demo", |
| 89 | &folder, |
| 90 | &["input.h", "cxx.h"], |
| 91 | &main_rs_file, |
| 92 | generated_rs_files, |
| 93 | rs_find_mode, |
| 94 | ); |
| 95 | if r.is_err() { |
| 96 | return Err(TestError::RsBuild); // details of Rust panic are a bit messy to include, and |
| 97 | // not important at the moment. |
| 98 | } |
| 99 | Ok(()) |
| 100 | } |
| 101 | |
| 102 | fn get_builder() -> &'static Mutex<LinkableTryBuilder> { |
| 103 | static INSTANCE: OnceCell<Mutex<LinkableTryBuilder>> = OnceCell::new(); |
| 104 | INSTANCE.get_or_init(|| Mutex::new(LinkableTryBuilder::new())) |
| 105 | } |
| 106 | |
| 107 | /// TryBuild which maintains a directory of libraries to link. |
| 108 | /// This is desirable because otherwise, if we alter the RUSTFLAGS |
| 109 | /// then trybuild rebuilds *everything* including all the dev-dependencies. |
| 110 | /// This object exists purely so that we use the same RUSTFLAGS for every |
| 111 | /// test case. |
| 112 | struct LinkableTryBuilder { |
| 113 | /// Directory in which we'll keep any linkable libraries |
| 114 | temp_dir: TempDir, |
| 115 | } |
| 116 | |
| 117 | impl LinkableTryBuilder { |
| 118 | fn new() -> Self { |
| 119 | LinkableTryBuilder { |
| 120 | temp_dir: tempdir().unwrap(), |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | fn move_items_into_temp_dir<P1: AsRef<Path>>(&self, src_path: &P1, pattern: &str) { |
| 125 | for item in std::fs::read_dir(src_path).unwrap() { |
| 126 | let item = item.unwrap(); |
| 127 | if item.file_name().into_string().unwrap().contains(pattern) { |
| 128 | let dest = self.temp_dir.path().join(item.file_name()); |
| 129 | if dest.exists() { |
| 130 | std::fs::remove_file(&dest).unwrap(); |
| 131 | } |
| 132 | if KEEP_TEMPDIRS { |
| 133 | std::fs::copy(item.path(), dest).unwrap(); |
| 134 | } else { |
| 135 | std::fs::rename(item.path(), dest).unwrap(); |
| 136 | } |
| 137 | } |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | #[allow(clippy::too_many_arguments)] |
| 142 | fn build<P1: AsRef<Path>, P2: AsRef<Path>, P3: AsRef<Path> + RefUnwindSafe>( |
| 143 | &self, |
| 144 | library_path: &P1, |
| 145 | library_name: &str, |
| 146 | header_path: &P2, |
| 147 | header_names: &[&str], |
| 148 | rs_path: &P3, |
| 149 | generated_rs_files: Vec<PathBuf>, |
| 150 | rs_find_mode: RsFindMode, |
| 151 | ) -> std::thread::Result<()> { |
| 152 | // Copy all items from the source dir into our temporary dir if their name matches |
| 153 | // the pattern given in `library_name`. |
| 154 | self.move_items_into_temp_dir(library_path, library_name); |
| 155 | for header_name in header_names { |
| 156 | self.move_items_into_temp_dir(header_path, header_name); |
| 157 | } |
| 158 | for generated_rs in generated_rs_files { |
| 159 | self.move_items_into_temp_dir( |
| 160 | &generated_rs.parent().unwrap(), |
| 161 | generated_rs.file_name().unwrap().to_str().unwrap(), |
| 162 | ); |
| 163 | } |
| 164 | let temp_path = self.temp_dir.path().to_str().unwrap(); |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 165 | let mut rustflags = format!("-L {temp_path}"); |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 166 | if std::env::var_os("AUTOCXX_ASAN").is_some() { |
| 167 | rustflags.push_str(" -Z sanitizer=address -Clinker=clang++ -Clink-arg=-fuse-ld=lld"); |
| 168 | } |
| 169 | std::env::set_var("RUSTFLAGS", rustflags); |
| 170 | match rs_find_mode { |
| 171 | RsFindMode::AutocxxRs => std::env::set_var("AUTOCXX_RS", temp_path), |
| 172 | RsFindMode::AutocxxRsArchive => std::env::set_var( |
| 173 | "AUTOCXX_RS_JSON_ARCHIVE", |
| 174 | self.temp_dir.path().join("gen.rs.json"), |
| 175 | ), |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 176 | RsFindMode::AutocxxRsFile => std::env::set_var( |
| 177 | "AUTOCXX_RS_FILE", |
| 178 | self.temp_dir.path().join("gen0.include.rs"), |
| 179 | ), |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 180 | RsFindMode::Custom(f) => f(self.temp_dir.path()), |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 181 | }; |
| 182 | std::panic::catch_unwind(|| { |
| 183 | let test_cases = trybuild::TestCases::new(); |
| 184 | test_cases.pass(rs_path) |
| 185 | }) |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | fn write_to_file(tdir: &TempDir, filename: &str, content: &str) -> PathBuf { |
| 190 | let path = tdir.path().join(filename); |
| 191 | let mut f = File::create(&path).unwrap(); |
| 192 | f.write_all(content.as_bytes()).unwrap(); |
| 193 | path |
| 194 | } |
| 195 | |
| 196 | /// A positive test, we expect to pass. |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 197 | #[track_caller] |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 198 | pub fn run_test( |
| 199 | cxx_code: &str, |
| 200 | header_code: &str, |
| 201 | rust_code: TokenStream, |
| 202 | generate: &[&str], |
| 203 | generate_pods: &[&str], |
| 204 | ) { |
| 205 | do_run_test( |
| 206 | cxx_code, |
| 207 | header_code, |
| 208 | rust_code, |
| 209 | directives_from_lists(generate, generate_pods, None), |
| 210 | None, |
| 211 | None, |
| 212 | None, |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 213 | "unsafe_ffi", |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 214 | None, |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 215 | ) |
| 216 | .unwrap() |
| 217 | } |
| 218 | |
| 219 | // A trait for objects which can check the output of the code creation |
| 220 | // process. |
| 221 | pub trait CodeCheckerFns { |
| 222 | fn check_rust(&self, _rs: syn::File) -> Result<(), TestError> { |
| 223 | Ok(()) |
| 224 | } |
| 225 | fn check_cpp(&self, _cpp: &[PathBuf]) -> Result<(), TestError> { |
| 226 | Ok(()) |
| 227 | } |
| 228 | fn skip_build(&self) -> bool { |
| 229 | false |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | // A function applied to the resultant generated Rust code |
| 234 | // which can be used to inspect that code. |
| 235 | pub type CodeChecker = Box<dyn CodeCheckerFns>; |
| 236 | |
| 237 | // A trait for objects which can modify builders for testing purposes. |
| 238 | pub trait BuilderModifierFns { |
| 239 | fn modify_autocxx_builder<'a>( |
| 240 | &self, |
| 241 | builder: Builder<'a, TestBuilderContext>, |
| 242 | ) -> Builder<'a, TestBuilderContext>; |
| 243 | fn modify_cc_builder<'a>(&self, builder: &'a mut cc::Build) -> &'a mut cc::Build { |
| 244 | builder |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | pub type BuilderModifier = Box<dyn BuilderModifierFns>; |
| 249 | |
| 250 | /// A positive test, we expect to pass. |
| 251 | #[allow(clippy::too_many_arguments)] // least typing for each test |
| 252 | pub fn run_test_ex( |
| 253 | cxx_code: &str, |
| 254 | header_code: &str, |
| 255 | rust_code: TokenStream, |
| 256 | directives: TokenStream, |
| 257 | builder_modifier: Option<BuilderModifier>, |
| 258 | code_checker: Option<CodeChecker>, |
| 259 | extra_rust: Option<TokenStream>, |
| 260 | ) { |
| 261 | do_run_test( |
| 262 | cxx_code, |
| 263 | header_code, |
| 264 | rust_code, |
| 265 | directives, |
| 266 | builder_modifier, |
| 267 | code_checker, |
| 268 | extra_rust, |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 269 | "unsafe_ffi", |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 270 | None, |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 271 | ) |
| 272 | .unwrap() |
| 273 | } |
| 274 | |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 275 | pub fn run_generate_all_test(header_code: &str) { |
| 276 | run_test_ex( |
| 277 | "", |
| 278 | header_code, |
| 279 | quote! {}, |
| 280 | quote! { generate_all!() }, |
| 281 | None, |
| 282 | None, |
| 283 | None, |
| 284 | ); |
| 285 | } |
| 286 | |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 287 | pub fn run_test_expect_fail( |
| 288 | cxx_code: &str, |
| 289 | header_code: &str, |
| 290 | rust_code: TokenStream, |
| 291 | generate: &[&str], |
| 292 | generate_pods: &[&str], |
| 293 | ) { |
| 294 | do_run_test( |
| 295 | cxx_code, |
| 296 | header_code, |
| 297 | rust_code, |
| 298 | directives_from_lists(generate, generate_pods, None), |
| 299 | None, |
| 300 | None, |
| 301 | None, |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 302 | "unsafe_ffi", |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 303 | None, |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 304 | ) |
| 305 | .expect_err("Unexpected success"); |
| 306 | } |
| 307 | |
| 308 | pub fn run_test_expect_fail_ex( |
| 309 | cxx_code: &str, |
| 310 | header_code: &str, |
| 311 | rust_code: TokenStream, |
| 312 | directives: TokenStream, |
| 313 | builder_modifier: Option<BuilderModifier>, |
| 314 | code_checker: Option<CodeChecker>, |
| 315 | extra_rust: Option<TokenStream>, |
| 316 | ) { |
| 317 | do_run_test( |
| 318 | cxx_code, |
| 319 | header_code, |
| 320 | rust_code, |
| 321 | directives, |
| 322 | builder_modifier, |
| 323 | code_checker, |
| 324 | extra_rust, |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 325 | "unsafe_ffi", |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 326 | None, |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 327 | ) |
| 328 | .expect_err("Unexpected success"); |
| 329 | } |
| 330 | |
| 331 | /// In the future maybe the tests will distinguish the exact type of failure expected. |
| 332 | #[derive(Debug)] |
| 333 | pub enum TestError { |
| 334 | AutoCxx(BuilderError), |
| 335 | CppBuild(cc::Error), |
| 336 | RsBuild, |
| 337 | NoRs, |
| 338 | RsFileOpen(std::io::Error), |
| 339 | RsFileRead(std::io::Error), |
| 340 | RsFileParse(syn::Error), |
| 341 | RsCodeExaminationFail(String), |
| 342 | CppCodeExaminationFail, |
| 343 | } |
| 344 | |
| 345 | pub fn directives_from_lists( |
| 346 | generate: &[&str], |
| 347 | generate_pods: &[&str], |
| 348 | extra_directives: Option<TokenStream>, |
| 349 | ) -> TokenStream { |
| 350 | let generate = generate.iter().map(|s| { |
| 351 | quote! { |
| 352 | generate!(#s) |
| 353 | } |
| 354 | }); |
| 355 | let generate_pods = generate_pods.iter().map(|s| { |
| 356 | quote! { |
| 357 | generate_pod!(#s) |
| 358 | } |
| 359 | }); |
| 360 | quote! { |
| 361 | #(#generate)* |
| 362 | #(#generate_pods)* |
| 363 | #extra_directives |
| 364 | } |
| 365 | } |
| 366 | |
| 367 | #[allow(clippy::too_many_arguments)] // least typing for each test |
| 368 | pub fn do_run_test( |
| 369 | cxx_code: &str, |
| 370 | header_code: &str, |
| 371 | rust_code: TokenStream, |
| 372 | directives: TokenStream, |
| 373 | builder_modifier: Option<BuilderModifier>, |
| 374 | rust_code_checker: Option<CodeChecker>, |
| 375 | extra_rust: Option<TokenStream>, |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 376 | safety_policy: &str, |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 377 | module_attributes: Option<TokenStream>, |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 378 | ) -> Result<(), TestError> { |
| 379 | let hexathorpe = Token); |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 380 | let safety_policy = format_ident!("{}", safety_policy); |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 381 | let unexpanded_rust = quote! { |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 382 | #module_attributes |
| 383 | |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 384 | use autocxx::prelude::*; |
| 385 | |
| 386 | include_cpp!( |
| 387 | #hexathorpe include "input.h" |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 388 | safety!(#safety_policy) |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 389 | #directives |
| 390 | ); |
| 391 | |
| 392 | #extra_rust |
| 393 | |
| 394 | fn main() { |
| 395 | #rust_code |
| 396 | } |
| 397 | |
| 398 | }; |
| 399 | do_run_test_manual( |
| 400 | cxx_code, |
| 401 | header_code, |
| 402 | unexpanded_rust, |
| 403 | builder_modifier, |
| 404 | rust_code_checker, |
| 405 | ) |
| 406 | } |
| 407 | |
| 408 | /// The [`BuilderContext`] used in autocxx's integration tests. |
| 409 | pub struct TestBuilderContext; |
| 410 | |
| 411 | impl BuilderContext for TestBuilderContext { |
| 412 | fn get_dependency_recorder() -> Option<Box<dyn RebuildDependencyRecorder>> { |
| 413 | None |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | pub fn do_run_test_manual( |
| 418 | cxx_code: &str, |
| 419 | header_code: &str, |
| 420 | mut rust_code: TokenStream, |
| 421 | builder_modifier: Option<BuilderModifier>, |
| 422 | rust_code_checker: Option<CodeChecker>, |
| 423 | ) -> Result<(), TestError> { |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 424 | let builder_modifier = consider_forcing_wrapper_generation(builder_modifier); |
| 425 | |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 426 | const HEADER_NAME: &str = "input.h"; |
| 427 | // Step 2: Write the C++ header snippet to a temp file |
| 428 | let tdir = tempdir().unwrap(); |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 429 | write_to_file(&tdir, HEADER_NAME, &format!("#pragma once\n{header_code}")); |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 430 | write_to_file(&tdir, "cxx.h", HEADER); |
| 431 | |
| 432 | rust_code.append_all(quote! { |
| 433 | #[link(name="autocxx-demo")] |
| 434 | extern {} |
| 435 | }); |
| 436 | info!("Unexpanded Rust: {}", rust_code); |
| 437 | |
| 438 | let write_rust_to_file = |ts: &TokenStream| -> PathBuf { |
| 439 | // Step 3: Write the Rust code to a temp file |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 440 | let rs_code = format!("{ts}"); |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 441 | write_to_file(&tdir, "input.rs", &rs_code) |
| 442 | }; |
| 443 | |
| 444 | let target_dir = tdir.path().join("target"); |
| 445 | std::fs::create_dir(&target_dir).unwrap(); |
| 446 | |
| 447 | let rs_path = write_rust_to_file(&rust_code); |
| 448 | |
| 449 | info!("Path is {:?}", tdir.path()); |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 450 | let builder = Builder::<TestBuilderContext>::new(&rs_path, [tdir.path()]) |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 451 | .custom_gendir(target_dir.clone()); |
| 452 | let builder = if let Some(builder_modifier) = &builder_modifier { |
| 453 | builder_modifier.modify_autocxx_builder(builder) |
| 454 | } else { |
| 455 | builder |
| 456 | }; |
| 457 | let build_results = builder.build_listing_files().map_err(TestError::AutoCxx)?; |
| 458 | let mut b = build_results.0; |
| 459 | let generated_rs_files = build_results.1; |
| 460 | |
| 461 | if let Some(code_checker) = &rust_code_checker { |
| 462 | let mut file = File::open(generated_rs_files.get(0).ok_or(TestError::NoRs)?) |
| 463 | .map_err(TestError::RsFileOpen)?; |
| 464 | let mut content = String::new(); |
| 465 | file.read_to_string(&mut content) |
| 466 | .map_err(TestError::RsFileRead)?; |
| 467 | |
| 468 | let ast = syn::parse_file(&content).map_err(TestError::RsFileParse)?; |
| 469 | code_checker.check_rust(ast)?; |
| 470 | code_checker.check_cpp(&build_results.2)?; |
| 471 | if code_checker.skip_build() { |
| 472 | return Ok(()); |
| 473 | } |
| 474 | } |
| 475 | |
| 476 | if !cxx_code.is_empty() { |
| 477 | // Step 4: Write the C++ code snippet to a .cc file, along with a #include |
| 478 | // of the header emitted in step 5. |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 479 | let cxx_code = format!("#include \"input.h\"\n#include \"cxxgen.h\"\n{cxx_code}"); |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 480 | let cxx_path = write_to_file(&tdir, "input.cxx", &cxx_code); |
| 481 | b.file(cxx_path); |
| 482 | } |
| 483 | |
| 484 | let b = configure_builder(&mut b).out_dir(&target_dir); |
| 485 | let b = if let Some(builder_modifier) = builder_modifier { |
| 486 | builder_modifier.modify_cc_builder(b) |
| 487 | } else { |
| 488 | b |
| 489 | }; |
| 490 | b.include(tdir.path()) |
| 491 | .try_compile("autocxx-demo") |
| 492 | .map_err(TestError::CppBuild)?; |
| 493 | if KEEP_TEMPDIRS { |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 494 | println!("Generated .rs files: {generated_rs_files:?}"); |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 495 | } |
| 496 | // Step 8: use the trybuild crate to build the Rust file. |
| 497 | let r = get_builder().lock().unwrap().build( |
| 498 | &target_dir, |
| 499 | "autocxx-demo", |
| 500 | &tdir.path(), |
| 501 | &["input.h", "cxx.h"], |
| 502 | &rs_path, |
| 503 | generated_rs_files, |
| 504 | RsFindMode::AutocxxRs, |
| 505 | ); |
| 506 | if KEEP_TEMPDIRS { |
| 507 | println!("Tempdir: {:?}", tdir.into_path().to_str()); |
| 508 | } |
| 509 | if r.is_err() { |
| 510 | return Err(TestError::RsBuild); // details of Rust panic are a bit messy to include, and |
| 511 | // not important at the moment. |
| 512 | } |
| 513 | Ok(()) |
| 514 | } |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame] | 515 | |
| 516 | /// If AUTOCXX_FORCE_WRAPPER_GENERATION is set, always force both C++ |
| 517 | /// and Rust side shims, for extra testing of obscure code paths. |
| 518 | fn consider_forcing_wrapper_generation( |
| 519 | existing_builder_modifier: Option<BuilderModifier>, |
| 520 | ) -> Option<BuilderModifier> { |
| 521 | if std::env::var("AUTOCXX_FORCE_WRAPPER_GENERATION").is_err() { |
| 522 | existing_builder_modifier |
| 523 | } else { |
| 524 | Some(Box::new(ForceWrapperGeneration(existing_builder_modifier))) |
| 525 | } |
| 526 | } |
| 527 | |
| 528 | struct ForceWrapperGeneration(Option<BuilderModifier>); |
| 529 | |
| 530 | impl BuilderModifierFns for ForceWrapperGeneration { |
| 531 | fn modify_autocxx_builder<'a>( |
| 532 | &self, |
| 533 | builder: Builder<'a, TestBuilderContext>, |
| 534 | ) -> Builder<'a, TestBuilderContext> { |
| 535 | let builder = builder.force_wrapper_generation(true); |
| 536 | if let Some(modifier) = &self.0 { |
| 537 | modifier.modify_autocxx_builder(builder) |
| 538 | } else { |
| 539 | builder |
| 540 | } |
| 541 | } |
| 542 | fn modify_cc_builder<'a>(&self, builder: &'a mut cc::Build) -> &'a mut cc::Build { |
| 543 | if let Some(modifier) = &self.0 { |
| 544 | modifier.modify_cc_builder(builder) |
| 545 | } else { |
| 546 | builder |
| 547 | } |
| 548 | } |
| 549 | } |