blob: 31d2ab09865b094010eb71c159afbf6348f98c01 [file] [log] [blame]
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -07001// Part of the Crubit project, under the Apache License v2.0 with LLVM
2// Exceptions. See /LICENSE for license information.
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
Lukasz Anforowicz2293ef12022-09-29 13:37:05 -07005#![feature(never_type)]
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -07006#![feature(rustc_private)]
7#![deny(rustc::internal)]
8
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -07009extern crate rustc_driver;
10extern crate rustc_error_codes;
11extern crate rustc_errors;
Lukasz Anforowicz5bddf182022-09-30 16:06:59 -070012extern crate rustc_feature;
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -070013extern crate rustc_hir;
14extern crate rustc_interface;
Lukasz Anforowicz88bde9b2022-10-14 14:48:07 -070015extern crate rustc_lint_defs;
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -070016extern crate rustc_middle;
17extern crate rustc_session;
18extern crate rustc_span;
Lukasz Anforowicze4333062022-10-17 14:47:53 -070019extern crate rustc_target;
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -070020
Lukasz Anforowicz13794df2022-10-21 07:56:34 -070021// TODO(b/254679226): `bindings` and `cmdline` should be separate crates.
Lukasz Anforowicze7874b52022-09-30 16:52:28 -070022mod bindings;
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -070023mod cmdline;
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -070024
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070025use anyhow::Context;
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -070026use itertools::Itertools;
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070027use rustc_middle::ty::TyCtxt;
28use std::path::Path;
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -070029
Lukasz Anforowicze7874b52022-09-30 16:52:28 -070030use bindings::GeneratedBindings;
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070031use cmdline::Cmdline;
Lukasz Anforowicz5c081b22022-11-11 08:40:17 -080032use token_stream_printer::{
33 cc_tokens_to_formatted_string, rs_tokens_to_formatted_string, RustfmtConfig,
34};
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -070035
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070036/// This mostly wraps and simplifies a subset of APIs from the `rustc_driver`
37/// module.
Lukasz Anforowiczc3452842022-09-23 08:02:58 -070038mod bindings_driver {
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -070039
Lukasz Anforowicz76e26232022-10-18 17:26:28 -070040 use anyhow::anyhow;
Lukasz Anforowicz31ca0492022-09-28 15:55:23 -070041 use either::Either;
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -070042 use rustc_interface::interface::Compiler;
43 use rustc_interface::Queries;
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070044 use rustc_middle::ty::TyCtxt;
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -070045
Lukasz Anforowicze7874b52022-09-30 16:52:28 -070046 use crate::bindings::enter_tcx;
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -070047
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070048 /// Wrapper around `rustc_driver::RunCompiler::run` that exposes a
49 /// simplified API:
50 /// - Takes a `callback` that will be invoked from within Rust compiler,
51 /// after parsing and analysis are done,
Lukasz Anforowiczca87e472022-10-14 14:58:30 -070052 /// - Compilation will stop after parsing, analysis, and the `callback` are
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070053 /// done,
54 /// - Returns the combined results from the Rust compiler *and* the
55 /// `callback`.
56 pub fn run_after_analysis_and_stop<F, R>(
57 rustc_args: &[String],
58 callback: F,
59 ) -> anyhow::Result<R>
60 where
61 F: FnOnce(TyCtxt) -> anyhow::Result<R> + Send,
62 R: Send,
63 {
64 AfterAnalysisCallback::new(rustc_args, callback).run()
65 }
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -070066
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070067 struct AfterAnalysisCallback<'a, F, R>
68 where
69 F: FnOnce(TyCtxt) -> anyhow::Result<R> + Send,
70 R: Send,
71 {
72 args: &'a [String],
Lukasz Anforowicz31ca0492022-09-28 15:55:23 -070073 callback_or_result: Either<F, anyhow::Result<R>>,
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070074 }
75
76 impl<'a, F, R> AfterAnalysisCallback<'a, F, R>
77 where
78 F: FnOnce(TyCtxt) -> anyhow::Result<R> + Send,
79 R: Send,
80 {
81 fn new(args: &'a [String], callback: F) -> Self {
Lukasz Anforowicz31ca0492022-09-28 15:55:23 -070082 Self { args, callback_or_result: Either::Left(callback) }
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -070083 }
84
Lukasz Anforowiczca87e472022-10-14 14:58:30 -070085 /// Runs Rust compiler, and then invokes the stored callback (with
86 /// `TyCtxt` of the parsed+analyzed Rust crate as the callback's
87 /// argument), and then finally returns the combined results
88 /// from Rust compiler *and* the callback.
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070089 fn run(mut self) -> anyhow::Result<R> {
Lukasz Anforowiczc3452842022-09-23 08:02:58 -070090 // Rust compiler unwinds with a special sentinel value to abort compilation on
91 // fatal errors. We use `catch_fatal_errors` to 1) catch such panics and
92 // translate them into a Result, and 2) resume and propagate other panics.
Lukasz Anforowiczca87e472022-10-14 14:58:30 -070093 use rustc_interface::interface::Result;
94 let rustc_result: Result<Result<()>> = rustc_driver::catch_fatal_errors(|| {
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070095 rustc_driver::RunCompiler::new(self.args, &mut self).run()
Lukasz Anforowiczc3452842022-09-23 08:02:58 -070096 });
97
Lukasz Anforowiczca87e472022-10-14 14:58:30 -070098 // Flatten `Result<Result<T, ...>>` into `Result<T, ...>` (i.e. combine the
99 // result from `RunCompiler::run` and `catch_fatal_errors`).
100 //
101 // TODO(lukasza): Use `Result::flatten` API when it gets stabilized. See also
102 // https://github.com/rust-lang/rust/issues/70142
103 let rustc_result: Result<()> = rustc_result.and_then(|result| result);
Lukasz Anforowiczc3452842022-09-23 08:02:58 -0700104
105 // Translate `rustc_interface::interface::Result` into `anyhow::Result`. (Can't
106 // use `?` because the trait `std::error::Error` is not implemented for
107 // `ErrorGuaranteed` which is required by the impl of
108 // `From<ErrorGuaranteed>` for `anyhow::Error`.)
Lukasz Anforowiczca87e472022-10-14 14:58:30 -0700109 let rustc_result: anyhow::Result<()> = rustc_result.map_err(|_err| {
Lukasz Anforowiczc3452842022-09-23 08:02:58 -0700110 // We can ignore `_err` because it has no payload / because this type has only
111 // one valid/possible value.
112 anyhow::format_err!("Errors reported by Rust compiler.")
113 });
114
Lukasz Anforowicz76e26232022-10-18 17:26:28 -0700115 // Return either `rustc_result` or `self.callback_result` or a new error.
Lukasz Anforowiczc3452842022-09-23 08:02:58 -0700116 rustc_result.and_then(|()| {
Lukasz Anforowicz76e26232022-10-18 17:26:28 -0700117 self.callback_or_result.right_or_else(|_left| {
118 // When rustc cmdline arguments (i.e. `self.args`) are empty (or contain
119 // `--help`) then the `after_analysis` callback won't be invoked. Handle
120 // this case by emitting an explicit error at the Crubit level.
121 Err(anyhow!("The Rust compiler had no crate to compile and analyze"))
122 })
Lukasz Anforowiczc3452842022-09-23 08:02:58 -0700123 })
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -0700124 }
125 }
126
Lukasz Anforowiczd0f0a842022-11-03 12:40:13 -0700127 impl<'a, F, R> rustc_driver::Callbacks for AfterAnalysisCallback<'a, F, R>
Lukasz Anforowicz1093f782022-09-28 12:45:10 -0700128 where
129 F: FnOnce(TyCtxt) -> anyhow::Result<R> + Send,
130 R: Send,
131 {
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -0700132 fn after_analysis<'tcx>(
133 &mut self,
134 _compiler: &Compiler,
135 queries: &'tcx Queries<'tcx>,
136 ) -> rustc_driver::Compilation {
Lukasz Anforowiczb8279db2022-09-22 07:40:55 -0700137 let rustc_result = enter_tcx(queries, |tcx| {
Lukasz Anforowicz31ca0492022-09-28 15:55:23 -0700138 let callback = {
139 let temporary_placeholder = Either::Right(Err(anyhow::anyhow!("unused")));
140 std::mem::replace(&mut self.callback_or_result, temporary_placeholder)
141 .left_or_else(|_| panic!("`after_analysis` should only run once"))
142 };
143 self.callback_or_result = Either::Right(callback(tcx));
Lukasz Anforowiczb8279db2022-09-22 07:40:55 -0700144 });
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -0700145
146 // `expect`ing no errors in `rustc_result`, because `after_analysis` is only
147 // called by `rustc_driver` if earlier compiler analysis was successful
148 // (which as the *last* compilation phase presumably covers *all*
149 // errors).
Lukasz Anforowiczb8279db2022-09-22 07:40:55 -0700150 rustc_result.expect("Expecting no compile errors inside `after_analysis` callback.");
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -0700151
152 rustc_driver::Compilation::Stop
153 }
154 }
Lukasz Anforowicz581fd752022-09-21 11:30:15 -0700155}
156
Lukasz Anforowicz1093f782022-09-28 12:45:10 -0700157fn write_file(path: &Path, content: &str) -> anyhow::Result<()> {
158 std::fs::write(path, content)
159 .with_context(|| format!("Error when writing to {}", path.display()))
160}
161
162fn run_with_tcx(cmdline: &Cmdline, tcx: TyCtxt) -> anyhow::Result<()> {
Lukasz Anforowicz24160d52022-10-19 06:45:45 -0700163 let bindings = GeneratedBindings::generate(tcx)?;
Lukasz Anforowicze7a25002022-11-10 06:21:42 -0800164
Lukasz Anforowicz5c081b22022-11-11 08:40:17 -0800165 {
Lukasz Anforowicz5bf49432022-12-12 12:17:24 -0800166 let h_body =
167 cc_tokens_to_formatted_string(bindings.h_body, &cmdline.clang_format_exe_path)?;
Lukasz Anforowicz5c081b22022-11-11 08:40:17 -0800168 write_file(&cmdline.h_out, &h_body)?;
169 }
170
171 {
172 let rustfmt_config =
173 RustfmtConfig::new(&cmdline.rustfmt_exe_path, cmdline.rustfmt_config_path.as_deref());
174 let rs_body = rs_tokens_to_formatted_string(bindings.rs_body, &rustfmt_config)?;
175 write_file(&cmdline.rs_out, &rs_body)?;
176 }
Lukasz Anforowicze7a25002022-11-10 06:21:42 -0800177
178 Ok(())
Lukasz Anforowicz1093f782022-09-28 12:45:10 -0700179}
180
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700181/// Main entrypoint that (unlike `main`) doesn't do any intitializations that
182/// should only happen once for the binary (e.g. it doesn't call
Lukasz Anforowicz5bf49432022-12-12 12:17:24 -0800183/// `init_env_logger`) and therefore can be used from the tests module below.
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700184fn run_with_cmdline_args(args: &[String]) -> anyhow::Result<()> {
185 let cmdline = Cmdline::new(args)?;
Lukasz Anforowicza18aab82022-09-29 11:18:06 -0700186 bindings_driver::run_after_analysis_and_stop(&cmdline.rustc_args, |tcx| {
Lukasz Anforowicz1093f782022-09-28 12:45:10 -0700187 run_with_tcx(&cmdline, tcx)
188 })
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700189}
190
Lukasz Anforowicz581fd752022-09-21 11:30:15 -0700191fn main() -> anyhow::Result<()> {
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -0700192 rustc_driver::init_env_logger("CRUBIT_LOG");
193
194 // TODO: Investigate if we should install a signal handler here. See also how
195 // compiler/rustc_driver/src/lib.rs calls `signal_handler::install()`.
196
Lukasz Anforowicz13794df2022-10-21 07:56:34 -0700197 // TODO(b/254689400): Provide Crubit-specific panic hook message (we shouldn't use
Lukasz Anforowiczca87e472022-10-14 14:58:30 -0700198 // `rustc_driver::install_ice_hook` because it's message asks to file bugs at
199 // https://github.com/rust-lang/rust/issues/new.
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -0700200
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700201 // `std::env::args()` will panic if any of the cmdline arguments are not valid
202 // Unicode. This seems okay.
203 let args = std::env::args().collect_vec();
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -0700204
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700205 run_with_cmdline_args(&args)
Lukasz Anforowiczd1a00dc2022-09-29 13:21:51 -0700206 .map_err(|anyhow_err| match anyhow_err.downcast::<clap::Error>() {
207 // Explicitly call `clap::Error::exit`, because 1) it results in *colored* output and
208 // 2) it uses a zero exit code for specific "errors" (e.g. for `--help` output).
Lukasz Anforowiczd1a00dc2022-09-29 13:21:51 -0700209 Ok(clap_err) => {
Lukasz Anforowicz2293ef12022-09-29 13:37:05 -0700210 let _ : ! = clap_err.exit();
Lukasz Anforowiczd1a00dc2022-09-29 13:21:51 -0700211 },
212
213 // Return `other_err` from `main`. This will print the error message (no color codes
214 // though) and terminate the process with a non-zero exit code.
215 Err(other_err) => other_err,
216 })
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700217}
218
219#[cfg(test)]
220mod tests {
221 use super::run_with_cmdline_args;
222
Lukasz Anforowicze7874b52022-09-30 16:52:28 -0700223 use crate::bindings::tests::get_sysroot_for_testing;
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700224 use itertools::Itertools;
Lukasz Anforowicz4a554762022-12-12 09:35:00 -0800225 use regex::{Regex, RegexBuilder};
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700226 use std::path::PathBuf;
227 use tempfile::{tempdir, TempDir};
Lukasz Anforowicz5bf49432022-12-12 12:17:24 -0800228 use token_stream_printer::{CLANG_FORMAT_EXE_PATH_FOR_TESTING, RUSTFMT_EXE_PATH_FOR_TESTING};
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700229
230 /// Test data builder (see also
231 /// https://testing.googleblog.com/2018/02/testing-on-toilet-cleanly-create-test.html).
232 struct TestArgs {
233 h_path: Option<String>,
234 extra_crubit_args: Vec<String>,
Lukasz Anforowicz24160d52022-10-19 06:45:45 -0700235
236 /// Arg for the following `rustc` flag: `--codegen=panic=<arg>`.
237 panic_mechanism: String,
238
239 /// Other `rustc` flags.
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700240 extra_rustc_args: Vec<String>,
Lukasz Anforowicz24160d52022-10-19 06:45:45 -0700241
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700242 tempdir: TempDir,
243 }
244
Lukasz Anforowiczed1c4f02022-09-29 11:11:20 -0700245 /// Result of `TestArgs::run` that helps tests access test outputs (e.g. the
246 /// internally generated `h_path` and/or `rs_input_path`).
247 #[derive(Debug)]
248 struct TestResult {
249 h_path: PathBuf,
Lukasz Anforowicze7a25002022-11-10 06:21:42 -0800250 rs_path: PathBuf,
Lukasz Anforowiczed1c4f02022-09-29 11:11:20 -0700251 }
252
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700253 impl TestArgs {
254 fn default_args() -> anyhow::Result<Self> {
255 Ok(Self {
256 h_path: None,
257 extra_crubit_args: vec![],
Lukasz Anforowicz24160d52022-10-19 06:45:45 -0700258 panic_mechanism: "abort".to_string(),
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700259 extra_rustc_args: vec![],
260 tempdir: tempdir()?,
261 })
262 }
263
264 /// Use the specified `h_path` rather than auto-generating one in
265 /// `self`-managed temporary directory.
266 fn with_h_path(mut self, h_path: &str) -> Self {
267 self.h_path = Some(h_path.to_string());
268 self
269 }
270
Lukasz Anforowicz24160d52022-10-19 06:45:45 -0700271 /// Replaces the default `--codegen=panic=abort` with the specified
272 /// `panic_mechanism`.
273 fn with_panic_mechanism(mut self, panic_mechanism: &str) -> Self {
274 self.panic_mechanism = panic_mechanism.to_string();
275 self
276 }
277
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700278 /// Appends `extra_rustc_args` at the end of the cmdline (i.e. as
279 /// additional rustc args, in addition to `--sysroot`,
280 /// `--crate-type=...`, etc.).
281 fn with_extra_rustc_args<T>(mut self, extra_rustc_args: T) -> Self
282 where
283 T: IntoIterator,
284 T::Item: Into<String>,
285 {
286 self.extra_rustc_args = extra_rustc_args.into_iter().map(|t| t.into()).collect_vec();
287 self
288 }
289
290 /// Appends `extra_crubit_args` before the first `--`.
291 fn with_extra_crubit_args<T>(mut self, extra_crubit_args: T) -> Self
292 where
293 T: IntoIterator,
294 T::Item: Into<String>,
295 {
296 self.extra_crubit_args = extra_crubit_args.into_iter().map(|t| t.into()).collect_vec();
297 self
298 }
299
300 /// Invokes `super::run_with_cmdline_args` with default `test_crate.rs`
301 /// input (and with other default args + args gathered by
302 /// `self`).
303 ///
304 /// Returns the path to the `h_out` file. The file's lifetime is the
305 /// same as `&self`.
Lukasz Anforowiczed1c4f02022-09-29 11:11:20 -0700306 fn run(&self) -> anyhow::Result<TestResult> {
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700307 let h_path = match self.h_path.as_ref() {
Lukasz Anforowiczed1c4f02022-09-29 11:11:20 -0700308 None => self.tempdir.path().join("test_crate_cc_api.h"),
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700309 Some(s) => PathBuf::from(s),
310 };
Lukasz Anforowicze7a25002022-11-10 06:21:42 -0800311 let rs_path = self.tempdir.path().join("test_crate_cc_api_impl.rs");
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700312
313 let rs_input_path = self.tempdir.path().join("test_crate.rs");
314 std::fs::write(
315 &rs_input_path,
Lukasz Anforowicza577d822022-12-12 15:00:46 -0800316 r#" pub mod public_module {
317 pub fn public_function() {
318 private_function()
319 }
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700320
Lukasz Anforowicza577d822022-12-12 15:00:46 -0800321 fn private_function() {}
322 }
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700323 "#,
324 )?;
325
326 let mut args = vec![
327 "cc_bindings_from_rs_unittest_executable".to_string(),
Lukasz Anforowicza18aab82022-09-29 11:18:06 -0700328 format!("--h-out={}", h_path.display()),
Lukasz Anforowicze7a25002022-11-10 06:21:42 -0800329 format!("--rs-out={}", rs_path.display()),
Lukasz Anforowicz5bf49432022-12-12 12:17:24 -0800330 format!("--clang-format-exe-path={CLANG_FORMAT_EXE_PATH_FOR_TESTING}"),
Lukasz Anforowicz5c081b22022-11-11 08:40:17 -0800331 format!("--rustfmt-exe-path={RUSTFMT_EXE_PATH_FOR_TESTING}"),
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700332 ];
333 args.extend(self.extra_crubit_args.iter().cloned());
334 args.extend([
335 "--".to_string(),
Lukasz Anforowicz24160d52022-10-19 06:45:45 -0700336 format!("--codegen=panic={}", &self.panic_mechanism),
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700337 "--crate-type=lib".to_string(),
338 format!("--sysroot={}", get_sysroot_for_testing().display()),
339 rs_input_path.display().to_string(),
340 ]);
341 args.extend(self.extra_rustc_args.iter().cloned());
342
343 run_with_cmdline_args(&args)?;
344
Lukasz Anforowicze7a25002022-11-10 06:21:42 -0800345 Ok(TestResult { h_path, rs_path })
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700346 }
347 }
348
Lukasz Anforowicz4a554762022-12-12 09:35:00 -0800349 // TODO(b/261074843): Go back to exact string matching (and hardcoding thunk
350 // names) once we are using stable name mangling (which may be coming in Q1
351 // 2023). ("Go back" = more or less revert cl/492292910 + manual review and
352 // tweaks.)
353 fn assert_body_matches(actual: &str, expected: &str) {
354 fn build_regex(expected_body: &str) -> Regex {
355 let patt = regex::escape(expected_body);
356 let patt = format!("^{patt}"); // Not always matching $ enables prefix checks below.
357 let patt = patt.replace("ANY_IDENTIFIER_CHARACTERS", "[a-zA-Z0-9_]*");
358 RegexBuilder::new(&patt).multi_line(false).dot_matches_new_line(false).build().unwrap()
359 }
360 let is_whole_h_body_matching = {
361 match build_regex(expected).shortest_match(&actual) {
362 None => false,
363 Some(len) => len == actual.len(),
364 }
365 };
366 if !is_whole_h_body_matching {
367 let longest_matching_expectation_len = (0..=expected.len())
368 .rev() // Iterating from longest to shortest prefix
369 .filter(|&len| {
370 expected
371 .get(0..len) // Only valid UTF-8 boundaries
372 .filter(|prefix| build_regex(prefix).is_match(&actual))
373 .is_some()
374 })
375 .next() // Getting the first regex that matched
376 .unwrap(); // We must get a match at least for 0-length expected body
377 let longest_matching_regex =
378 build_regex(&expected[0..longest_matching_expectation_len]);
379 let len_of_longest_match = longest_matching_regex.shortest_match(&actual).unwrap(); // Again - we must get a match at least for 0-length expected body
380 let mut marked_body = actual.to_string();
381 marked_body.insert_str(len_of_longest_match, "!!!>>>");
382 let mut marked_pattern = expected.to_string();
383 marked_pattern.insert_str(longest_matching_expectation_len, "!!!>>>");
384 panic!(
Lukasz Anforowicza577d822022-12-12 15:00:46 -0800385 "Mismatched expectations:\n\
Lukasz Anforowicz4a554762022-12-12 09:35:00 -0800386 #### Actual body (first mismatch follows the \"!!!>>>\" marker):\n\
387 {marked_body}\n\
388 #### Mismatched pattern (mismatch follows the \"!!!>>>\" marker):\n\
389 {marked_pattern}"
390 );
391 }
392 }
393
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700394 #[test]
395 fn test_happy_path() -> anyhow::Result<()> {
396 let test_args = TestArgs::default_args()?;
Lukasz Anforowiczed1c4f02022-09-29 11:11:20 -0700397 let test_result = test_args.run().expect("Default args should succeed");
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700398
Lukasz Anforowiczed1c4f02022-09-29 11:11:20 -0700399 assert!(test_result.h_path.exists());
400 let h_body = std::fs::read_to_string(&test_result.h_path)?;
Lukasz Anforowicz4a554762022-12-12 09:35:00 -0800401 assert_body_matches(
402 &h_body,
403 r#"// Automatically @generated C++ bindings for the following Rust crate:
Lukasz Anforowicz4aa1ff92022-10-10 11:22:22 -0700404// test_crate
405
Lukasz Anforowicz31b29cd2022-10-10 11:33:41 -0700406#pragma once
407
Lukasz Anforowiczbad99632022-11-18 16:39:13 -0800408namespace test_crate {
Lukasz Anforowicza577d822022-12-12 15:00:46 -0800409
410// Error generating bindings for `public_module` defined at
411// /tmp/.ANY_IDENTIFIER_CHARACTERS/test_crate.rs:1:2: 1:23: Unsupported
412// rustc_hir::hir::ItemKind: module
413
414namespace public_module {
Lukasz Anforowicz2f849cf2022-11-17 15:52:39 -0800415namespace __crubit_internal {
Lukasz Anforowicz4a554762022-12-12 09:35:00 -0800416extern "C" void
417__crubit_thunk__ANY_IDENTIFIER_CHARACTERS();
Lukasz Anforowicz2f849cf2022-11-17 15:52:39 -0800418}
Lukasz Anforowicz2f849cf2022-11-17 15:52:39 -0800419inline void public_function() {
Lukasz Anforowiczbad99632022-11-18 16:39:13 -0800420 return __crubit_internal::
Lukasz Anforowicz4a554762022-12-12 09:35:00 -0800421 __crubit_thunk__ANY_IDENTIFIER_CHARACTERS();
Lukasz Anforowicz2f849cf2022-11-17 15:52:39 -0800422}
Lukasz Anforowicza577d822022-12-12 15:00:46 -0800423} // namespace public_module
Lukasz Anforowicz4a554762022-12-12 09:35:00 -0800424} // namespace test_crate"#,
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700425 );
Lukasz Anforowicze7a25002022-11-10 06:21:42 -0800426
427 assert!(test_result.rs_path.exists());
428 let rs_body = std::fs::read_to_string(&test_result.rs_path)?;
Lukasz Anforowicz4a554762022-12-12 09:35:00 -0800429 assert_body_matches(
430 &rs_body,
Lukasz Anforowicze7a25002022-11-10 06:21:42 -0800431 r#"// Automatically @generated C++ bindings for the following Rust crate:
432// test_crate
Lukasz Anforowicz2f849cf2022-11-17 15:52:39 -0800433
Lukasz Anforowiczd16b6bf2022-11-22 18:35:08 -0800434#![allow(improper_ctypes_definitions)]
435
Lukasz Anforowicz2f849cf2022-11-17 15:52:39 -0800436#[no_mangle]
Lukasz Anforowicza577d822022-12-12 15:00:46 -0800437extern "C" fn __crubit_thunk__ANY_IDENTIFIER_CHARACTERS()
438-> () {
439 ::test_crate::public_module::public_function()
Lukasz Anforowicz2f849cf2022-11-17 15:52:39 -0800440}
Lukasz Anforowicz4a554762022-12-12 09:35:00 -0800441"#,
Lukasz Anforowicze7a25002022-11-10 06:21:42 -0800442 );
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700443 Ok(())
444 }
445
446 #[test]
447 fn test_cmdline_error_propagation() -> anyhow::Result<()> {
448 // Tests that errors from `Cmdline::new` get propagated. Broader coverage of
449 // various error types can be found in tests in `cmdline.rs`.
450 let err = TestArgs::default_args()?
451 .with_extra_crubit_args(["--unrecognized-crubit-flag"])
452 .run()
453 .expect_err("--unrecognized_crubit_flag should trigger an error");
454
Lukasz Anforowiczca87e472022-10-14 14:58:30 -0700455 let msg = format!("{err:#}");
Lukasz Anforowicza18aab82022-09-29 11:18:06 -0700456 assert!(
457 msg.contains("Found argument '--unrecognized-crubit-flag' which wasn't expected"),
458 "msg = {}",
459 msg,
460 );
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700461 Ok(())
462 }
463
464 #[test]
465 fn test_rustc_error_propagation() -> anyhow::Result<()> {
466 // Tests that `rustc` errors are propagated.
467 let err = TestArgs::default_args()?
468 .with_extra_rustc_args(["--unrecognized-rustc-flag"])
469 .run()
470 .expect_err("--unrecognized-rustc-flag should trigger an error");
471
Lukasz Anforowiczca87e472022-10-14 14:58:30 -0700472 let msg = format!("{err:#}");
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700473 assert_eq!("Errors reported by Rust compiler.", msg);
474 Ok(())
475 }
476
477 #[test]
Lukasz Anforowicz76e26232022-10-18 17:26:28 -0700478 fn test_rustc_help() -> anyhow::Result<()> {
479 // Tests that we gracefully handle scenarios where `rustc` doesn't compile
480 // anything (e.g. when there are no rustc cmdline arguments, or when
481 // `--help` is present).
482 let err = TestArgs::default_args()?
483 .with_extra_rustc_args(["--help"])
484 .run()
485 .expect_err("--help passed to rustc should trigger Crubit-level error");
486
487 let msg = format!("{err:#}");
488 assert_eq!("The Rust compiler had no crate to compile and analyze", msg);
489 Ok(())
490 }
491
492 #[test]
Lukasz Anforowicz24160d52022-10-19 06:45:45 -0700493 fn test_rustc_unsupported_panic_mechanism() -> anyhow::Result<()> {
494 // Tests that `panic=unwind` results in an error.
495 let err = TestArgs::default_args()?
496 .with_panic_mechanism("unwind")
497 .run()
498 .expect_err("panic=unwind should trigger an error");
499
500 let msg = format!("{err:#}");
501 assert_eq!("No support for panic=unwind strategy (b/254049425)", msg);
502 Ok(())
503 }
504
505 #[test]
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700506 fn test_invalid_h_out_path() -> anyhow::Result<()> {
Lukasz Anforowicza18aab82022-09-29 11:18:06 -0700507 // Tests not only the specific problem of an invalid `--h-out` argument, but
Lukasz Anforowiczca87e472022-10-14 14:58:30 -0700508 // also tests that errors from `run_with_tcx` are propagated.
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700509 let err = TestArgs::default_args()?
510 .with_h_path("../..")
511 .run()
Lukasz Anforowicza18aab82022-09-29 11:18:06 -0700512 .expect_err("Unwriteable --h-out should trigger an error");
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700513
Lukasz Anforowiczca87e472022-10-14 14:58:30 -0700514 let msg = format!("{err:#}");
515 assert_eq!("Error when writing to ../..: Is a directory (os error 21)", msg);
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700516 Ok(())
517 }
518
519 #[test]
520 fn test_no_output_file() -> anyhow::Result<()> {
521 // Tests that we stop the compilation midway.
522 let tmpdir = tempdir()?;
523 let out_path = tmpdir.path().join("unexpected_output.o");
524 TestArgs::default_args()?
525 .with_extra_rustc_args(vec!["-o", &out_path.display().to_string()])
526 .run()
527 .expect("No rustc or Crubit errors are expected in this test");
528
529 assert!(!out_path.exists());
530 Ok(())
531 }
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -0700532}