1use std::borrow::Cow;
2use std::collections::{HashMap, HashSet};
3use std::ffi::{OsStr, OsString};
4use std::fs::{self, File, create_dir_all};
5use std::hash::{DefaultHasher, Hash, Hasher};
6use std::io::prelude::*;
7use std::io::{self, BufReader};
8use std::path::{Path, PathBuf};
9use std::process::{Child, Command, ExitStatus, Output, Stdio};
10use std::sync::Arc;
11use std::{env, iter, str};
12
13use anyhow::Context;
14use colored::Colorize;
15use regex::{Captures, Regex};
16use tracing::*;
17
18use crate::common::{
19 Assembly, Codegen, CodegenUnits, CompareMode, Config, CoverageMap, CoverageRun, Crashes,
20 DebugInfo, Debugger, FailMode, Incremental, MirOpt, PassMode, Pretty, RunMake, Rustdoc,
21 RustdocJs, RustdocJson, TestPaths, UI_EXTENSIONS, UI_FIXED, UI_RUN_STDERR, UI_RUN_STDOUT,
22 UI_STDERR, UI_STDOUT, UI_SVG, UI_WINDOWS_SVG, Ui, expected_output_path, incremental_dir,
23 output_base_dir, output_base_name, output_testname_unique,
24};
25use crate::compute_diff::{DiffLine, make_diff, write_diff, write_filtered_diff};
26use crate::errors::{self, Error, ErrorKind};
27use crate::header::TestProps;
28use crate::read2::{Truncated, read2_abbreviated};
29use crate::util::{PathBufExt, add_dylib_path, logv, static_regex};
30use crate::{ColorConfig, json, stamp_file_path};
31
32mod debugger;
33
34mod assembly;
37mod codegen;
38mod codegen_units;
39mod coverage;
40mod crashes;
41mod debuginfo;
42mod incremental;
43mod js_doc;
44mod mir_opt;
45mod pretty;
46mod run_make;
47mod rustdoc;
48mod rustdoc_json;
49mod ui;
50#[cfg(test)]
53mod tests;
54
55const FAKE_SRC_BASE: &str = "fake-test-src-base";
56
57#[cfg(windows)]
58fn disable_error_reporting<F: FnOnce() -> R, R>(f: F) -> R {
59 use std::sync::Mutex;
60
61 use windows::Win32::System::Diagnostics::Debug::{
62 SEM_FAILCRITICALERRORS, SEM_NOGPFAULTERRORBOX, SetErrorMode,
63 };
64
65 static LOCK: Mutex<()> = Mutex::new(());
66
67 let _lock = LOCK.lock().unwrap();
69
70 unsafe {
81 let old_mode = SetErrorMode(SEM_NOGPFAULTERRORBOX | SEM_FAILCRITICALERRORS);
83 SetErrorMode(old_mode | SEM_NOGPFAULTERRORBOX | SEM_FAILCRITICALERRORS);
84 let r = f();
85 SetErrorMode(old_mode);
86 r
87 }
88}
89
90#[cfg(not(windows))]
91fn disable_error_reporting<F: FnOnce() -> R, R>(f: F) -> R {
92 f()
93}
94
95fn get_lib_name(name: &str, aux_type: AuxType) -> Option<String> {
97 match aux_type {
98 AuxType::Bin => None,
99 AuxType::Lib => Some(format!("lib{name}.rlib")),
104 AuxType::Dylib | AuxType::ProcMacro => Some(dylib_name(name)),
105 }
106}
107
108fn dylib_name(name: &str) -> String {
109 format!("{}{name}.{}", std::env::consts::DLL_PREFIX, std::env::consts::DLL_EXTENSION)
110}
111
112pub fn run(config: Arc<Config>, testpaths: &TestPaths, revision: Option<&str>) {
113 match &*config.target {
114 "arm-linux-androideabi"
115 | "armv7-linux-androideabi"
116 | "thumbv7neon-linux-androideabi"
117 | "aarch64-linux-android" => {
118 if !config.adb_device_status {
119 panic!("android device not available");
120 }
121 }
122
123 _ => {
124 if config.debugger == Some(Debugger::Gdb) && config.gdb.is_none() {
126 panic!("gdb not available but debuginfo gdb debuginfo test requested");
127 }
128 }
129 }
130
131 if config.verbose {
132 print!("\n\n");
134 }
135 debug!("running {:?}", testpaths.file.display());
136 let mut props = TestProps::from_file(&testpaths.file, revision, &config);
137
138 if props.incremental {
142 props.incremental_dir = Some(incremental_dir(&config, testpaths, revision));
143 }
144
145 let cx = TestCx { config: &config, props: &props, testpaths, revision };
146 create_dir_all(&cx.output_base_dir())
147 .with_context(|| {
148 format!("failed to create output base directory {}", cx.output_base_dir().display())
149 })
150 .unwrap();
151 if props.incremental {
152 cx.init_incremental_test();
153 }
154
155 if config.mode == Incremental {
156 assert!(!props.revisions.is_empty(), "Incremental tests require revisions.");
159 for revision in &props.revisions {
160 let mut revision_props = TestProps::from_file(&testpaths.file, Some(revision), &config);
161 revision_props.incremental_dir = props.incremental_dir.clone();
162 let rev_cx = TestCx {
163 config: &config,
164 props: &revision_props,
165 testpaths,
166 revision: Some(revision),
167 };
168 rev_cx.run_revision();
169 }
170 } else {
171 cx.run_revision();
172 }
173
174 cx.create_stamp();
175}
176
177pub fn compute_stamp_hash(config: &Config) -> String {
178 let mut hash = DefaultHasher::new();
179 config.stage_id.hash(&mut hash);
180 config.run.hash(&mut hash);
181
182 match config.debugger {
183 Some(Debugger::Cdb) => {
184 config.cdb.hash(&mut hash);
185 }
186
187 Some(Debugger::Gdb) => {
188 config.gdb.hash(&mut hash);
189 env::var_os("PATH").hash(&mut hash);
190 env::var_os("PYTHONPATH").hash(&mut hash);
191 }
192
193 Some(Debugger::Lldb) => {
194 config.python.hash(&mut hash);
195 config.lldb_python_dir.hash(&mut hash);
196 env::var_os("PATH").hash(&mut hash);
197 env::var_os("PYTHONPATH").hash(&mut hash);
198 }
199
200 None => {}
201 }
202
203 if let Ui = config.mode {
204 config.force_pass_mode.hash(&mut hash);
205 }
206
207 format!("{:x}", hash.finish())
208}
209
210fn remove_and_create_dir_all(path: &Path) {
211 let _ = fs::remove_dir_all(path);
212 fs::create_dir_all(path).unwrap();
213}
214
215#[derive(Copy, Clone, Debug)]
216struct TestCx<'test> {
217 config: &'test Config,
218 props: &'test TestProps,
219 testpaths: &'test TestPaths,
220 revision: Option<&'test str>,
221}
222
223enum ReadFrom {
224 Path,
225 Stdin(String),
226}
227
228enum TestOutput {
229 Compile,
230 Run,
231}
232
233#[derive(Copy, Clone, PartialEq)]
235enum WillExecute {
236 Yes,
237 No,
238 Disabled,
239}
240
241#[derive(Copy, Clone)]
243enum Emit {
244 None,
245 Metadata,
246 LlvmIr,
247 Mir,
248 Asm,
249 LinkArgsAsm,
250}
251
252impl<'test> TestCx<'test> {
253 fn run_revision(&self) {
256 if self.props.should_ice && self.config.mode != Incremental && self.config.mode != Crashes {
257 self.fatal("cannot use should-ice in a test that is not cfail");
258 }
259 match self.config.mode {
260 Pretty => self.run_pretty_test(),
261 DebugInfo => self.run_debuginfo_test(),
262 Codegen => self.run_codegen_test(),
263 Rustdoc => self.run_rustdoc_test(),
264 RustdocJson => self.run_rustdoc_json_test(),
265 CodegenUnits => self.run_codegen_units_test(),
266 Incremental => self.run_incremental_test(),
267 RunMake => self.run_rmake_test(),
268 Ui => self.run_ui_test(),
269 MirOpt => self.run_mir_opt_test(),
270 Assembly => self.run_assembly_test(),
271 RustdocJs => self.run_rustdoc_js_test(),
272 CoverageMap => self.run_coverage_map_test(), CoverageRun => self.run_coverage_run_test(), Crashes => self.run_crash_test(),
275 }
276 }
277
278 fn pass_mode(&self) -> Option<PassMode> {
279 self.props.pass_mode(self.config)
280 }
281
282 fn should_run(&self, pm: Option<PassMode>) -> WillExecute {
283 let test_should_run = match self.config.mode {
284 Ui if pm == Some(PassMode::Run) || self.props.fail_mode == Some(FailMode::Run) => true,
285 MirOpt if pm == Some(PassMode::Run) => true,
286 Ui | MirOpt => false,
287 mode => panic!("unimplemented for mode {:?}", mode),
288 };
289 if test_should_run { self.run_if_enabled() } else { WillExecute::No }
290 }
291
292 fn run_if_enabled(&self) -> WillExecute {
293 if self.config.run_enabled() { WillExecute::Yes } else { WillExecute::Disabled }
294 }
295
296 fn should_run_successfully(&self, pm: Option<PassMode>) -> bool {
297 match self.config.mode {
298 Ui | MirOpt => pm == Some(PassMode::Run),
299 mode => panic!("unimplemented for mode {:?}", mode),
300 }
301 }
302
303 fn should_compile_successfully(&self, pm: Option<PassMode>) -> bool {
304 match self.config.mode {
305 RustdocJs => true,
306 Ui => pm.is_some() || self.props.fail_mode > Some(FailMode::Build),
307 Crashes => false,
308 Incremental => {
309 let revision =
310 self.revision.expect("incremental tests require a list of revisions");
311 if revision.starts_with("cpass")
312 || revision.starts_with("rpass")
313 || revision.starts_with("rfail")
314 {
315 true
316 } else if revision.starts_with("cfail") {
317 pm.is_some()
318 } else {
319 panic!("revision name must begin with cpass, rpass, rfail, or cfail");
320 }
321 }
322 mode => panic!("unimplemented for mode {:?}", mode),
323 }
324 }
325
326 fn check_if_test_should_compile(
327 &self,
328 fail_mode: Option<FailMode>,
329 pass_mode: Option<PassMode>,
330 proc_res: &ProcRes,
331 ) {
332 if self.should_compile_successfully(pass_mode) {
333 if !proc_res.status.success() {
334 match (fail_mode, pass_mode) {
335 (Some(FailMode::Build), Some(PassMode::Check)) => {
336 self.fatal_proc_rec(
338 "`build-fail` test is required to pass check build, but check build failed",
339 proc_res,
340 );
341 }
342 _ => {
343 self.fatal_proc_rec(
344 "test compilation failed although it shouldn't!",
345 proc_res,
346 );
347 }
348 }
349 }
350 } else {
351 if proc_res.status.success() {
352 {
353 self.error(&format!("{} test did not emit an error", self.config.mode));
354 if self.config.mode == crate::common::Mode::Ui {
355 println!("note: by default, ui tests are expected not to compile");
356 }
357 proc_res.fatal(None, || ());
358 };
359 }
360
361 if !self.props.dont_check_failure_status {
362 self.check_correct_failure_status(proc_res);
363 }
364 }
365 }
366
367 fn get_output(&self, proc_res: &ProcRes) -> String {
368 if self.props.check_stdout {
369 format!("{}{}", proc_res.stdout, proc_res.stderr)
370 } else {
371 proc_res.stderr.clone()
372 }
373 }
374
375 fn check_correct_failure_status(&self, proc_res: &ProcRes) {
376 let expected_status = Some(self.props.failure_status.unwrap_or(1));
377 let received_status = proc_res.status.code();
378
379 if expected_status != received_status {
380 self.fatal_proc_rec(
381 &format!(
382 "Error: expected failure status ({:?}) but received status {:?}.",
383 expected_status, received_status
384 ),
385 proc_res,
386 );
387 }
388 }
389
390 #[must_use = "caller should check whether the command succeeded"]
400 fn run_command_to_procres(&self, cmd: &mut Command) -> ProcRes {
401 let output = cmd
402 .output()
403 .unwrap_or_else(|e| self.fatal(&format!("failed to exec `{cmd:?}` because: {e}")));
404
405 let proc_res = ProcRes {
406 status: output.status,
407 stdout: String::from_utf8(output.stdout).unwrap(),
408 stderr: String::from_utf8(output.stderr).unwrap(),
409 truncated: Truncated::No,
410 cmdline: format!("{cmd:?}"),
411 };
412 self.dump_output(
413 self.config.verbose,
414 &cmd.get_program().to_string_lossy(),
415 &proc_res.stdout,
416 &proc_res.stderr,
417 );
418
419 proc_res
420 }
421
422 fn print_source(&self, read_from: ReadFrom, pretty_type: &str) -> ProcRes {
423 let aux_dir = self.aux_output_dir_name();
424 let input: &str = match read_from {
425 ReadFrom::Stdin(_) => "-",
426 ReadFrom::Path => self.testpaths.file.to_str().unwrap(),
427 };
428
429 let mut rustc = Command::new(&self.config.rustc_path);
430 rustc
431 .arg(input)
432 .args(&["-Z", &format!("unpretty={}", pretty_type)])
433 .args(&["--target", &self.config.target])
434 .arg("-L")
435 .arg(&aux_dir)
436 .arg("-A")
437 .arg("internal_features")
438 .args(&self.props.compile_flags)
439 .envs(self.props.rustc_env.clone());
440 self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags);
441
442 let src = match read_from {
443 ReadFrom::Stdin(src) => Some(src),
444 ReadFrom::Path => None,
445 };
446
447 self.compose_and_run(
448 rustc,
449 self.config.compile_lib_path.to_str().unwrap(),
450 Some(aux_dir.to_str().unwrap()),
451 src,
452 )
453 }
454
455 fn compare_source(&self, expected: &str, actual: &str) {
456 if expected != actual {
457 self.fatal(&format!(
458 "pretty-printed source does not match expected source\n\
459 expected:\n\
460 ------------------------------------------\n\
461 {}\n\
462 ------------------------------------------\n\
463 actual:\n\
464 ------------------------------------------\n\
465 {}\n\
466 ------------------------------------------\n\
467 diff:\n\
468 ------------------------------------------\n\
469 {}\n",
470 expected,
471 actual,
472 write_diff(expected, actual, 3),
473 ));
474 }
475 }
476
477 fn set_revision_flags(&self, cmd: &mut Command) {
478 let normalize_revision = |revision: &str| revision.to_lowercase().replace("-", "_");
481
482 if let Some(revision) = self.revision {
483 let normalized_revision = normalize_revision(revision);
484 let cfg_arg = ["--cfg", &normalized_revision];
485 let arg = format!("--cfg={normalized_revision}");
486 if self
487 .props
488 .compile_flags
489 .windows(2)
490 .any(|args| args == cfg_arg || args[0] == arg || args[1] == arg)
491 {
492 panic!(
493 "error: redundant cfg argument `{normalized_revision}` is already created by the revision"
494 );
495 }
496 if self.config.builtin_cfg_names().contains(&normalized_revision) {
497 panic!("error: revision `{normalized_revision}` collides with a builtin cfg");
498 }
499 cmd.args(cfg_arg);
500 }
501
502 if !self.props.no_auto_check_cfg {
503 let mut check_cfg = String::with_capacity(25);
504
505 check_cfg.push_str("cfg(test,FALSE");
511 for revision in &self.props.revisions {
512 check_cfg.push(',');
513 check_cfg.push_str(&normalize_revision(revision));
514 }
515 check_cfg.push(')');
516
517 cmd.args(&["--check-cfg", &check_cfg]);
518 }
519 }
520
521 fn typecheck_source(&self, src: String) -> ProcRes {
522 let mut rustc = Command::new(&self.config.rustc_path);
523
524 let out_dir = self.output_base_name().with_extension("pretty-out");
525 remove_and_create_dir_all(&out_dir);
526
527 let target = if self.props.force_host { &*self.config.host } else { &*self.config.target };
528
529 let aux_dir = self.aux_output_dir_name();
530
531 rustc
532 .arg("-")
533 .arg("-Zno-codegen")
534 .arg("--out-dir")
535 .arg(&out_dir)
536 .arg(&format!("--target={}", target))
537 .arg("-L")
538 .arg(&self.config.build_base)
539 .arg("-L")
540 .arg(aux_dir)
541 .arg("-A")
542 .arg("internal_features");
543 self.set_revision_flags(&mut rustc);
544 self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags);
545 rustc.args(&self.props.compile_flags);
546
547 self.compose_and_run_compiler(rustc, Some(src), self.testpaths)
548 }
549
550 fn maybe_add_external_args(&self, cmd: &mut Command, args: &Vec<String>) {
551 const OPT_FLAGS: &[&str] = &["-O", "-Copt-level=", "opt-level="];
556 const DEBUG_FLAGS: &[&str] = &["-g", "-Cdebuginfo=", "debuginfo="];
557
558 let have_opt_flag =
562 self.props.compile_flags.iter().any(|arg| OPT_FLAGS.iter().any(|f| arg.starts_with(f)));
563 let have_debug_flag = self
564 .props
565 .compile_flags
566 .iter()
567 .any(|arg| DEBUG_FLAGS.iter().any(|f| arg.starts_with(f)));
568
569 for arg in args {
570 if OPT_FLAGS.iter().any(|f| arg.starts_with(f)) && have_opt_flag {
571 continue;
572 }
573 if DEBUG_FLAGS.iter().any(|f| arg.starts_with(f)) && have_debug_flag {
574 continue;
575 }
576 cmd.arg(arg);
577 }
578 }
579
580 fn check_all_error_patterns(
581 &self,
582 output_to_check: &str,
583 proc_res: &ProcRes,
584 pm: Option<PassMode>,
585 ) {
586 if self.props.error_patterns.is_empty() && self.props.regex_error_patterns.is_empty() {
587 if pm.is_some() {
588 return;
590 } else {
591 self.fatal(&format!(
592 "no error pattern specified in {:?}",
593 self.testpaths.file.display()
594 ));
595 }
596 }
597
598 let mut missing_patterns: Vec<String> = Vec::new();
599
600 self.check_error_patterns(output_to_check, &mut missing_patterns);
601 self.check_regex_error_patterns(output_to_check, proc_res, &mut missing_patterns);
602
603 if missing_patterns.is_empty() {
604 return;
605 }
606
607 if missing_patterns.len() == 1 {
608 self.fatal_proc_rec(
609 &format!("error pattern '{}' not found!", missing_patterns[0]),
610 proc_res,
611 );
612 } else {
613 for pattern in missing_patterns {
614 self.error(&format!("error pattern '{}' not found!", pattern));
615 }
616 self.fatal_proc_rec("multiple error patterns not found", proc_res);
617 }
618 }
619
620 fn check_error_patterns(&self, output_to_check: &str, missing_patterns: &mut Vec<String>) {
621 debug!("check_error_patterns");
622 for pattern in &self.props.error_patterns {
623 if output_to_check.contains(pattern.trim()) {
624 debug!("found error pattern {}", pattern);
625 } else {
626 missing_patterns.push(pattern.to_string());
627 }
628 }
629 }
630
631 fn check_regex_error_patterns(
632 &self,
633 output_to_check: &str,
634 proc_res: &ProcRes,
635 missing_patterns: &mut Vec<String>,
636 ) {
637 debug!("check_regex_error_patterns");
638
639 for pattern in &self.props.regex_error_patterns {
640 let pattern = pattern.trim();
641 let re = match Regex::new(pattern) {
642 Ok(re) => re,
643 Err(err) => {
644 self.fatal_proc_rec(
645 &format!("invalid regex error pattern '{}': {:?}", pattern, err),
646 proc_res,
647 );
648 }
649 };
650 if re.is_match(output_to_check) {
651 debug!("found regex error pattern {}", pattern);
652 } else {
653 missing_patterns.push(pattern.to_string());
654 }
655 }
656 }
657
658 fn check_no_compiler_crash(&self, proc_res: &ProcRes, should_ice: bool) {
659 match proc_res.status.code() {
660 Some(101) if !should_ice => {
661 self.fatal_proc_rec("compiler encountered internal error", proc_res)
662 }
663 None => self.fatal_proc_rec("compiler terminated by signal", proc_res),
664 _ => (),
665 }
666 }
667
668 fn check_forbid_output(&self, output_to_check: &str, proc_res: &ProcRes) {
669 for pat in &self.props.forbid_output {
670 if output_to_check.contains(pat) {
671 self.fatal_proc_rec("forbidden pattern found in compiler output", proc_res);
672 }
673 }
674 }
675
676 fn check_expected_errors(&self, expected_errors: Vec<errors::Error>, proc_res: &ProcRes) {
677 debug!(
678 "check_expected_errors: expected_errors={:?} proc_res.status={:?}",
679 expected_errors, proc_res.status
680 );
681 if proc_res.status.success()
682 && expected_errors.iter().any(|x| x.kind == Some(ErrorKind::Error))
683 {
684 self.fatal_proc_rec("process did not return an error status", proc_res);
685 }
686
687 if self.props.known_bug {
688 if !expected_errors.is_empty() {
689 self.fatal_proc_rec(
690 "`known_bug` tests should not have an expected error",
691 proc_res,
692 );
693 }
694 return;
695 }
696
697 let file_name = format!("{}", self.testpaths.file.display()).replace(r"\", "/");
699
700 let diagnostic_file_name = if self.props.remap_src_base {
703 let mut p = PathBuf::from(FAKE_SRC_BASE);
704 p.push(&self.testpaths.relative_dir);
705 p.push(self.testpaths.file.file_name().unwrap());
706 p.display().to_string()
707 } else {
708 self.testpaths.file.display().to_string()
709 };
710
711 let expect_help = expected_errors.iter().any(|ee| ee.kind == Some(ErrorKind::Help));
716 let expect_note = expected_errors.iter().any(|ee| ee.kind == Some(ErrorKind::Note));
717
718 let actual_errors = json::parse_output(&diagnostic_file_name, &proc_res.stderr, proc_res);
720 let mut unexpected = Vec::new();
721 let mut found = vec![false; expected_errors.len()];
722 for mut actual_error in actual_errors {
723 actual_error.msg = self.normalize_output(&actual_error.msg, &[]);
724
725 let opt_index =
726 expected_errors.iter().enumerate().position(|(index, expected_error)| {
727 !found[index]
728 && actual_error.line_num == expected_error.line_num
729 && (expected_error.kind.is_none()
730 || actual_error.kind == expected_error.kind)
731 && actual_error.msg.contains(&expected_error.msg)
732 });
733
734 match opt_index {
735 Some(index) => {
736 assert!(!found[index]);
738 found[index] = true;
739 }
740
741 None => {
742 if self.is_unexpected_compiler_message(&actual_error, expect_help, expect_note)
744 {
745 self.error(&format!(
746 "{}:{}: unexpected {}: '{}'",
747 file_name,
748 actual_error.line_num,
749 actual_error
750 .kind
751 .as_ref()
752 .map_or(String::from("message"), |k| k.to_string()),
753 actual_error.msg
754 ));
755 unexpected.push(actual_error);
756 }
757 }
758 }
759 }
760
761 let mut not_found = Vec::new();
762 for (index, expected_error) in expected_errors.iter().enumerate() {
764 if !found[index] {
765 self.error(&format!(
766 "{}:{}: expected {} not found: {}",
767 file_name,
768 expected_error.line_num,
769 expected_error.kind.as_ref().map_or("message".into(), |k| k.to_string()),
770 expected_error.msg
771 ));
772 not_found.push(expected_error);
773 }
774 }
775
776 if !unexpected.is_empty() || !not_found.is_empty() {
777 self.error(&format!(
778 "{} unexpected errors found, {} expected errors not found",
779 unexpected.len(),
780 not_found.len()
781 ));
782 println!("status: {}\ncommand: {}\n", proc_res.status, proc_res.cmdline);
783 if !unexpected.is_empty() {
784 println!("{}", "--- unexpected errors (from JSON output) ---".green());
785 for error in &unexpected {
786 println!("{}", error.render_for_expected());
787 }
788 println!("{}", "---".green());
789 }
790 if !not_found.is_empty() {
791 println!("{}", "--- not found errors (from test file) ---".red());
792 for error in ¬_found {
793 println!("{}", error.render_for_expected());
794 }
795 println!("{}", "---\n".red());
796 }
797 panic!("errors differ from expected");
798 }
799 }
800
801 fn is_unexpected_compiler_message(
806 &self,
807 actual_error: &Error,
808 expect_help: bool,
809 expect_note: bool,
810 ) -> bool {
811 !actual_error.msg.is_empty()
812 && match actual_error.kind {
813 Some(ErrorKind::Help) => expect_help,
814 Some(ErrorKind::Note) => expect_note,
815 Some(ErrorKind::Error) | Some(ErrorKind::Warning) => true,
816 Some(ErrorKind::Suggestion) | None => false,
817 }
818 }
819
820 fn should_emit_metadata(&self, pm: Option<PassMode>) -> Emit {
821 match (pm, self.props.fail_mode, self.config.mode) {
822 (Some(PassMode::Check), ..) | (_, Some(FailMode::Check), Ui) => Emit::Metadata,
823 _ => Emit::None,
824 }
825 }
826
827 fn compile_test(&self, will_execute: WillExecute, emit: Emit) -> ProcRes {
828 self.compile_test_general(will_execute, emit, self.props.local_pass_mode(), Vec::new())
829 }
830
831 fn compile_test_with_passes(
832 &self,
833 will_execute: WillExecute,
834 emit: Emit,
835 passes: Vec<String>,
836 ) -> ProcRes {
837 self.compile_test_general(will_execute, emit, self.props.local_pass_mode(), passes)
838 }
839
840 fn compile_test_general(
841 &self,
842 will_execute: WillExecute,
843 emit: Emit,
844 local_pm: Option<PassMode>,
845 passes: Vec<String>,
846 ) -> ProcRes {
847 let output_file = match will_execute {
849 WillExecute::Yes => TargetLocation::ThisFile(self.make_exe_name()),
850 WillExecute::No | WillExecute::Disabled => {
851 TargetLocation::ThisDirectory(self.output_base_dir())
852 }
853 };
854
855 let allow_unused = match self.config.mode {
856 Ui => {
857 if !self.is_rustdoc()
863 && local_pm != Some(PassMode::Run)
867 {
868 AllowUnused::Yes
869 } else {
870 AllowUnused::No
871 }
872 }
873 _ => AllowUnused::No,
874 };
875
876 let rustc = self.make_compile_args(
877 &self.testpaths.file,
878 output_file,
879 emit,
880 allow_unused,
881 LinkToAux::Yes,
882 passes,
883 );
884
885 self.compose_and_run_compiler(rustc, None, self.testpaths)
886 }
887
888 fn document(&self, root_out_dir: &Path, root_testpaths: &TestPaths) -> ProcRes {
891 if self.props.build_aux_docs {
892 for rel_ab in &self.props.aux.builds {
893 let aux_testpaths = self.compute_aux_test_paths(root_testpaths, rel_ab);
894 let props_for_aux =
895 self.props.from_aux_file(&aux_testpaths.file, self.revision, self.config);
896 let aux_cx = TestCx {
897 config: self.config,
898 props: &props_for_aux,
899 testpaths: &aux_testpaths,
900 revision: self.revision,
901 };
902 create_dir_all(aux_cx.output_base_dir()).unwrap();
904 let auxres = aux_cx.document(&root_out_dir, root_testpaths);
907 if !auxres.status.success() {
908 return auxres;
909 }
910 }
911 }
912
913 let aux_dir = self.aux_output_dir_name();
914
915 let rustdoc_path = self.config.rustdoc_path.as_ref().expect("--rustdoc-path not passed");
916
917 let out_dir: Cow<'_, Path> = if self.props.unique_doc_out_dir {
920 let file_name = self.testpaths.file.file_stem().expect("file name should not be empty");
921 let out_dir = PathBuf::from_iter([
922 root_out_dir,
923 Path::new("docs"),
924 Path::new(file_name),
925 Path::new("doc"),
926 ]);
927 create_dir_all(&out_dir).unwrap();
928 Cow::Owned(out_dir)
929 } else {
930 Cow::Borrowed(root_out_dir)
931 };
932
933 let mut rustdoc = Command::new(rustdoc_path);
934 let current_dir = output_base_dir(self.config, root_testpaths, self.safe_revision());
935 rustdoc.current_dir(current_dir);
936 rustdoc
937 .arg("-L")
938 .arg(self.config.run_lib_path.to_str().unwrap())
939 .arg("-L")
940 .arg(aux_dir)
941 .arg("-o")
942 .arg(out_dir.as_ref())
943 .arg("--deny")
944 .arg("warnings")
945 .arg(&self.testpaths.file)
946 .arg("-A")
947 .arg("internal_features")
948 .args(&self.props.compile_flags)
949 .args(&self.props.doc_flags);
950
951 if self.config.mode == RustdocJson {
952 rustdoc.arg("--output-format").arg("json").arg("-Zunstable-options");
953 }
954
955 if let Some(ref linker) = self.config.target_linker {
956 rustdoc.arg(format!("-Clinker={}", linker));
957 }
958
959 self.compose_and_run_compiler(rustdoc, None, root_testpaths)
960 }
961
962 fn exec_compiled_test(&self) -> ProcRes {
963 self.exec_compiled_test_general(&[], true)
964 }
965
966 fn exec_compiled_test_general(
967 &self,
968 env_extra: &[(&str, &str)],
969 delete_after_success: bool,
970 ) -> ProcRes {
971 let prepare_env = |cmd: &mut Command| {
972 for key in &self.props.unset_exec_env {
973 cmd.env_remove(key);
974 }
975
976 for (key, val) in &self.props.exec_env {
977 cmd.env(key, val);
978 }
979 for (key, val) in env_extra {
980 cmd.env(key, val);
981 }
982 };
983
984 let proc_res = match &*self.config.target {
985 _ if self.config.remote_test_client.is_some() => {
1000 let aux_dir = self.aux_output_dir_name();
1001 let ProcArgs { prog, args } = self.make_run_args();
1002 let mut support_libs = Vec::new();
1003 if let Ok(entries) = aux_dir.read_dir() {
1004 for entry in entries {
1005 let entry = entry.unwrap();
1006 if !entry.path().is_file() {
1007 continue;
1008 }
1009 support_libs.push(entry.path());
1010 }
1011 }
1012 let mut test_client =
1013 Command::new(self.config.remote_test_client.as_ref().unwrap());
1014 test_client
1015 .args(&["run", &support_libs.len().to_string()])
1016 .arg(&prog)
1017 .args(support_libs)
1018 .args(args);
1019
1020 prepare_env(&mut test_client);
1021
1022 self.compose_and_run(
1023 test_client,
1024 self.config.run_lib_path.to_str().unwrap(),
1025 Some(aux_dir.to_str().unwrap()),
1026 None,
1027 )
1028 }
1029 _ if self.config.target.contains("vxworks") => {
1030 let aux_dir = self.aux_output_dir_name();
1031 let ProcArgs { prog, args } = self.make_run_args();
1032 let mut wr_run = Command::new("wr-run");
1033 wr_run.args(&[&prog]).args(args);
1034
1035 prepare_env(&mut wr_run);
1036
1037 self.compose_and_run(
1038 wr_run,
1039 self.config.run_lib_path.to_str().unwrap(),
1040 Some(aux_dir.to_str().unwrap()),
1041 None,
1042 )
1043 }
1044 _ => {
1045 let aux_dir = self.aux_output_dir_name();
1046 let ProcArgs { prog, args } = self.make_run_args();
1047 let mut program = Command::new(&prog);
1048 program.args(args).current_dir(&self.output_base_dir());
1049
1050 prepare_env(&mut program);
1051
1052 self.compose_and_run(
1053 program,
1054 self.config.run_lib_path.to_str().unwrap(),
1055 Some(aux_dir.to_str().unwrap()),
1056 None,
1057 )
1058 }
1059 };
1060
1061 if delete_after_success && proc_res.status.success() {
1062 let _ = fs::remove_file(self.make_exe_name());
1065 }
1066
1067 proc_res
1068 }
1069
1070 fn compute_aux_test_paths(&self, of: &TestPaths, rel_ab: &str) -> TestPaths {
1073 let test_ab =
1074 of.file.parent().expect("test file path has no parent").join("auxiliary").join(rel_ab);
1075 if !test_ab.exists() {
1076 self.fatal(&format!("aux-build `{}` source not found", test_ab.display()))
1077 }
1078
1079 TestPaths {
1080 file: test_ab,
1081 relative_dir: of
1082 .relative_dir
1083 .join(self.output_testname_unique())
1084 .join("auxiliary")
1085 .join(rel_ab)
1086 .parent()
1087 .expect("aux-build path has no parent")
1088 .to_path_buf(),
1089 }
1090 }
1091
1092 fn is_vxworks_pure_static(&self) -> bool {
1093 if self.config.target.contains("vxworks") {
1094 match env::var("RUST_VXWORKS_TEST_DYLINK") {
1095 Ok(s) => s != "1",
1096 _ => true,
1097 }
1098 } else {
1099 false
1100 }
1101 }
1102
1103 fn is_vxworks_pure_dynamic(&self) -> bool {
1104 self.config.target.contains("vxworks") && !self.is_vxworks_pure_static()
1105 }
1106
1107 fn has_aux_dir(&self) -> bool {
1108 !self.props.aux.builds.is_empty()
1109 || !self.props.aux.crates.is_empty()
1110 || !self.props.aux.proc_macros.is_empty()
1111 }
1112
1113 fn aux_output_dir(&self) -> PathBuf {
1114 let aux_dir = self.aux_output_dir_name();
1115
1116 if !self.props.aux.builds.is_empty() {
1117 remove_and_create_dir_all(&aux_dir);
1118 }
1119
1120 if !self.props.aux.bins.is_empty() {
1121 let aux_bin_dir = self.aux_bin_output_dir_name();
1122 remove_and_create_dir_all(&aux_dir);
1123 remove_and_create_dir_all(&aux_bin_dir);
1124 }
1125
1126 aux_dir
1127 }
1128
1129 fn build_all_auxiliary(&self, of: &TestPaths, aux_dir: &Path, rustc: &mut Command) {
1130 for rel_ab in &self.props.aux.builds {
1131 self.build_auxiliary(of, rel_ab, &aux_dir, None);
1132 }
1133
1134 for rel_ab in &self.props.aux.bins {
1135 self.build_auxiliary(of, rel_ab, &aux_dir, Some(AuxType::Bin));
1136 }
1137
1138 let path_to_crate_name = |path: &str| -> String {
1139 path.rsplit_once('/')
1140 .map_or(path, |(_, tail)| tail)
1141 .trim_end_matches(".rs")
1142 .replace('-', "_")
1143 };
1144
1145 let add_extern =
1146 |rustc: &mut Command, aux_name: &str, aux_path: &str, aux_type: AuxType| {
1147 let lib_name = get_lib_name(&path_to_crate_name(aux_path), aux_type);
1148 if let Some(lib_name) = lib_name {
1149 rustc.arg("--extern").arg(format!(
1150 "{}={}/{}",
1151 aux_name,
1152 aux_dir.display(),
1153 lib_name
1154 ));
1155 }
1156 };
1157
1158 for (aux_name, aux_path) in &self.props.aux.crates {
1159 let aux_type = self.build_auxiliary(of, &aux_path, &aux_dir, None);
1160 add_extern(rustc, aux_name, aux_path, aux_type);
1161 }
1162
1163 for proc_macro in &self.props.aux.proc_macros {
1164 self.build_auxiliary(of, proc_macro, &aux_dir, Some(AuxType::ProcMacro));
1165 let crate_name = path_to_crate_name(proc_macro);
1166 add_extern(rustc, &crate_name, proc_macro, AuxType::ProcMacro);
1167 }
1168
1169 if let Some(aux_file) = &self.props.aux.codegen_backend {
1172 let aux_type = self.build_auxiliary(of, aux_file, aux_dir, None);
1173 if let Some(lib_name) = get_lib_name(aux_file.trim_end_matches(".rs"), aux_type) {
1174 let lib_path = aux_dir.join(&lib_name);
1175 rustc.arg(format!("-Zcodegen-backend={}", lib_path.display()));
1176 }
1177 }
1178 }
1179
1180 fn compose_and_run_compiler(
1183 &self,
1184 mut rustc: Command,
1185 input: Option<String>,
1186 root_testpaths: &TestPaths,
1187 ) -> ProcRes {
1188 if self.props.add_core_stubs {
1189 let minicore_path = self.build_minicore();
1190 rustc.arg("--extern");
1191 rustc.arg(&format!("minicore={}", minicore_path.to_str().unwrap()));
1192 }
1193
1194 let aux_dir = self.aux_output_dir();
1195 self.build_all_auxiliary(root_testpaths, &aux_dir, &mut rustc);
1196
1197 rustc.envs(self.props.rustc_env.clone());
1198 self.props.unset_rustc_env.iter().fold(&mut rustc, Command::env_remove);
1199 self.compose_and_run(
1200 rustc,
1201 self.config.compile_lib_path.to_str().unwrap(),
1202 Some(aux_dir.to_str().unwrap()),
1203 input,
1204 )
1205 }
1206
1207 fn build_minicore(&self) -> PathBuf {
1210 let output_file_path = self.output_base_dir().join("libminicore.rlib");
1211 let mut rustc = self.make_compile_args(
1212 &self.config.minicore_path,
1213 TargetLocation::ThisFile(output_file_path.clone()),
1214 Emit::None,
1215 AllowUnused::Yes,
1216 LinkToAux::No,
1217 vec![],
1218 );
1219
1220 rustc.args(&["--crate-type", "rlib"]);
1221 rustc.arg("-Cpanic=abort");
1222
1223 let res =
1224 self.compose_and_run(rustc, self.config.compile_lib_path.to_str().unwrap(), None, None);
1225 if !res.status.success() {
1226 self.fatal_proc_rec(
1227 &format!(
1228 "auxiliary build of {:?} failed to compile: ",
1229 self.config.minicore_path.display()
1230 ),
1231 &res,
1232 );
1233 }
1234
1235 output_file_path
1236 }
1237
1238 fn build_auxiliary(
1242 &self,
1243 of: &TestPaths,
1244 source_path: &str,
1245 aux_dir: &Path,
1246 aux_type: Option<AuxType>,
1247 ) -> AuxType {
1248 let aux_testpaths = self.compute_aux_test_paths(of, source_path);
1249 let mut aux_props =
1250 self.props.from_aux_file(&aux_testpaths.file, self.revision, self.config);
1251 if aux_type == Some(AuxType::ProcMacro) {
1252 aux_props.force_host = true;
1253 }
1254 let mut aux_dir = aux_dir.to_path_buf();
1255 if aux_type == Some(AuxType::Bin) {
1256 aux_dir.push("bin");
1260 }
1261 let aux_output = TargetLocation::ThisDirectory(aux_dir.clone());
1262 let aux_cx = TestCx {
1263 config: self.config,
1264 props: &aux_props,
1265 testpaths: &aux_testpaths,
1266 revision: self.revision,
1267 };
1268 create_dir_all(aux_cx.output_base_dir()).unwrap();
1270 let input_file = &aux_testpaths.file;
1271 let mut aux_rustc = aux_cx.make_compile_args(
1272 input_file,
1273 aux_output,
1274 Emit::None,
1275 AllowUnused::No,
1276 LinkToAux::No,
1277 Vec::new(),
1278 );
1279 aux_cx.build_all_auxiliary(of, &aux_dir, &mut aux_rustc);
1280
1281 aux_rustc.envs(aux_props.rustc_env.clone());
1282 for key in &aux_props.unset_rustc_env {
1283 aux_rustc.env_remove(key);
1284 }
1285
1286 let (aux_type, crate_type) = if aux_type == Some(AuxType::Bin) {
1287 (AuxType::Bin, Some("bin"))
1288 } else if aux_type == Some(AuxType::ProcMacro) {
1289 (AuxType::ProcMacro, Some("proc-macro"))
1290 } else if aux_type.is_some() {
1291 panic!("aux_type {aux_type:?} not expected");
1292 } else if aux_props.no_prefer_dynamic {
1293 (AuxType::Dylib, None)
1294 } else if self.config.target.contains("emscripten")
1295 || (self.config.target.contains("musl")
1296 && !aux_props.force_host
1297 && !self.config.host.contains("musl"))
1298 || self.config.target.contains("wasm32")
1299 || self.config.target.contains("nvptx")
1300 || self.is_vxworks_pure_static()
1301 || self.config.target.contains("bpf")
1302 || !self.config.target_cfg().dynamic_linking
1303 || matches!(self.config.mode, CoverageMap | CoverageRun)
1304 {
1305 (AuxType::Lib, Some("lib"))
1319 } else {
1320 (AuxType::Dylib, Some("dylib"))
1321 };
1322
1323 if let Some(crate_type) = crate_type {
1324 aux_rustc.args(&["--crate-type", crate_type]);
1325 }
1326
1327 if aux_type == AuxType::ProcMacro {
1328 aux_rustc.args(&["--extern", "proc_macro"]);
1330 }
1331
1332 aux_rustc.arg("-L").arg(&aux_dir);
1333
1334 let auxres = aux_cx.compose_and_run(
1335 aux_rustc,
1336 aux_cx.config.compile_lib_path.to_str().unwrap(),
1337 Some(aux_dir.to_str().unwrap()),
1338 None,
1339 );
1340 if !auxres.status.success() {
1341 self.fatal_proc_rec(
1342 &format!(
1343 "auxiliary build of {:?} failed to compile: ",
1344 aux_testpaths.file.display()
1345 ),
1346 &auxres,
1347 );
1348 }
1349 aux_type
1350 }
1351
1352 fn read2_abbreviated(&self, child: Child) -> (Output, Truncated) {
1353 let mut filter_paths_from_len = Vec::new();
1354 let mut add_path = |path: &Path| {
1355 let path = path.display().to_string();
1356 let windows = path.replace("\\", "\\\\");
1357 if windows != path {
1358 filter_paths_from_len.push(windows);
1359 }
1360 filter_paths_from_len.push(path);
1361 };
1362
1363 add_path(&self.config.src_base);
1369 add_path(&self.config.build_base);
1370
1371 read2_abbreviated(child, &filter_paths_from_len).expect("failed to read output")
1372 }
1373
1374 fn compose_and_run(
1375 &self,
1376 mut command: Command,
1377 lib_path: &str,
1378 aux_path: Option<&str>,
1379 input: Option<String>,
1380 ) -> ProcRes {
1381 let cmdline = {
1382 let cmdline = self.make_cmdline(&command, lib_path);
1383 logv(self.config, format!("executing {}", cmdline));
1384 cmdline
1385 };
1386
1387 command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::piped());
1388
1389 add_dylib_path(&mut command, iter::once(lib_path).chain(aux_path));
1392
1393 let mut child = disable_error_reporting(|| command.spawn())
1394 .unwrap_or_else(|e| panic!("failed to exec `{command:?}`: {e:?}"));
1395 if let Some(input) = input {
1396 child.stdin.as_mut().unwrap().write_all(input.as_bytes()).unwrap();
1397 }
1398
1399 let (Output { status, stdout, stderr }, truncated) = self.read2_abbreviated(child);
1400
1401 let result = ProcRes {
1402 status,
1403 stdout: String::from_utf8_lossy(&stdout).into_owned(),
1404 stderr: String::from_utf8_lossy(&stderr).into_owned(),
1405 truncated,
1406 cmdline,
1407 };
1408
1409 self.dump_output(
1410 self.config.verbose,
1411 &command.get_program().to_string_lossy(),
1412 &result.stdout,
1413 &result.stderr,
1414 );
1415
1416 result
1417 }
1418
1419 fn is_rustdoc(&self) -> bool {
1420 matches!(self.config.suite.as_str(), "rustdoc-ui" | "rustdoc-js" | "rustdoc-json")
1421 }
1422
1423 fn get_mir_dump_dir(&self) -> PathBuf {
1424 let mut mir_dump_dir = PathBuf::from(self.config.build_base.as_path());
1425 debug!("input_file: {:?}", self.testpaths.file);
1426 mir_dump_dir.push(&self.testpaths.relative_dir);
1427 mir_dump_dir.push(self.testpaths.file.file_stem().unwrap());
1428 mir_dump_dir
1429 }
1430
1431 fn make_compile_args(
1432 &self,
1433 input_file: &Path,
1434 output_file: TargetLocation,
1435 emit: Emit,
1436 allow_unused: AllowUnused,
1437 link_to_aux: LinkToAux,
1438 passes: Vec<String>, ) -> Command {
1440 let is_aux = input_file.components().map(|c| c.as_os_str()).any(|c| c == "auxiliary");
1441 let is_rustdoc = self.is_rustdoc() && !is_aux;
1442 let mut rustc = if !is_rustdoc {
1443 Command::new(&self.config.rustc_path)
1444 } else {
1445 Command::new(&self.config.rustdoc_path.clone().expect("no rustdoc built yet"))
1446 };
1447 rustc.arg(input_file);
1448
1449 rustc.arg("-Zthreads=1");
1451
1452 rustc.arg("-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX");
1461 rustc.arg("-Ztranslate-remapped-path-to-local-path=no");
1462
1463 rustc.arg("-Z").arg(format!(
1468 "ignore-directory-in-diagnostics-source-blocks={}",
1469 home::cargo_home().expect("failed to find cargo home").to_str().unwrap()
1470 ));
1471 rustc.arg("-Z").arg(format!(
1473 "ignore-directory-in-diagnostics-source-blocks={}",
1474 self.config.find_rust_src_root().unwrap().join("vendor").display(),
1475 ));
1476
1477 if !self.props.compile_flags.iter().any(|flag| flag.starts_with("--sysroot"))
1479 && !self.config.host_rustcflags.iter().any(|flag| flag == "--sysroot")
1480 {
1481 rustc.arg("--sysroot").arg(&self.config.sysroot_base);
1483 }
1484
1485 let custom_target = self.props.compile_flags.iter().any(|x| x.starts_with("--target"));
1487
1488 if !custom_target {
1489 let target =
1490 if self.props.force_host { &*self.config.host } else { &*self.config.target };
1491
1492 rustc.arg(&format!("--target={}", target));
1493 }
1494 self.set_revision_flags(&mut rustc);
1495
1496 if !is_rustdoc {
1497 if let Some(ref incremental_dir) = self.props.incremental_dir {
1498 rustc.args(&["-C", &format!("incremental={}", incremental_dir.display())]);
1499 rustc.args(&["-Z", "incremental-verify-ich"]);
1500 }
1501
1502 if self.config.mode == CodegenUnits {
1503 rustc.args(&["-Z", "human_readable_cgu_names"]);
1504 }
1505 }
1506
1507 if self.config.optimize_tests && !is_rustdoc {
1508 match self.config.mode {
1509 Ui => {
1510 if self.config.optimize_tests
1515 && self.props.pass_mode(&self.config) == Some(PassMode::Run)
1516 && !self
1517 .props
1518 .compile_flags
1519 .iter()
1520 .any(|arg| arg == "-O" || arg.contains("opt-level"))
1521 {
1522 rustc.arg("-O");
1523 }
1524 }
1525 DebugInfo => { }
1526 CoverageMap | CoverageRun => {
1527 }
1532 _ => {
1533 rustc.arg("-O");
1534 }
1535 }
1536 }
1537
1538 let set_mir_dump_dir = |rustc: &mut Command| {
1539 let mir_dump_dir = self.get_mir_dump_dir();
1540 remove_and_create_dir_all(&mir_dump_dir);
1541 let mut dir_opt = "-Zdump-mir-dir=".to_string();
1542 dir_opt.push_str(mir_dump_dir.to_str().unwrap());
1543 debug!("dir_opt: {:?}", dir_opt);
1544 rustc.arg(dir_opt);
1545 };
1546
1547 match self.config.mode {
1548 Incremental => {
1549 if self.props.error_patterns.is_empty()
1553 && self.props.regex_error_patterns.is_empty()
1554 {
1555 rustc.args(&["--error-format", "json"]);
1556 rustc.args(&["--json", "future-incompat"]);
1557 }
1558 rustc.arg("-Zui-testing");
1559 rustc.arg("-Zdeduplicate-diagnostics=no");
1560 }
1561 Ui => {
1562 if !self.props.compile_flags.iter().any(|s| s.starts_with("--error-format")) {
1563 rustc.args(&["--error-format", "json"]);
1564 rustc.args(&["--json", "future-incompat"]);
1565 }
1566 rustc.arg("-Ccodegen-units=1");
1567 rustc.arg("-Zui-testing");
1569 rustc.arg("-Zdeduplicate-diagnostics=no");
1570 rustc.arg("-Zwrite-long-types-to-disk=no");
1571 rustc.arg("-Cstrip=debuginfo");
1573 }
1574 MirOpt => {
1575 let zdump_arg = if !passes.is_empty() {
1579 format!("-Zdump-mir={}", passes.join(" | "))
1580 } else {
1581 "-Zdump-mir=all".to_string()
1582 };
1583
1584 rustc.args(&[
1585 "-Copt-level=1",
1586 &zdump_arg,
1587 "-Zvalidate-mir",
1588 "-Zlint-mir",
1589 "-Zdump-mir-exclude-pass-number",
1590 "-Zmir-include-spans=false", "--crate-type=rlib",
1592 ]);
1593 if let Some(pass) = &self.props.mir_unit_test {
1594 rustc.args(&["-Zmir-opt-level=0", &format!("-Zmir-enable-passes=+{}", pass)]);
1595 } else {
1596 rustc.args(&[
1597 "-Zmir-opt-level=4",
1598 "-Zmir-enable-passes=+ReorderBasicBlocks,+ReorderLocals",
1599 ]);
1600 }
1601
1602 set_mir_dump_dir(&mut rustc);
1603 }
1604 CoverageMap => {
1605 rustc.arg("-Cinstrument-coverage");
1606 rustc.arg("-Zno-profiler-runtime");
1609 rustc.arg("-Copt-level=2");
1613 }
1614 CoverageRun => {
1615 rustc.arg("-Cinstrument-coverage");
1616 rustc.arg("-Copt-level=2");
1620 }
1621 Assembly | Codegen => {
1622 rustc.arg("-Cdebug-assertions=no");
1623 }
1624 Crashes => {
1625 set_mir_dump_dir(&mut rustc);
1626 }
1627 Pretty | DebugInfo | Rustdoc | RustdocJson | RunMake | CodegenUnits | RustdocJs => {
1628 }
1630 }
1631
1632 if self.props.remap_src_base {
1633 rustc.arg(format!(
1634 "--remap-path-prefix={}={}",
1635 self.config.src_base.display(),
1636 FAKE_SRC_BASE,
1637 ));
1638 }
1639
1640 match emit {
1641 Emit::None => {}
1642 Emit::Metadata if is_rustdoc => {}
1643 Emit::Metadata => {
1644 rustc.args(&["--emit", "metadata"]);
1645 }
1646 Emit::LlvmIr => {
1647 rustc.args(&["--emit", "llvm-ir"]);
1648 }
1649 Emit::Mir => {
1650 rustc.args(&["--emit", "mir"]);
1651 }
1652 Emit::Asm => {
1653 rustc.args(&["--emit", "asm"]);
1654 }
1655 Emit::LinkArgsAsm => {
1656 rustc.args(&["-Clink-args=--emit=asm"]);
1657 }
1658 }
1659
1660 if !is_rustdoc {
1661 if self.config.target == "wasm32-unknown-unknown" || self.is_vxworks_pure_static() {
1662 } else if !self.props.no_prefer_dynamic {
1664 rustc.args(&["-C", "prefer-dynamic"]);
1665 }
1666 }
1667
1668 match output_file {
1669 _ if self.props.compile_flags.iter().any(|flag| flag == "-o") => {}
1672 TargetLocation::ThisFile(path) => {
1673 rustc.arg("-o").arg(path);
1674 }
1675 TargetLocation::ThisDirectory(path) => {
1676 if is_rustdoc {
1677 rustc.arg("-o").arg(path);
1679 } else {
1680 rustc.arg("--out-dir").arg(path);
1681 }
1682 }
1683 }
1684
1685 match self.config.compare_mode {
1686 Some(CompareMode::Polonius) => {
1687 rustc.args(&["-Zpolonius"]);
1688 }
1689 Some(CompareMode::NextSolver) => {
1690 rustc.args(&["-Znext-solver"]);
1691 }
1692 Some(CompareMode::NextSolverCoherence) => {
1693 rustc.args(&["-Znext-solver=coherence"]);
1694 }
1695 Some(CompareMode::SplitDwarf) if self.config.target.contains("windows") => {
1696 rustc.args(&["-Csplit-debuginfo=unpacked", "-Zunstable-options"]);
1697 }
1698 Some(CompareMode::SplitDwarf) => {
1699 rustc.args(&["-Csplit-debuginfo=unpacked"]);
1700 }
1701 Some(CompareMode::SplitDwarfSingle) => {
1702 rustc.args(&["-Csplit-debuginfo=packed"]);
1703 }
1704 None => {}
1705 }
1706
1707 if let AllowUnused::Yes = allow_unused {
1710 rustc.args(&["-A", "unused"]);
1711 }
1712
1713 rustc.args(&["-A", "internal_features"]);
1715
1716 if self.props.force_host {
1717 self.maybe_add_external_args(&mut rustc, &self.config.host_rustcflags);
1718 if !is_rustdoc {
1719 if let Some(ref linker) = self.config.host_linker {
1720 rustc.arg(format!("-Clinker={}", linker));
1721 }
1722 }
1723 } else {
1724 self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags);
1725 if !is_rustdoc {
1726 if let Some(ref linker) = self.config.target_linker {
1727 rustc.arg(format!("-Clinker={}", linker));
1728 }
1729 }
1730 }
1731
1732 if self.config.host.contains("musl") || self.is_vxworks_pure_dynamic() {
1734 rustc.arg("-Ctarget-feature=-crt-static");
1735 }
1736
1737 if let LinkToAux::Yes = link_to_aux {
1738 if self.has_aux_dir() {
1741 rustc.arg("-L").arg(self.aux_output_dir_name());
1742 }
1743 }
1744
1745 rustc.args(&self.props.compile_flags);
1746
1747 if self.props.add_core_stubs {
1753 rustc.arg("-Cpanic=abort");
1754 }
1755
1756 rustc
1757 }
1758
1759 fn make_exe_name(&self) -> PathBuf {
1760 let mut f = self.output_base_dir().join("a");
1765 if self.config.target.contains("emscripten") {
1767 f = f.with_extra_extension("js");
1768 } else if self.config.target.starts_with("wasm") {
1769 f = f.with_extra_extension("wasm");
1770 } else if self.config.target.contains("spirv") {
1771 f = f.with_extra_extension("spv");
1772 } else if !env::consts::EXE_SUFFIX.is_empty() {
1773 f = f.with_extra_extension(env::consts::EXE_SUFFIX);
1774 }
1775 f
1776 }
1777
1778 fn make_run_args(&self) -> ProcArgs {
1779 let mut args = self.split_maybe_args(&self.config.runner);
1782
1783 let exe_file = self.make_exe_name();
1784
1785 args.push(exe_file.into_os_string());
1786
1787 args.extend(self.props.run_flags.iter().map(OsString::from));
1789
1790 let prog = args.remove(0);
1791 ProcArgs { prog, args }
1792 }
1793
1794 fn split_maybe_args(&self, argstr: &Option<String>) -> Vec<OsString> {
1795 match *argstr {
1796 Some(ref s) => s
1797 .split(' ')
1798 .filter_map(|s| {
1799 if s.chars().all(|c| c.is_whitespace()) {
1800 None
1801 } else {
1802 Some(OsString::from(s))
1803 }
1804 })
1805 .collect(),
1806 None => Vec::new(),
1807 }
1808 }
1809
1810 fn make_cmdline(&self, command: &Command, libpath: &str) -> String {
1811 use crate::util;
1812
1813 if cfg!(unix) {
1815 format!("{:?}", command)
1816 } else {
1817 fn lib_path_cmd_prefix(path: &str) -> String {
1820 format!("{}=\"{}\"", util::lib_path_env_var(), util::make_new_path(path))
1821 }
1822
1823 format!("{} {:?}", lib_path_cmd_prefix(libpath), command)
1824 }
1825 }
1826
1827 fn dump_output(&self, print_output: bool, proc_name: &str, out: &str, err: &str) {
1828 let revision = if let Some(r) = self.revision { format!("{}.", r) } else { String::new() };
1829
1830 self.dump_output_file(out, &format!("{}out", revision));
1831 self.dump_output_file(err, &format!("{}err", revision));
1832
1833 if !print_output {
1834 return;
1835 }
1836
1837 let path = Path::new(proc_name);
1838 let proc_name = if path.file_stem().is_some_and(|p| p == "rmake") {
1839 OsString::from_iter(
1840 path.parent()
1841 .unwrap()
1842 .file_name()
1843 .into_iter()
1844 .chain(Some(OsStr::new("/")))
1845 .chain(path.file_name()),
1846 )
1847 } else {
1848 path.file_name().unwrap().into()
1849 };
1850 let proc_name = proc_name.to_string_lossy();
1851 println!("------{proc_name} stdout------------------------------");
1852 println!("{}", out);
1853 println!("------{proc_name} stderr------------------------------");
1854 println!("{}", err);
1855 println!("------------------------------------------");
1856 }
1857
1858 fn dump_output_file(&self, out: &str, extension: &str) {
1859 let outfile = self.make_out_name(extension);
1860 fs::write(&outfile, out).unwrap();
1861 }
1862
1863 fn make_out_name(&self, extension: &str) -> PathBuf {
1866 self.output_base_name().with_extension(extension)
1867 }
1868
1869 fn aux_output_dir_name(&self) -> PathBuf {
1872 self.output_base_dir()
1873 .join("auxiliary")
1874 .with_extra_extension(self.config.mode.aux_dir_disambiguator())
1875 }
1876
1877 fn aux_bin_output_dir_name(&self) -> PathBuf {
1880 self.aux_output_dir_name().join("bin")
1881 }
1882
1883 fn output_testname_unique(&self) -> PathBuf {
1885 output_testname_unique(self.config, self.testpaths, self.safe_revision())
1886 }
1887
1888 fn safe_revision(&self) -> Option<&str> {
1891 if self.config.mode == Incremental { None } else { self.revision }
1892 }
1893
1894 fn output_base_dir(&self) -> PathBuf {
1898 output_base_dir(self.config, self.testpaths, self.safe_revision())
1899 }
1900
1901 fn output_base_name(&self) -> PathBuf {
1905 output_base_name(self.config, self.testpaths, self.safe_revision())
1906 }
1907
1908 fn error(&self, err: &str) {
1909 match self.revision {
1910 Some(rev) => println!("\nerror in revision `{}`: {}", rev, err),
1911 None => println!("\nerror: {}", err),
1912 }
1913 }
1914
1915 #[track_caller]
1916 fn fatal(&self, err: &str) -> ! {
1917 self.error(err);
1918 error!("fatal error, panic: {:?}", err);
1919 panic!("fatal error");
1920 }
1921
1922 fn fatal_proc_rec(&self, err: &str, proc_res: &ProcRes) -> ! {
1923 self.error(err);
1924 proc_res.fatal(None, || ());
1925 }
1926
1927 fn fatal_proc_rec_with_ctx(
1928 &self,
1929 err: &str,
1930 proc_res: &ProcRes,
1931 on_failure: impl FnOnce(Self),
1932 ) -> ! {
1933 self.error(err);
1934 proc_res.fatal(None, || on_failure(*self));
1935 }
1936
1937 fn compile_test_and_save_ir(&self) -> (ProcRes, PathBuf) {
1940 let output_path = self.output_base_name().with_extension("ll");
1941 let input_file = &self.testpaths.file;
1942 let rustc = self.make_compile_args(
1943 input_file,
1944 TargetLocation::ThisFile(output_path.clone()),
1945 Emit::LlvmIr,
1946 AllowUnused::No,
1947 LinkToAux::Yes,
1948 Vec::new(),
1949 );
1950
1951 let proc_res = self.compose_and_run_compiler(rustc, None, self.testpaths);
1952 (proc_res, output_path)
1953 }
1954
1955 fn verify_with_filecheck(&self, output: &Path) -> ProcRes {
1956 let mut filecheck = Command::new(self.config.llvm_filecheck.as_ref().unwrap());
1957 filecheck.arg("--input-file").arg(output).arg(&self.testpaths.file);
1958
1959 filecheck.arg("--check-prefix=CHECK");
1961
1962 if let Some(rev) = self.revision {
1970 filecheck.arg("--check-prefix").arg(rev);
1971 }
1972
1973 filecheck.arg("--allow-unused-prefixes");
1977
1978 filecheck.args(&["--dump-input-context", "100"]);
1980
1981 filecheck.args(&self.props.filecheck_flags);
1983
1984 self.compose_and_run(filecheck, "", None, None)
1985 }
1986
1987 fn charset() -> &'static str {
1988 if cfg!(target_os = "freebsd") { "ISO-8859-1" } else { "UTF-8" }
1990 }
1991
1992 fn compare_to_default_rustdoc(&mut self, out_dir: &Path) {
1993 if !self.config.has_html_tidy {
1994 return;
1995 }
1996 println!("info: generating a diff against nightly rustdoc");
1997
1998 let suffix =
1999 self.safe_revision().map_or("nightly".into(), |path| path.to_owned() + "-nightly");
2000 let compare_dir = output_base_dir(self.config, self.testpaths, Some(&suffix));
2001 remove_and_create_dir_all(&compare_dir);
2002
2003 let new_rustdoc = TestCx {
2005 config: &Config {
2006 rustdoc_path: Some("rustdoc".into()),
2009 rustc_path: "rustc".into(),
2011 ..self.config.clone()
2012 },
2013 ..*self
2014 };
2015
2016 let output_file = TargetLocation::ThisDirectory(new_rustdoc.aux_output_dir_name());
2017 let mut rustc = new_rustdoc.make_compile_args(
2018 &new_rustdoc.testpaths.file,
2019 output_file,
2020 Emit::None,
2021 AllowUnused::Yes,
2022 LinkToAux::Yes,
2023 Vec::new(),
2024 );
2025 let aux_dir = new_rustdoc.aux_output_dir();
2026 new_rustdoc.build_all_auxiliary(&new_rustdoc.testpaths, &aux_dir, &mut rustc);
2027
2028 let proc_res = new_rustdoc.document(&compare_dir, &new_rustdoc.testpaths);
2029 if !proc_res.status.success() {
2030 eprintln!("failed to run nightly rustdoc");
2031 return;
2032 }
2033
2034 #[rustfmt::skip]
2035 let tidy_args = [
2036 "--new-blocklevel-tags", "rustdoc-search,rustdoc-toolbar",
2037 "--indent", "yes",
2038 "--indent-spaces", "2",
2039 "--wrap", "0",
2040 "--show-warnings", "no",
2041 "--markup", "yes",
2042 "--quiet", "yes",
2043 "-modify",
2044 ];
2045 let tidy_dir = |dir| {
2046 for entry in walkdir::WalkDir::new(dir) {
2047 let entry = entry.expect("failed to read file");
2048 if entry.file_type().is_file()
2049 && entry.path().extension().and_then(|p| p.to_str()) == Some("html")
2050 {
2051 let status =
2052 Command::new("tidy").args(&tidy_args).arg(entry.path()).status().unwrap();
2053 assert!(status.success() || status.code() == Some(1));
2055 }
2056 }
2057 };
2058 tidy_dir(out_dir);
2059 tidy_dir(&compare_dir);
2060
2061 let pager = {
2062 let output = Command::new("git").args(&["config", "--get", "core.pager"]).output().ok();
2063 output.and_then(|out| {
2064 if out.status.success() {
2065 Some(String::from_utf8(out.stdout).expect("invalid UTF8 in git pager"))
2066 } else {
2067 None
2068 }
2069 })
2070 };
2071
2072 let diff_filename = format!("build/tmp/rustdoc-compare-{}.diff", std::process::id());
2073
2074 if !write_filtered_diff(
2075 &diff_filename,
2076 out_dir,
2077 &compare_dir,
2078 self.config.verbose,
2079 |file_type, extension| {
2080 file_type.is_file() && (extension == Some("html") || extension == Some("js"))
2081 },
2082 ) {
2083 return;
2084 }
2085
2086 match self.config.color {
2087 ColorConfig::AlwaysColor => colored::control::set_override(true),
2088 ColorConfig::NeverColor => colored::control::set_override(false),
2089 _ => {}
2090 }
2091
2092 if let Some(pager) = pager {
2093 let pager = pager.trim();
2094 if self.config.verbose {
2095 eprintln!("using pager {}", pager);
2096 }
2097 let output = Command::new(pager)
2098 .env("PAGER", "")
2100 .stdin(File::open(&diff_filename).unwrap())
2101 .output()
2104 .unwrap();
2105 assert!(output.status.success());
2106 println!("{}", String::from_utf8_lossy(&output.stdout));
2107 eprintln!("{}", String::from_utf8_lossy(&output.stderr));
2108 } else {
2109 use colored::Colorize;
2110 eprintln!("warning: no pager configured, falling back to unified diff");
2111 eprintln!(
2112 "help: try configuring a git pager (e.g. `delta`) with `git config --global core.pager delta`"
2113 );
2114 let mut out = io::stdout();
2115 let mut diff = BufReader::new(File::open(&diff_filename).unwrap());
2116 let mut line = Vec::new();
2117 loop {
2118 line.truncate(0);
2119 match diff.read_until(b'\n', &mut line) {
2120 Ok(0) => break,
2121 Ok(_) => {}
2122 Err(e) => eprintln!("ERROR: {:?}", e),
2123 }
2124 match String::from_utf8(line.clone()) {
2125 Ok(line) => {
2126 if line.starts_with('+') {
2127 write!(&mut out, "{}", line.green()).unwrap();
2128 } else if line.starts_with('-') {
2129 write!(&mut out, "{}", line.red()).unwrap();
2130 } else if line.starts_with('@') {
2131 write!(&mut out, "{}", line.blue()).unwrap();
2132 } else {
2133 out.write_all(line.as_bytes()).unwrap();
2134 }
2135 }
2136 Err(_) => {
2137 write!(&mut out, "{}", String::from_utf8_lossy(&line).reversed()).unwrap();
2138 }
2139 }
2140 }
2141 };
2142 }
2143
2144 fn get_lines<P: AsRef<Path>>(
2145 &self,
2146 path: &P,
2147 mut other_files: Option<&mut Vec<String>>,
2148 ) -> Vec<usize> {
2149 let content = fs::read_to_string(&path).unwrap();
2150 let mut ignore = false;
2151 content
2152 .lines()
2153 .enumerate()
2154 .filter_map(|(line_nb, line)| {
2155 if (line.trim_start().starts_with("pub mod ")
2156 || line.trim_start().starts_with("mod "))
2157 && line.ends_with(';')
2158 {
2159 if let Some(ref mut other_files) = other_files {
2160 other_files.push(line.rsplit("mod ").next().unwrap().replace(';', ""));
2161 }
2162 None
2163 } else {
2164 let sline = line.rsplit("///").next().unwrap();
2165 let line = sline.trim_start();
2166 if line.starts_with("```") {
2167 if ignore {
2168 ignore = false;
2169 None
2170 } else {
2171 ignore = true;
2172 Some(line_nb + 1)
2173 }
2174 } else {
2175 None
2176 }
2177 }
2178 })
2179 .collect()
2180 }
2181
2182 fn check_rustdoc_test_option(&self, res: ProcRes) {
2187 let mut other_files = Vec::new();
2188 let mut files: HashMap<String, Vec<usize>> = HashMap::new();
2189 let normalized = fs::canonicalize(&self.testpaths.file).expect("failed to canonicalize");
2190 let normalized = normalized.to_str().unwrap().replace('\\', "/");
2191 files.insert(normalized, self.get_lines(&self.testpaths.file, Some(&mut other_files)));
2192 for other_file in other_files {
2193 let mut path = self.testpaths.file.clone();
2194 path.set_file_name(&format!("{}.rs", other_file));
2195 let path = fs::canonicalize(path).expect("failed to canonicalize");
2196 let normalized = path.to_str().unwrap().replace('\\', "/");
2197 files.insert(normalized, self.get_lines(&path, None));
2198 }
2199
2200 let mut tested = 0;
2201 for _ in res.stdout.split('\n').filter(|s| s.starts_with("test ")).inspect(|s| {
2202 if let Some((left, right)) = s.split_once(" - ") {
2203 let path = left.rsplit("test ").next().unwrap();
2204 let path = fs::canonicalize(&path).expect("failed to canonicalize");
2205 let path = path.to_str().unwrap().replace('\\', "/");
2206 if let Some(ref mut v) = files.get_mut(&path) {
2207 tested += 1;
2208 let mut iter = right.split("(line ");
2209 iter.next();
2210 let line = iter
2211 .next()
2212 .unwrap_or(")")
2213 .split(')')
2214 .next()
2215 .unwrap_or("0")
2216 .parse()
2217 .unwrap_or(0);
2218 if let Ok(pos) = v.binary_search(&line) {
2219 v.remove(pos);
2220 } else {
2221 self.fatal_proc_rec(
2222 &format!("Not found doc test: \"{}\" in \"{}\":{:?}", s, path, v),
2223 &res,
2224 );
2225 }
2226 }
2227 }
2228 }) {}
2229 if tested == 0 {
2230 self.fatal_proc_rec(&format!("No test has been found... {:?}", files), &res);
2231 } else {
2232 for (entry, v) in &files {
2233 if !v.is_empty() {
2234 self.fatal_proc_rec(
2235 &format!(
2236 "Not found test at line{} \"{}\":{:?}",
2237 if v.len() > 1 { "s" } else { "" },
2238 entry,
2239 v
2240 ),
2241 &res,
2242 );
2243 }
2244 }
2245 }
2246 }
2247
2248 fn force_color_svg(&self) -> bool {
2249 self.props.compile_flags.iter().any(|s| s.contains("--color=always"))
2250 }
2251
2252 fn load_compare_outputs(
2253 &self,
2254 proc_res: &ProcRes,
2255 output_kind: TestOutput,
2256 explicit_format: bool,
2257 ) -> usize {
2258 let stderr_bits = format!("{}bit.stderr", self.config.get_pointer_width());
2259 let (stderr_kind, stdout_kind) = match output_kind {
2260 TestOutput::Compile => (
2261 if self.force_color_svg() {
2262 if self.config.target.contains("windows") {
2263 UI_WINDOWS_SVG
2266 } else {
2267 UI_SVG
2268 }
2269 } else if self.props.stderr_per_bitwidth {
2270 &stderr_bits
2271 } else {
2272 UI_STDERR
2273 },
2274 UI_STDOUT,
2275 ),
2276 TestOutput::Run => (UI_RUN_STDERR, UI_RUN_STDOUT),
2277 };
2278
2279 let expected_stderr = self.load_expected_output(stderr_kind);
2280 let expected_stdout = self.load_expected_output(stdout_kind);
2281
2282 let mut normalized_stdout =
2283 self.normalize_output(&proc_res.stdout, &self.props.normalize_stdout);
2284 match output_kind {
2285 TestOutput::Run if self.config.remote_test_client.is_some() => {
2286 normalized_stdout = static_regex!(
2291 "^uploaded \"\\$TEST_BUILD_DIR(/[[:alnum:]_\\-.]+)+\", waiting for result\n"
2292 )
2293 .replace(&normalized_stdout, "")
2294 .to_string();
2295 normalized_stdout = static_regex!("^died due to signal [0-9]+\n")
2298 .replace(&normalized_stdout, "")
2299 .to_string();
2300 }
2303 _ => {}
2304 };
2305
2306 let stderr = if self.force_color_svg() {
2307 anstyle_svg::Term::new().render_svg(&proc_res.stderr)
2308 } else if explicit_format {
2309 proc_res.stderr.clone()
2310 } else {
2311 json::extract_rendered(&proc_res.stderr)
2312 };
2313
2314 let normalized_stderr = self.normalize_output(&stderr, &self.props.normalize_stderr);
2315 let mut errors = 0;
2316 match output_kind {
2317 TestOutput::Compile => {
2318 if !self.props.dont_check_compiler_stdout {
2319 if self
2320 .compare_output(
2321 stdout_kind,
2322 &normalized_stdout,
2323 &proc_res.stdout,
2324 &expected_stdout,
2325 )
2326 .should_error()
2327 {
2328 errors += 1;
2329 }
2330 }
2331 if !self.props.dont_check_compiler_stderr {
2332 if self
2333 .compare_output(stderr_kind, &normalized_stderr, &stderr, &expected_stderr)
2334 .should_error()
2335 {
2336 errors += 1;
2337 }
2338 }
2339 }
2340 TestOutput::Run => {
2341 if self
2342 .compare_output(
2343 stdout_kind,
2344 &normalized_stdout,
2345 &proc_res.stdout,
2346 &expected_stdout,
2347 )
2348 .should_error()
2349 {
2350 errors += 1;
2351 }
2352
2353 if self
2354 .compare_output(stderr_kind, &normalized_stderr, &stderr, &expected_stderr)
2355 .should_error()
2356 {
2357 errors += 1;
2358 }
2359 }
2360 }
2361 errors
2362 }
2363
2364 fn normalize_output(&self, output: &str, custom_rules: &[(String, String)]) -> String {
2365 let rflags = self.props.run_flags.join(" ");
2368 let cflags = self.props.compile_flags.join(" ");
2369 let json = rflags.contains("--format json")
2370 || rflags.contains("--format=json")
2371 || cflags.contains("--error-format json")
2372 || cflags.contains("--error-format pretty-json")
2373 || cflags.contains("--error-format=json")
2374 || cflags.contains("--error-format=pretty-json")
2375 || cflags.contains("--output-format json")
2376 || cflags.contains("--output-format=json");
2377
2378 let mut normalized = output.to_string();
2379
2380 let mut normalize_path = |from: &Path, to: &str| {
2381 let mut from = from.display().to_string();
2382 if json {
2383 from = from.replace("\\", "\\\\");
2384 }
2385 normalized = normalized.replace(&from, to);
2386 };
2387
2388 let parent_dir = self.testpaths.file.parent().unwrap();
2389 normalize_path(parent_dir, "$DIR");
2390
2391 if self.props.remap_src_base {
2392 let mut remapped_parent_dir = PathBuf::from(FAKE_SRC_BASE);
2393 if self.testpaths.relative_dir != Path::new("") {
2394 remapped_parent_dir.push(&self.testpaths.relative_dir);
2395 }
2396 normalize_path(&remapped_parent_dir, "$DIR");
2397 }
2398
2399 let base_dir = Path::new("/rustc/FAKE_PREFIX");
2400 normalize_path(&base_dir.join("library"), "$SRC_DIR");
2402 normalize_path(&base_dir.join("compiler"), "$COMPILER_DIR");
2406
2407 let rust_src_dir = &self.config.sysroot_base.join("lib/rustlib/src/rust");
2409 rust_src_dir.try_exists().expect(&*format!("{} should exists", rust_src_dir.display()));
2410 let rust_src_dir = rust_src_dir.read_link().unwrap_or(rust_src_dir.to_path_buf());
2411 normalize_path(&rust_src_dir.join("library"), "$SRC_DIR_REAL");
2412
2413 let test_build_dir = &self.config.build_base;
2415 let parent_build_dir = test_build_dir.parent().unwrap().parent().unwrap().parent().unwrap();
2416
2417 normalize_path(test_build_dir, "$TEST_BUILD_DIR");
2419 normalize_path(parent_build_dir, "$BUILD_DIR");
2421
2422 if json {
2423 normalized = normalized.replace("\\n", "\n");
2428 }
2429
2430 normalized = static_regex!("SRC_DIR(.+):\\d+:\\d+(: \\d+:\\d+)?")
2435 .replace_all(&normalized, "SRC_DIR$1:LL:COL")
2436 .into_owned();
2437
2438 normalized = Self::normalize_platform_differences(&normalized);
2439 normalized = normalized.replace("\t", "\\t"); normalized =
2446 static_regex!("\\s*//(\\[.*\\])?~.*").replace_all(&normalized, "").into_owned();
2447
2448 let v0_crate_hash_prefix_re = static_regex!(r"_R.*?Cs[0-9a-zA-Z]+_");
2451 let v0_crate_hash_re = static_regex!(r"Cs[0-9a-zA-Z]+_");
2452
2453 const V0_CRATE_HASH_PLACEHOLDER: &str = r"CsCRATE_HASH_";
2454 if v0_crate_hash_prefix_re.is_match(&normalized) {
2455 normalized =
2457 v0_crate_hash_re.replace_all(&normalized, V0_CRATE_HASH_PLACEHOLDER).into_owned();
2458 }
2459
2460 let v0_back_ref_prefix_re = static_regex!(r"\(_R.*?B[0-9a-zA-Z]_");
2461 let v0_back_ref_re = static_regex!(r"B[0-9a-zA-Z]_");
2462
2463 const V0_BACK_REF_PLACEHOLDER: &str = r"B<REF>_";
2464 if v0_back_ref_prefix_re.is_match(&normalized) {
2465 normalized =
2467 v0_back_ref_re.replace_all(&normalized, V0_BACK_REF_PLACEHOLDER).into_owned();
2468 }
2469
2470 {
2477 let mut seen_allocs = indexmap::IndexSet::new();
2478
2479 normalized = static_regex!(
2481 r"╾─*a(lloc)?([0-9]+)(\+0x[0-9]+)?(<imm>)?( \([0-9]+ ptr bytes\))?─*╼"
2482 )
2483 .replace_all(&normalized, |caps: &Captures<'_>| {
2484 let index = caps.get(2).unwrap().as_str().to_string();
2486 let (index, _) = seen_allocs.insert_full(index);
2487 let offset = caps.get(3).map_or("", |c| c.as_str());
2488 let imm = caps.get(4).map_or("", |c| c.as_str());
2489 format!("╾ALLOC{index}{offset}{imm}╼")
2491 })
2492 .into_owned();
2493
2494 normalized = static_regex!(r"\balloc([0-9]+)\b")
2496 .replace_all(&normalized, |caps: &Captures<'_>| {
2497 let index = caps.get(1).unwrap().as_str().to_string();
2498 let (index, _) = seen_allocs.insert_full(index);
2499 format!("ALLOC{index}")
2500 })
2501 .into_owned();
2502 }
2503
2504 for rule in custom_rules {
2506 let re = Regex::new(&rule.0).expect("bad regex in custom normalization rule");
2507 normalized = re.replace_all(&normalized, &rule.1[..]).into_owned();
2508 }
2509 normalized
2510 }
2511
2512 fn normalize_platform_differences(output: &str) -> String {
2518 let output = output.replace(r"\\", r"\");
2519
2520 static_regex!(
2525 r#"(?x)
2526 (?:
2527 # Match paths that don't include spaces.
2528 (?:\\[\pL\pN\.\-_']+)+\.\pL+
2529 |
2530 # If the path starts with a well-known root, then allow spaces and no file extension.
2531 \$(?:DIR|SRC_DIR|TEST_BUILD_DIR|BUILD_DIR|LIB_DIR)(?:\\[\pL\pN\.\-_'\ ]+)+
2532 )"#
2533 )
2534 .replace_all(&output, |caps: &Captures<'_>| {
2535 println!("{}", &caps[0]);
2536 caps[0].replace(r"\", "/")
2537 })
2538 .replace("\r\n", "\n")
2539 }
2540
2541 fn expected_output_path(&self, kind: &str) -> PathBuf {
2542 let mut path =
2543 expected_output_path(&self.testpaths, self.revision, &self.config.compare_mode, kind);
2544
2545 if !path.exists() {
2546 if let Some(CompareMode::Polonius) = self.config.compare_mode {
2547 path = expected_output_path(&self.testpaths, self.revision, &None, kind);
2548 }
2549 }
2550
2551 if !path.exists() {
2552 path = expected_output_path(&self.testpaths, self.revision, &None, kind);
2553 }
2554
2555 path
2556 }
2557
2558 fn load_expected_output(&self, kind: &str) -> String {
2559 let path = self.expected_output_path(kind);
2560 if path.exists() {
2561 match self.load_expected_output_from_path(&path) {
2562 Ok(x) => x,
2563 Err(x) => self.fatal(&x),
2564 }
2565 } else {
2566 String::new()
2567 }
2568 }
2569
2570 fn load_expected_output_from_path(&self, path: &Path) -> Result<String, String> {
2571 fs::read_to_string(path).map_err(|err| {
2572 format!("failed to load expected output from `{}`: {}", path.display(), err)
2573 })
2574 }
2575
2576 fn delete_file(&self, file: &Path) {
2577 if !file.exists() {
2578 return;
2580 }
2581 if let Err(e) = fs::remove_file(file) {
2582 self.fatal(&format!("failed to delete `{}`: {}", file.display(), e,));
2583 }
2584 }
2585
2586 fn compare_output(
2587 &self,
2588 stream: &str,
2589 actual: &str,
2590 actual_unnormalized: &str,
2591 expected: &str,
2592 ) -> CompareOutcome {
2593 let expected_path =
2594 expected_output_path(self.testpaths, self.revision, &self.config.compare_mode, stream);
2595
2596 if self.config.bless && actual.is_empty() && expected_path.exists() {
2597 self.delete_file(&expected_path);
2598 }
2599
2600 let are_different = match (self.force_color_svg(), expected.find('\n'), actual.find('\n')) {
2601 (true, Some(nl_e), Some(nl_a)) => expected[nl_e..] != actual[nl_a..],
2604 _ => expected != actual,
2605 };
2606 if !are_different {
2607 return CompareOutcome::Same;
2608 }
2609
2610 let compare_output_by_lines = self.config.runner.is_some();
2614
2615 let tmp;
2616 let (expected, actual): (&str, &str) = if compare_output_by_lines {
2617 let actual_lines: HashSet<_> = actual.lines().collect();
2618 let expected_lines: Vec<_> = expected.lines().collect();
2619 let mut used = expected_lines.clone();
2620 used.retain(|line| actual_lines.contains(line));
2621 if used.len() == expected_lines.len() && (expected.is_empty() == actual.is_empty()) {
2623 return CompareOutcome::Same;
2624 }
2625 if expected_lines.is_empty() {
2626 ("", actual)
2628 } else {
2629 tmp = (expected_lines.join("\n"), used.join("\n"));
2630 (&tmp.0, &tmp.1)
2631 }
2632 } else {
2633 (expected, actual)
2634 };
2635
2636 let test_name = self.config.compare_mode.as_ref().map_or("", |m| m.to_str());
2638 let actual_path = self
2639 .output_base_name()
2640 .with_extra_extension(self.revision.unwrap_or(""))
2641 .with_extra_extension(test_name)
2642 .with_extra_extension(stream);
2643
2644 if let Err(err) = fs::write(&actual_path, &actual) {
2645 self.fatal(&format!("failed to write {stream} to `{actual_path:?}`: {err}",));
2646 }
2647 println!("Saved the actual {stream} to {actual_path:?}");
2648
2649 if !self.config.bless {
2650 if expected.is_empty() {
2651 println!("normalized {}:\n{}\n", stream, actual);
2652 } else {
2653 self.show_diff(
2654 stream,
2655 &expected_path,
2656 &actual_path,
2657 expected,
2658 actual,
2659 actual_unnormalized,
2660 );
2661 }
2662 } else {
2663 if self.revision.is_some() {
2666 let old =
2667 expected_output_path(self.testpaths, None, &self.config.compare_mode, stream);
2668 self.delete_file(&old);
2669 }
2670
2671 if !actual.is_empty() {
2672 if let Err(err) = fs::write(&expected_path, &actual) {
2673 self.fatal(&format!("failed to write {stream} to `{expected_path:?}`: {err}"));
2674 }
2675 println!("Blessing the {stream} of {test_name} in {expected_path:?}");
2676 }
2677 }
2678
2679 println!("\nThe actual {0} differed from the expected {0}.", stream);
2680
2681 if self.config.bless { CompareOutcome::Blessed } else { CompareOutcome::Differed }
2682 }
2683
2684 fn show_diff(
2686 &self,
2687 stream: &str,
2688 expected_path: &Path,
2689 actual_path: &Path,
2690 expected: &str,
2691 actual: &str,
2692 actual_unnormalized: &str,
2693 ) {
2694 eprintln!("diff of {stream}:\n");
2695 if let Some(diff_command) = self.config.diff_command.as_deref() {
2696 let mut args = diff_command.split_whitespace();
2697 let name = args.next().unwrap();
2698 match Command::new(name).args(args).args([expected_path, actual_path]).output() {
2699 Err(err) => {
2700 self.fatal(&format!(
2701 "failed to call custom diff command `{diff_command}`: {err}"
2702 ));
2703 }
2704 Ok(output) => {
2705 let output = String::from_utf8_lossy(&output.stdout);
2706 eprint!("{output}");
2707 }
2708 }
2709 } else {
2710 eprint!("{}", write_diff(expected, actual, 3));
2711 }
2712
2713 let diff_results = make_diff(actual, expected, 0);
2715
2716 let (mut mismatches_normalized, mut mismatch_line_nos) = (String::new(), vec![]);
2717 for hunk in diff_results {
2718 let mut line_no = hunk.line_number;
2719 for line in hunk.lines {
2720 if let DiffLine::Expected(normalized) = line {
2722 mismatches_normalized += &normalized;
2723 mismatches_normalized += "\n";
2724 mismatch_line_nos.push(line_no);
2725 line_no += 1;
2726 }
2727 }
2728 }
2729 let mut mismatches_unnormalized = String::new();
2730 let diff_normalized = make_diff(actual, actual_unnormalized, 0);
2731 for hunk in diff_normalized {
2732 if mismatch_line_nos.contains(&hunk.line_number) {
2733 for line in hunk.lines {
2734 if let DiffLine::Resulting(unnormalized) = line {
2735 mismatches_unnormalized += &unnormalized;
2736 mismatches_unnormalized += "\n";
2737 }
2738 }
2739 }
2740 }
2741
2742 let normalized_diff = make_diff(&mismatches_normalized, &mismatches_unnormalized, 0);
2743 if !normalized_diff.is_empty()
2745 && !mismatches_unnormalized.is_empty()
2746 && !mismatches_normalized.is_empty()
2747 {
2748 eprintln!("Note: some mismatched output was normalized before being compared");
2749 eprint!("{}", write_diff(&mismatches_unnormalized, &mismatches_normalized, 0));
2751 }
2752 }
2753
2754 fn check_and_prune_duplicate_outputs(
2755 &self,
2756 proc_res: &ProcRes,
2757 modes: &[CompareMode],
2758 require_same_modes: &[CompareMode],
2759 ) {
2760 for kind in UI_EXTENSIONS {
2761 let canon_comparison_path =
2762 expected_output_path(&self.testpaths, self.revision, &None, kind);
2763
2764 let canon = match self.load_expected_output_from_path(&canon_comparison_path) {
2765 Ok(canon) => canon,
2766 _ => continue,
2767 };
2768 let bless = self.config.bless;
2769 let check_and_prune_duplicate_outputs = |mode: &CompareMode, require_same: bool| {
2770 let examined_path =
2771 expected_output_path(&self.testpaths, self.revision, &Some(mode.clone()), kind);
2772
2773 let examined_content = match self.load_expected_output_from_path(&examined_path) {
2775 Ok(content) => content,
2776 _ => return,
2777 };
2778
2779 let is_duplicate = canon == examined_content;
2780
2781 match (bless, require_same, is_duplicate) {
2782 (true, _, true) => {
2784 self.delete_file(&examined_path);
2785 }
2786 (_, true, false) => {
2789 self.fatal_proc_rec(
2790 &format!("`{}` should not have different output from base test!", kind),
2791 proc_res,
2792 );
2793 }
2794 _ => {}
2795 }
2796 };
2797 for mode in modes {
2798 check_and_prune_duplicate_outputs(mode, false);
2799 }
2800 for mode in require_same_modes {
2801 check_and_prune_duplicate_outputs(mode, true);
2802 }
2803 }
2804 }
2805
2806 fn create_stamp(&self) {
2807 let stamp_file_path = stamp_file_path(&self.config, self.testpaths, self.revision);
2808 fs::write(&stamp_file_path, compute_stamp_hash(&self.config)).unwrap();
2809 }
2810
2811 fn init_incremental_test(&self) {
2812 let incremental_dir = self.props.incremental_dir.as_ref().unwrap();
2819 if incremental_dir.exists() {
2820 let canonicalized = incremental_dir.canonicalize().unwrap();
2823 fs::remove_dir_all(canonicalized).unwrap();
2824 }
2825 fs::create_dir_all(&incremental_dir).unwrap();
2826
2827 if self.config.verbose {
2828 println!("init_incremental_test: incremental_dir={}", incremental_dir.display());
2829 }
2830 }
2831}
2832
2833struct ProcArgs {
2834 prog: OsString,
2835 args: Vec<OsString>,
2836}
2837
2838pub struct ProcRes {
2839 status: ExitStatus,
2840 stdout: String,
2841 stderr: String,
2842 truncated: Truncated,
2843 cmdline: String,
2844}
2845
2846impl ProcRes {
2847 pub fn print_info(&self) {
2848 fn render(name: &str, contents: &str) -> String {
2849 let contents = json::extract_rendered(contents);
2850 let contents = contents.trim_end();
2851 if contents.is_empty() {
2852 format!("{name}: none")
2853 } else {
2854 format!(
2855 "\
2856 --- {name} -------------------------------\n\
2857 {contents}\n\
2858 ------------------------------------------",
2859 )
2860 }
2861 }
2862
2863 println!(
2864 "status: {}\ncommand: {}\n{}\n{}\n",
2865 self.status,
2866 self.cmdline,
2867 render("stdout", &self.stdout),
2868 render("stderr", &self.stderr),
2869 );
2870 }
2871
2872 pub fn fatal(&self, err: Option<&str>, on_failure: impl FnOnce()) -> ! {
2873 if let Some(e) = err {
2874 println!("\nerror: {}", e);
2875 }
2876 self.print_info();
2877 on_failure();
2878 std::panic::resume_unwind(Box::new(()));
2881 }
2882}
2883
2884#[derive(Debug)]
2885enum TargetLocation {
2886 ThisFile(PathBuf),
2887 ThisDirectory(PathBuf),
2888}
2889
2890enum AllowUnused {
2891 Yes,
2892 No,
2893}
2894
2895enum LinkToAux {
2896 Yes,
2897 No,
2898}
2899
2900#[derive(Debug, PartialEq)]
2901enum AuxType {
2902 Bin,
2903 Lib,
2904 Dylib,
2905 ProcMacro,
2906}
2907
2908#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2911enum CompareOutcome {
2912 Same,
2914 Blessed,
2916 Differed,
2918}
2919
2920impl CompareOutcome {
2921 fn should_error(&self) -> bool {
2922 matches!(self, CompareOutcome::Differed)
2923 }
2924}